A cautionary tale of a developer, a cat contract, and a tragic misunderstanding of trust.
Once upon a time in the land of Solidity, I wrote a smart contract. It was bold. It was elegant. It was… secure.
At least, that’s what I told myself.
public owner;
constructor() {
owner = tx.origin;
}
"Look at that!" I said, proudly flexing in front of my rubber duck. "Only the person who deployed this contract — me — can call the sensitive functions! tx.origin
makes sure no impostors get through."
If only I knew.
One day, a new developer friend — let’s call her Mallory — sent me a new dApp to try out. It was called KittySnuggles.sol and promised to send you non-stop pictures of pixelated cats directly to your wallet.
How could I say no?
I clicked.
I approved the transaction.
It did, in fact, show me an adorable cat.
But then… my main contract — the one I lovingly named BankOfMe.sol
— sent Mallory all my ETH.
Let’s rewind.
My contract had this:
withdrawAll() external {
require(tx.origin == owner, "Not the owner!");
payable(owner).transfer(address(this).balance);
}
Seems fine, right?
WRONG.
Here's what really happened when I clicked on KittySnuggles:
I interacted with KittySnuggles (a malicious contract).
It, in turn, called BankOfMe and triggered withdrawAll()
.
Inside BankOfMe
, tx.origin
was still me, because I initiated the transaction!
The contract happily thought I was the one calling it.
But msg.sender
was KittySnuggles, not me.
Mallory's code ran. My ETH vanished.
tx.origin
You see, tx.origin
is like that overly trusting friend who always believes whoever started the drama is the good guy — even if the drama has passed through a dozen toxic people on its way to you.
Meanwhile, msg.sender
is more like a strict bouncer at a nightclub. It checks who is knocking right now, not who told someone to tell someone to knock.
Replace:
require(tx.origin == owner, "Not the owner!");
With:
require(msg.sender == owner, "Not the owner!");
Now, only the actual person or contract currently calling the function is checked — not the innocent human who just wanted cat pictures.
Never trust tx.origin
for authentication. It's like leaving your keys under the doormat and being surprised when someone uses them.
And Mallory? She’s living her best life somewhere on-chain, sipping piña coladas and rolling in my Ether. And I still don’t have my pixel cats.
“In Solidity, trust the caller (
msg.sender
), not the sender’s sender’s sender’s sender’s… ghost.”
Fabian Owuor