Opcode Summary
| Property | Value |
|---|---|
| Opcode | 0x32 |
| Mnemonic | ORIGIN |
| Gas | 2 |
| Stack Input | (none) |
| Stack Output | tx.origin (address) |
| Behavior | Pushes the 160-bit address of the externally owned account (EOA) that originally signed and submitted the transaction. This value is constant for the entire transaction, regardless of how many contract-to-contract calls occur in the call chain. |
Threat Surface
ORIGIN is one of the most dangerous opcodes for access control in the EVM. Unlike CALLER (0x33), which returns the immediate caller and changes at every call boundary, ORIGIN always returns the original EOA that signed the transaction. This property makes it inherently unsuitable for authorization.
The core threat: any contract in the call chain can read tx.origin and it will always resolve to the same EOA. If a contract uses tx.origin == owner as an access control check, then any intermediary contract that the owner interacts with can call that contract and pass the check. This enables a devastating class of phishing attacks where an attacker deploys a malicious contract, tricks the victim into calling it (e.g., by disguising it as a legitimate DApp, airdrop claim, or token approval), and the malicious contract then calls the target contract with the victim’s tx.origin.
The vulnerability is classified as SWC-115 (Authorization through tx.origin) in the Smart Contract Weakness Classification registry and SCWE-018 in the OWASP Smart Contract Security framework. The Solidity documentation itself explicitly warns against using tx.origin for authorization.
Furthermore, the ongoing evolution of Ethereum’s account model — particularly EIP-4337 (Account Abstraction) and ERC-2771 (Meta-Transactions) — fundamentally alters the semantics of tx.origin, breaking contracts that rely on assumptions about its value.
Smart Contract Threats
T1: tx.origin Phishing Attack (Critical)
The canonical ORIGIN vulnerability. When a contract protects a privileged function with require(tx.origin == owner), any contract that the owner interacts with can invoke that function on the owner’s behalf.
Attack flow:
- Victim deploys a contract (e.g., a wallet) that uses
tx.origin == ownerfor access control - Attacker deploys a phishing contract disguised as a legitimate service (DEX router, airdrop claim, NFT mint)
- Attacker tricks the victim into sending a transaction to the phishing contract (social engineering, malicious link, fake UI)
- The phishing contract’s
receive(),fallback(), or a bait function calls the victim’s contract, invoking a privileged operation (e.g.,transferTo(attacker, address(this).balance)) - The victim’s contract checks
tx.origin == owner— this passes because the victim’s EOA initiated the transaction - Funds are transferred to the attacker
The attack succeeds because tx.origin pierces through the entire call chain. The victim never directly called their own contract, but tx.origin makes it look like they did.
T2: tx.origin != msg.sender in Multi-Contract Architectures (High)
In any architecture where contracts call other contracts (routers, proxies, aggregators, DAOs), tx.origin and msg.sender diverge. Contracts that use tx.origin for authorization will accept calls from any contract in a transaction initiated by an authorized EOA, even if the intermediate contract is malicious or compromised.
Common vulnerable patterns:
- DeFi routers/aggregators: A user interacts with a DEX aggregator that routes through multiple pools. Any pool contract could call back to a
tx.origin-protected contract. - Callback-based protocols: Flash loan receivers, Uniswap V3 swap callbacks, and similar patterns involve the protocol calling back into user-supplied code. If the callback contract calls a
tx.origin-protected function, the check passes. - Governance/DAO execution: When a multisig or DAO executes a proposal,
tx.originis the EOA that submitted the execution transaction, not the DAO itself.
T3: Account Abstraction (EIP-4337) Breaking tx.origin Assumptions (High)
EIP-4337 introduces smart contract wallets as first-class accounts. Under this model:
- Users submit
UserOperationobjects to a separate mempool - Bundlers (EOAs) package multiple
UserOperations into a single transaction and submit it to theEntryPointcontract tx.originresolves to the bundler’s EOA, not the user’s account
This means:
tx.originno longer identifies the user who authorized the operationrequire(tx.origin == owner)will always fail for EIP-4337 wallet users because the bundler is the transaction originator- The common guard
require(tx.origin == msg.sender)(used to ensure the caller is an EOA, not a contract) will fail for all smart contract wallet users, effectively locking them out - Contracts that use
tx.originfor any purpose will be incompatible with the account abstraction ecosystem
As EIP-4337 adoption grows, tx.origin-dependent contracts become progressively more broken.
T4: tx.origin-Based Access Control Bypass via Legitimate Interactions (Medium)
Even without a dedicated phishing contract, tx.origin checks can be bypassed through legitimate protocol interactions. If an authorized user interacts with any contract that has a callback mechanism, re-entrancy path, or hook system, that contract can call back to the tx.origin-protected contract.
Examples:
- ERC-777 token hooks: Receiving ERC-777 tokens triggers a
tokensReceivedhook on the recipient. If the recipient contract calls atx.origin-protected function during this hook, the check passes with the sender’stx.origin. - ERC-1155
onERC1155Received: Similar callback-based attack surface. - Flash loan callbacks: The borrower’s callback executes in the context of the original transaction, inheriting
tx.origin.
Protocol-Level Threats
P1: Account Abstraction Paradigm Shift (High)
EIP-4337 fundamentally redefines the relationship between transaction signers and tx.origin. At the protocol level:
- The
EntryPointcontract (a singleton deployed at a canonical address) executes all account-abstracted transactions.tx.originis the bundler, a third party with no relationship to the user. - Future EIPs (e.g., EIP-3074 AUTH/AUTHCALL, EIP-7702) further decouple authorization from
tx.originby allowing EOAs to delegate execution to invoker contracts. - Any protocol that reads
tx.originfor analytics, access control, or fee logic will produce incorrect results for an increasing fraction of users.
The Ethereum roadmap explicitly moves toward a world where tx.origin is meaningless for identifying the authorizing party.
P2: Meta-Transaction and Relayer Implications (Medium)
ERC-2771 (Secure Protocol for Native Meta Transactions) enables gasless transactions by having a trusted forwarder relay user-signed messages. In this model:
tx.originis the relayer’s EOA, not the user- The user’s identity is extracted from appended calldata via
_msgSender(), not fromtx.origin - Contracts using
tx.originwill identify the relayer (e.g., OpenZeppelin Defender, Gelato, Biconomy) as the transaction originator
In December 2023, a critical vulnerability was discovered in contracts combining ERC-2771 with Multicall. The delegatecall in Multicall allowed attackers to manipulate the appended sender data, enabling arbitrary address spoofing. This affected over 9,800 contracts across 37 chains and resulted in documented losses including 84.59 ETH and 17,394 USDC. While this vulnerability was in the _msgSender() extraction logic rather than tx.origin directly, it illustrates the dangers of transaction-origin abstractions.
P3: Cross-Chain and L2 Implications (Low)
On L2 rollups with sequencers or cross-chain bridges:
tx.originon an L2 may refer to the sequencer or bridge contract, not the original user- Cross-chain message passing (e.g., L1 → L2 via canonical bridges) sets
tx.originto the bridge executor - Contracts deployed across multiple chains that use
tx.originwill behave inconsistently
Edge Cases
| Edge Case | Behavior | Security Implication |
|---|---|---|
tx.origin == msg.sender | True only when the caller is the EOA that signed the transaction (no intermediate contracts) | Commonly used as an “is EOA” check; breaks under EIP-4337 account abstraction |
tx.origin in delegatecall | Returns the same value as in the calling context — the original EOA | Cannot be used to distinguish between direct and delegated calls |
tx.origin with contract wallets (Gnosis Safe, etc.) | Returns the EOA that submitted the Safe transaction to the chain, not the Safe itself | Multi-sig owners rotating submission duty means tx.origin changes unpredictably |
tx.origin in a staticcall | Same as in the calling context; staticcall does not alter tx.origin | Read-only calls still expose the original EOA to called contracts |
tx.origin in constructor | Returns the deployer EOA | Storing tx.origin as owner during construction works but has the same phishing risk post-deployment |
tx.origin with EIP-4337 | Returns the bundler’s EOA, not the user | All tx.origin-based authorization fails for smart wallet users |
tx.origin with meta-transactions (ERC-2771) | Returns the relayer’s EOA, not the user | tx.origin is useless for identifying the user in gasless transaction flows |
Multiple contracts reading tx.origin in one tx | All see the same value | Any contract in the call chain can impersonate the originator to tx.origin-protected contracts |
Real-World Exploits
Exploit 1: ERC-2771 + Multicall Address Spoofing — 9,800+ Contracts Affected (December 2023)
Root cause: Interaction between ERC-2771 meta-transaction context and Multicall’s delegatecall pattern allowed arbitrary sender spoofing.
Details: ERC-2771 appends the original sender’s address as the last 20 bytes of calldata, and _msgSender() extracts it using assembly. When combined with Multicall (which uses delegatecall(data) for each sub-call), an attacker could craft calldata where each sub-call’s last 20 bytes contained an arbitrary address, spoofing _msgSender() to impersonate any user.
Attack flow:
- Attacker constructs a forwarded request to a contract implementing both ERC-2771 and Multicall
- The outer request is properly signed by the attacker’s address
- Inside the multicall array, each sub-call is padded so its last 20 bytes are the victim’s address
- Each
delegatecallwithin Multicall resolves_msgSender()to the victim - Attacker executes privileged functions as the victim
Impact: Over 9,800 contracts across 37 chains were affected. Documented on-chain losses: 84.59 ETH, 17,394 USDC, and additional smaller amounts on Ethereum and Polygon. Thirdweb issued an emergency advisory. OpenZeppelin published patches and mitigation guidance.
Relevance to ORIGIN: While this exploit targeted _msgSender() rather than tx.origin directly, it demonstrates the fundamental fragility of transaction-origin-based identity. The ERC-2771 pattern exists precisely because tx.origin is inadequate, yet the replacement mechanism introduced its own spoofing vulnerability. Any system that tries to trace “who really initiated this” through layers of abstraction is inherently fragile.
References:
Exploit 2: Ethernaut Level 4 “Telephone” — Canonical tx.origin CTF Challenge
Root cause: Direct use of tx.origin for ownership transfer authorization.
Details: The Ethernaut “Telephone” challenge presents a contract where the changeOwner() function checks if (tx.origin != msg.sender) and, if true, sets the caller as the new owner. This is a teaching exercise, but it precisely models real vulnerable contracts.
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}Attack: Deploy an intermediary contract that calls changeOwner(attacker). When the attacker calls the intermediary, tx.origin (the attacker’s EOA) differs from msg.sender (the intermediary contract), so the condition passes and ownership is transferred.
Relevance: This pattern has been found in production contracts during audits. The Ethernaut challenge has been completed by hundreds of thousands of developers, demonstrating the accessibility of the attack.
References:
Exploit 3: Audit Findings in Production DeFi Protocols
Root cause: Persistent use of tx.origin for authorization in production DeFi code.
Details: Security audit firms (Trail of Bits, OpenZeppelin, Consensys Diligence) have repeatedly flagged tx.origin usage in production contracts:
- Lending protocol liquidation managers using
tx.originto authorize liquidator bots, allowing any contract the bot interacts with to trigger unauthorized liquidations - Token contracts using
tx.origin == deployerfor mint/pause functions, enabling phishing-based token supply manipulation - Governance contracts using
tx.originto identify voters, misattributing votes when proposals are executed through intermediate contracts
The OWASP Smart Contract Security Verification Standard (SCSVS) classifies tx.origin authorization as SCWE-018 and SWC Registry classifies it as SWC-115, both marking it as a critical vulnerability.
References:
Attack Scenarios
Scenario A: Classic Wallet Drain via Phishing Contract
// Vulnerable wallet contract
contract VulnerableWallet {
address public owner;
constructor() {
owner = msg.sender;
}
function transferTo(address payable to, uint256 amount) public {
require(tx.origin == owner, "Not owner");
to.transfer(amount);
}
receive() external payable {}
}
// Attacker's phishing contract
contract PhishingAttack {
VulnerableWallet immutable target;
address payable immutable attacker;
constructor(VulnerableWallet _target) {
target = _target;
attacker = payable(msg.sender);
}
// Disguised as an airdrop claim, NFT mint, or token approval.
// When the wallet owner calls this (or sends ETH), funds are drained.
receive() external payable {
target.transferTo(attacker, address(target).balance);
}
}Attack: Attacker sends the wallet owner a link to “claim free tokens” that triggers a transaction to the PhishingAttack contract. The receive() function calls transferTo() on the victim’s wallet. Since tx.origin is the owner’s EOA, the require passes and all funds are sent to the attacker.
Scenario B: Flash Loan Callback Exploitation
contract VulnerableLendingPool {
mapping(address => bool) public authorizedLiquidators;
function liquidate(address borrower) external {
require(tx.origin == msg.sender, "No contracts");
require(authorizedLiquidators[tx.origin], "Not authorized");
// ... perform liquidation ...
}
}
// Attacker tricks authorized liquidator into calling this
contract MaliciousFlashLoanReceiver {
VulnerableLendingPool pool;
function executeOperation(uint256 amount, uint256 fee, bytes calldata) external {
// Called during a flash loan callback -- tx.origin is the
// authorized liquidator who initiated the flash loan
pool.liquidate(targetBorrower);
}
}Attack: The authorized liquidator bot takes a flash loan for a legitimate purpose. The flash loan callback routes through the attacker’s contract, which calls liquidate(). Both checks pass: tx.origin == msg.sender is false (so this specific example would revert), but if the contract only checked authorizedLiquidators[tx.origin], the call succeeds because tx.origin is the authorized bot.
Scenario C: EIP-4337 Compatibility Failure
contract LegacyVault {
modifier onlyEOA() {
require(tx.origin == msg.sender, "No contracts allowed");
_;
}
function withdraw(uint256 amount) external onlyEOA {
// Users with EIP-4337 smart wallets can NEVER call this function.
// tx.origin = bundler EOA, msg.sender = smart wallet contract.
// The require always fails for smart wallet users.
payable(msg.sender).transfer(amount);
}
}Impact: As EIP-4337 adoption increases, an increasing fraction of users are permanently locked out of the contract. Their funds are trapped.
Mitigations
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: tx.origin phishing | Never use tx.origin for authorization | Replace all require(tx.origin == owner) with require(msg.sender == owner) |
| T1: Phishing attack surface | Use msg.sender exclusively for access control | OpenZeppelin Ownable, AccessControl contracts use msg.sender by design |
| T2: Multi-contract interactions | Design access control for composability | Assume msg.sender may be a contract; use role-based access (RBAC) |
| T3: EIP-4337 compatibility | Remove tx.origin == msg.sender EOA checks | Use ERC-1271 isValidSignature() for signature validation from smart wallets |
| T3: Account abstraction | Avoid tx.origin entirely | Future-proof contracts by never reading tx.origin |
| T4: Callback exploitation | Implement reentrancy guards and explicit call-origin checks | Use OpenZeppelin ReentrancyGuard; validate msg.sender not tx.origin |
| P1: Protocol evolution | Design for a world where tx.origin is meaningless | Use ERC-4337’s UserOperation signer validation or ERC-1271 |
| P2: Meta-transactions | Use _msgSender() from ERC-2771, not tx.origin | OpenZeppelin ERC2771Context; but audit for Multicall interaction bugs |
Static Analysis Detection
All major Solidity analysis tools flag tx.origin usage:
- Slither: Detector
tx-originflags any use oftx.originin conditions - Mythril: Detects
tx.origin-based authorization patterns - Semgrep: Rule
solidity.security.tx-origin-usagecatchestx.origininrequire/ifstatements - Solhint: Rule
avoid-tx-originwarns on anytx.originreference
The Only Safe Uses of tx.origin
tx.origin has very few legitimate uses:
- Preventing contracts from calling a function (EOA-only gate):
require(tx.origin == msg.sender)— but this breaks EIP-4337 compatibility and is increasingly considered an anti-pattern - Gas refund attribution: Some protocols read
tx.originto refund gas to the transaction submitter, which is valid because the submitter is the one who paid gas - Analytics/logging: Emitting
tx.originin events for off-chain analysis (not for on-chain logic)
Severity Summary
| Threat ID | Category | Severity | Likelihood | Real-World Precedent |
|---|---|---|---|---|
| T1 | Smart Contract | Critical | High | SWC-115; ubiquitous in audit findings |
| T2 | Smart Contract | High | High | DeFi composability makes this pervasive |
| T3 | Smart Contract | High | Medium (growing) | EIP-4337 adoption increasing |
| T4 | Smart Contract | Medium | Medium | ERC-777 hooks, flash loan callbacks |
| P1 | Protocol | High | High | EIP-4337, EIP-3074, EIP-7702 roadmap |
| P2 | Protocol | Medium | Medium | ERC-2771/Multicall exploit (Dec 2023) |
| P3 | Protocol | Low | Low | L2 sequencer edge cases |
Related Opcodes
| Opcode | Relationship |
|---|---|
| CALLER (0x33) | Returns msg.sender — the immediate caller. This is the correct alternative to ORIGIN for access control. Changes at every call boundary. |
| ADDRESS (0x30) | Returns the current contract’s address. Combined with CALLER, enables proper access control without ORIGIN. |
| DELEGATECALL (0xF4) | Executes code in the context of the calling contract. tx.origin is preserved through delegatecall, meaning delegated code sees the same ORIGIN — relevant for proxy patterns. |
| STATICCALL (0xFA) | Read-only external call. tx.origin is still readable in static calls, leaking the originator’s address to untrusted view functions. |
| CALL (0xF1) | Standard external call. Each CALL changes msg.sender but preserves tx.origin, which is the root cause of the ORIGIN phishing vulnerability. |