Use of tx.origin
¶
A natural requirement for smart contracts is to authorize certain parties to perform privileged operations. These could include administrative functions such as changing an admin address, withdrawing funds, or triggering protocol-critical mechanisms. At first glance, Solidity provides two mechanisms to identify the calling party: msg.sender
and tx.origin
. While they may seem interchangeable in many scenarios, their behavior diverges depending on how the contract is invoked - and misunderstanding this difference can introduce critical vulnerabilities.
To illustrate how these properties differ, consider a setup involving three entities: an admin address initiating a transaction, a target contract that enforces access control, and an optional proxy contract sitting between the admin and the target.
flowchart LR
Admin -->|msg.sender| Target
Admin --> Proxy
Proxy -->|tx.origin| Target
In this example, when the admin calls the target contract directly, both msg.sender
and tx.origin
yield the same value: the admin's address. However, when using a proxy contract, msg.sender
becomes the proxy contract, while tx.origin
remains the original admin address. The Solidity documentation describes these values as:
- msg.sender (address): sender of the message (current call)
- tx.origin (address): sender of the transaction (full call chain)
The consequence of this distinction is that if a contract uses tx.origin
to authorize a caller, it may inadvertently allow malicious proxy contracts to spoof the identity of the original sender. This is possible because smart contracts can invoke other contracts without the user’s explicit approval for each step in the call chain. By relying on tx.origin
, a contract effectively trusts the entire call path - not just the direct caller - creating a dangerous surface for phishing-style attacks.
Attack Scenario¶
Consider a lending protocol that supports user liquidations to make this vulnerability concrete. In its early version, the project uses a LiquidationManager
contract to ensure that only an authorized bot or actor can perform liquidations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The contract should ensure that only a specific address can initiate liquidations. However, because it uses tx.origin
to perform this check, the protection can be bypassed by an attacker deploying the following contract:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
If the authorized liquidator calls the bait()
function - perhaps assuming it's a contract peripherahl - the manager contract sees tx.origin
as the correct liquidator address and approves the call. In reality, the liquidation is triggered by a malicious proxy, targeting an unintended user. As a result, the attacker gains the ability to perform unauthorized liquidations.
Fragile EOA Detection¶
In addition to authorization logic, many smart contracts (especially early DeFi projects) try to ensure that only externally owned accounts (EOAs) can interact with them. This is commonly implemented using a simple check:
1 |
|
This check works under the assumption that if tx.origin
and msg.sender
are the same, the call must have originated from an EOA. However, this assumption is becoming increasingly fragile. With the upcoming Ethereum Pectra upgrade (scheduled for May 7, 2025), EIP-7702 will be introduced, allowing EOAs to transition into smart contract accounts and vice versa.
Through this mechanism - known as self-sponsoring - a smart contract can construct a transaction that makes msg.sender
and tx.origin
equal, even though the originator is no longer a traditional EOA. This fundamentally breaks the tx.origin == msg.sender
assumption. The EIP's security considerations explicitly warn:
Allowing the sender of an EIP-7702 to also set code has the possibility to:
- Break atomic sandwich protections which rely on tx.origin;
- Break reentrancy guards of the style
require(tx.origin == msg.sender)
.
While the EIP authors argue that these risks are acceptable in light of broader design goals, the developers' and auditors' responsibility is to recognize, flag, and avoid any anti-pattern using tx.origin
. As of 2025, very few legitimate use cases for tx.origin
are left, and post-EIP-7702, there will be even fewer ones.