This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
Overview
Opcode: 0x32
Introduced: Frontier (EVM genesis)
ORIGIN pushes the address of the account that originated the transaction (tx.origin) onto the stack. This address never changes throughout the entire call chain, unlike CALLER which changes with each call.
Specification
Stack Input:
Stack Output:
origin (uint160 as uint256)
Gas Cost: 2 (GasQuickStep)
Operation:
stack.push(transaction.origin)
Behavior
ORIGIN provides the address of the externally owned account (EOA) that signed and initiated the transaction. This value remains constant throughout the entire execution, regardless of how many contract calls are made.
Key characteristics:
- Always an EOA (never a contract address)
- Immutable throughout transaction execution
- Same value in all contracts called during transaction
- Cannot be a contract (contracts cannot initiate transactions)
Examples
Basic Usage
import { origin } from '@tevm/voltaire/evm/context';
import { createFrame } from '@tevm/voltaire/evm/Frame';
import * as Address from '@tevm/voltaire/Address';
// Transaction originated by this EOA
const originAddr = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb');
const frame = createFrame({ stack: [] });
const err = origin(frame, originAddr);
console.log(frame.stack[0]); // 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbn
Call Chain Comparison
contract ContractC {
function check() public view returns (address txOrigin, address caller) {
return (tx.origin, msg.sender);
}
}
contract ContractB {
function forward(ContractC c) public returns (address, address) {
return c.check();
}
}
contract ContractA {
function start(ContractB b, ContractC c) public returns (address, address) {
return b.forward(c);
}
}
// Call chain: EOA (0xAAA) → A (0xBBB) → B (0xCCC) → C (0xDDD)
//
// In ContractC.check():
// - tx.origin = 0xAAA (EOA that started transaction)
// - msg.sender = 0xCCC (ContractB called us)
Gas Cost
Cost: 2 gas (GasQuickStep)
ORIGIN shares the lowest gas cost tier with other environment access opcodes:
- ADDRESS (0x30)
- CALLER (0x33)
- CALLVALUE (0x34)
- CALLDATASIZE (0x36)
- CODESIZE (0x38)
- GASPRICE (0x3a)
- RETURNDATASIZE (0x3d)
Common Usage
Logging Transaction Source
contract EventLogger {
event Action(address indexed origin, address indexed sender, string action);
function logAction(string memory action) public {
emit Action(tx.origin, msg.sender, action);
// tx.origin: Who initiated the transaction
// msg.sender: Who called this contract
}
}
Gas Refunds
contract GasRefunder {
function expensiveOperation() public {
// ... expensive operations ...
// Refund gas to transaction originator
uint256 gasUsed = gasleft();
payable(tx.origin).transfer(gasUsed * tx.gasprice);
}
}
Security
CRITICAL: Never Use for Authorization
VULNERABLE pattern:
// EXTREMELY DANGEROUS - DO NOT USE
contract Vulnerable {
address public owner;
function withdraw() public {
require(tx.origin == owner, "Not owner"); // WRONG!
payable(owner).transfer(address(this).balance);
}
}
Attack scenario:
contract Attacker {
Vulnerable victim;
constructor(Vulnerable _victim) {
victim = _victim;
}
function attack() public {
// Trick the owner into calling this
victim.withdraw();
}
}
// Attack flow:
// 1. Owner (tx.origin) calls Attacker.attack()
// 2. Attacker.attack() calls Vulnerable.withdraw()
// 3. tx.origin == owner ✓ (passes the check!)
// 4. Funds are stolen
SAFE pattern - use msg.sender:
contract Safe {
address public owner;
function withdraw() public {
require(msg.sender == owner, "Not owner"); // CORRECT!
payable(owner).transfer(address(this).balance);
}
}
tx.origin vs msg.sender
Critical distinction:
| Property | tx.origin | msg.sender |
|---|
| Value | Original EOA | Immediate caller |
| Changes in call chain | No | Yes |
| Can be contract | Never | Yes |
| Safe for auth | NO | YES |
| Opcode | ORIGIN (0x32) | CALLER (0x33) |
Example:
User EOA (0xAAA) → Contract A (0xBBB) → Contract B (0xCCC)
In Contract B:
- tx.origin = 0xAAA (never changes)
- msg.sender = 0xBBB (Contract A called us)
Phishing Attack Vector
// Attacker creates malicious contract
contract Phishing {
Vulnerable target;
function harmlessLooking() public {
// Owner thinks this is safe to call
// But it triggers the vulnerable withdraw
target.withdraw();
// tx.origin is still the owner, so it works!
}
}
Limited Valid Use Cases
Valid (but rare) use case - gas payment:
contract Relayer {
function executeForUser(address target, bytes memory data) public {
// Meta-transaction: we pay gas, user signs intent
(bool success,) = target.call(data);
require(success);
// Charge the original user (who signed the transaction)
// This is ONE OF THE RARE valid uses of tx.origin
payable(tx.origin).transfer(calculateGasCost());
}
}
Even better - explicit parameter:
contract BetterRelayer {
function executeForUser(
address user,
address target,
bytes memory data
) public {
// Don't use tx.origin at all
(bool success,) = target.call(data);
require(success);
payable(user).transfer(calculateGasCost());
}
}
Implementation
import { consumeGas } from "../Frame/consumeGas.js";
import { pushStack } from "../Frame/pushStack.js";
import { toU256 } from "../../primitives/Address/AddressType/toU256.js";
/**
* ORIGIN opcode (0x32) - Get execution origination address
*
* Stack: [] => [origin]
* Gas: 2 (GasQuickStep)
*/
export function origin(
frame: FrameType,
origin: Address
): EvmError | null {
const gasErr = consumeGas(frame, 2n);
if (gasErr) return gasErr;
const originU256 = toU256(origin);
const pushErr = pushStack(frame, originU256);
if (pushErr) return pushErr;
frame.pc += 1;
return null;
}
Edge Cases
Stack Overflow
// Stack at maximum capacity
const frame = createFrame({ stack: new Array(1024).fill(0n) });
const err = origin(frame, originAddr);
console.log(err); // { type: "StackOverflow" }
Out of Gas
// Insufficient gas
const frame = createFrame({ gasRemaining: 1n });
const err = origin(frame, originAddr);
console.log(err); // { type: "OutOfGas" }
Zero Address Origin
// Theoretically invalid (can't sign transaction)
// but handled by EVM
const frame = createFrame({});
const err = origin(frame, Address('0x0000000000000000000000000000000000000000'));
console.log(frame.stack[0]); // 0n
Best Practices
❌ DON’T: Use for authorization
// WRONG
require(tx.origin == owner);
✅ DO: Use msg.sender for authorization
// CORRECT
require(msg.sender == owner);
❌ DON’T: Trust tx.origin in access control
// WRONG
modifier onlyOwner() {
require(tx.origin == owner);
_;
}
✅ DO: Use for logging/analytics only
// ACCEPTABLE
event UserAction(address indexed origin, string action);
emit UserAction(tx.origin, "purchased");
// Acceptable but prefer explicit parameters
function chargeUser() internal {
payable(tx.origin).transfer(gasCost);
}
References