Skip to main content
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

Stack instructions (0x50-0x9f) provide fundamental operations for manipulating the EVM’s 256-bit word stack. These 86 opcodes form the core of EVM computation, enabling value manipulation, duplication, and reordering necessary for all contract execution.

Stack Architecture

The EVM stack has strict constraints:
  • Maximum depth: 1024 items
  • Word size: 256 bits (32 bytes) per item
  • Access limit: Only top 16 items accessible via DUP/SWAP
  • Growth: Downward (item 0 is deepest, item n-1 is top)
  • Errors: StackOverflow (>1024), StackUnderflow (<required depth)

Instruction Categories

Stack Removal (0x50)

POP (0x50) - Remove top stack item
  • Gas: 2
  • Stack: [value] => []
  • Use: Discard unneeded values

Push Operations (0x5f-0x7f)

Push immediate values from bytecode onto stack:
OpcodeNameBytesGasSince
0x5fPUSH002Shanghai (EIP-3855)
0x60PUSH113Frontier
0x61PUSH223Frontier
0x62PUSH333Frontier
0x63PUSH443Frontier
0x64PUSH553Frontier
0x65PUSH663Frontier
0x66PUSH773Frontier
0x67PUSH883Frontier
0x68PUSH993Frontier
0x69PUSH10103Frontier
0x6aPUSH11113Frontier
0x6bPUSH12123Frontier
0x6cPUSH13133Frontier
0x6dPUSH14143Frontier
0x6ePUSH15153Frontier
0x6fPUSH16163Frontier
0x70PUSH17173Frontier
0x71PUSH18183Frontier
0x72PUSH19193Frontier
0x73PUSH20203Frontier
0x74PUSH21213Frontier
0x75PUSH22223Frontier
0x76PUSH23233Frontier
0x77PUSH24243Frontier
0x78PUSH25253Frontier
0x79PUSH26263Frontier
0x7aPUSH27273Frontier
0x7bPUSH28283Frontier
0x7cPUSH29293Frontier
0x7dPUSH30303Frontier
0x7ePUSH31313Frontier
0x7fPUSH32323Frontier
Characteristics:
  • PUSH0: Pushes constant 0 (no bytecode reading)
  • PUSH1-32: Read N bytes immediately following opcode
  • Big-endian byte order
  • Zero-padded to 256 bits
  • PC advances by 1 + N bytes

Duplicate Operations (0x80-0x8f)

Duplicate stack items at specific depths:
OpcodeNameDuplicatesGasStack Effect
0x80DUP11st (top)3[a] => [a, a]
0x81DUP22nd3[a, b] => [a, b, b]
0x82DUP33rd3[a, b, c] => [a, b, c, c]
0x83DUP44th3[a, b, c, d] => [a, b, c, d, d]
0x84DUP55th3[a, b, c, d, e] => [a, b, c, d, e, e]
0x85DUP66th3Stack depth ≥ 6
0x86DUP77th3Stack depth ≥ 7
0x87DUP88th3Stack depth ≥ 8
0x88DUP99th3Stack depth ≥ 9
0x89DUP1010th3Stack depth ≥ 10
0x8aDUP1111th3Stack depth ≥ 11
0x8bDUP1212th3Stack depth ≥ 12
0x8cDUP1313th3Stack depth ≥ 13
0x8dDUP1414th3Stack depth ≥ 14
0x8eDUP1515th3Stack depth ≥ 15
0x8fDUP1616th3Stack depth ≥ 16
Characteristics:
  • DUP1: Most common, duplicates top
  • DUPn: Requires stack depth ≥ n
  • Result pushed to top
  • Original value unchanged
  • StackUnderflow if depth insufficient

Swap Operations (0x90-0x9f)

Exchange top stack item with items at specific depths:
OpcodeNameSwaps WithGasStack Effect
0x90SWAP12nd3[a, b] => [b, a]
0x91SWAP23rd3[a, b, c] => [c, b, a]
0x92SWAP34th3[a, b, c, d] => [d, b, c, a]
0x93SWAP45th3[a, b, c, d, e] => [e, b, c, d, a]
0x94SWAP56th3Stack depth ≥ 6
0x95SWAP67th3Stack depth ≥ 7
0x96SWAP78th3Stack depth ≥ 8
0x97SWAP89th3Stack depth ≥ 9
0x98SWAP910th3Stack depth ≥ 10
0x99SWAP1011th3Stack depth ≥ 11
0x9aSWAP1112th3Stack depth ≥ 12
0x9bSWAP1213th3Stack depth ≥ 13
0x9cSWAP1314th3Stack depth ≥ 14
0x9dSWAP1415th3Stack depth ≥ 15
0x9eSWAP1516th3Stack depth ≥ 16
0x9fSWAP1617th3Stack depth ≥ 17
Characteristics:
  • SWAP1: Most common, exchanges top two
  • SWAPn: Requires stack depth ≥ n+1
  • Only top and nth item change positions
  • Middle items unchanged
  • StackUnderflow if depth insufficient

Gas Costs

All stack operations are extremely cheap:
OperationGasConstant
POP2GasQuickStep
PUSH02GasQuickStep
PUSH1-323GasFastestStep
DUP1-163GasFastestStep
SWAP1-163GasFastestStep
Why so cheap?
  • Pure stack operations (no memory/storage access)
  • No external state reads
  • Constant-time execution
  • Critical for EVM performance

Common Patterns

Function Selector Matching

// Compiler generates PUSH4 for function selectors
function transfer(address to, uint256 amount) public {
    // PUSH4 0xa9059cbb  (transfer selector)
    // CALLDATALOAD
    // EQ
    // JUMPI
}

Address Literals

// PUSH20 for address constants
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

Stack Reordering

assembly {
    // Stack: [a, b, c]
    swap1    // [a, c, b]
    dup2     // [a, c, b, c]
    swap2    // [c, c, b, a]
}

Efficient Constants

assembly {
    // Before Shanghai: PUSH1 0x00 (3 gas)
    // After Shanghai: PUSH0 (2 gas)
    push0    // Most efficient way to get 0
}

Stack Depth Management

Safe Patterns

function deepStack() public pure {
    uint256 a = 1;  // Stack: 1
    uint256 b = 2;  // Stack: 2
    uint256 c = 3;  // Stack: 3
    // ... up to ~1000 locals possible

    // Compiler manages stack depth automatically
    return a + b + c;
}

Unsafe Patterns

// Stack too deep error
function tooManyLocals() public pure returns (uint256) {
    uint256 v1 = 1;   // Stack slot 1
    uint256 v2 = 2;   // Stack slot 2
    // ...
    uint256 v17 = 17; // ERROR: Stack too deep!
    // Can only access top 16 items with DUP/SWAP
    return v1 + v17;
}

Workarounds

// Use memory for deep variables
function workaround() public pure returns (uint256) {
    uint256 v1 = 1;
    uint256 v2 = 2;
    // ... v14, v15, v16

    // Move to memory before hitting limit
    uint256[10] memory extra;
    extra[0] = 17;
    extra[1] = 18;

    return v1 + extra[0];
}

Security Considerations

Stack Underflow

assembly {
    // DANGEROUS: No validation
    pop  // Reverts if stack empty
}
Protection:
assembly {
    // Check stack depth first
    if iszero(lt(mload(0x40), 32)) {
        pop
    }
}

Stack Overflow

function recursive(uint256 n) public pure returns (uint256) {
    if (n == 0) return 1;
    // Each recursion adds stack frames
    // Can hit 1024 limit
    return n * recursive(n - 1);
}
Protection:
function iterative(uint256 n) public pure returns (uint256) {
    uint256 result = 1;
    for (uint256 i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}

PUSH0 Availability

// Pre-Shanghai hardfork
assembly {
    push0  // InvalidOpcode error!
}
Protection:
// Check hardfork or use PUSH1 0
assembly {
    push1 0x00  // Works on all hardforks
}

Optimization Techniques

Minimize Stack Operations

// Inefficient: Extra DUP/SWAP
function inefficient(uint256 a, uint256 b) pure returns (uint256) {
    assembly {
        dup1
        dup3
        add
        swap1
        pop
    }
}

// Efficient: Direct operations
function efficient(uint256 a, uint256 b) pure returns (uint256) {
    assembly {
        add(a, b)
    }
}

Use PUSH0 (Shanghai+)

// Before Shanghai: PUSH1 0x00 (3 gas)
assembly { push1 0x00 }

// After Shanghai: PUSH0 (2 gas)
assembly { push0 }

Reuse Stack Values

// Bad: Push same value twice
assembly {
    push1 0x20
    mstore
    push1 0x20  // Wasteful
    add
}

// Good: DUP existing value
assembly {
    push1 0x20
    dup1
    mstore
    add
}

Implementation Reference

Stack instruction handlers implemented in:
  • TypeScript: /src/evm/stack/handlers/
  • Zig: /src/evm/stack/handlers_stack.zig
Each instruction follows standard handler pattern:
  1. Consume gas
  2. Validate stack constraints
  3. Perform operation (pop/push/duplicate/swap)
  4. Increment program counter
  5. Return error or null

All Stack Instructions

Complete opcode reference:
0x50: POP
0x5f: PUSH0
0x60-0x7f: PUSH1-PUSH32 (33 opcodes)
0x80-0x8f: DUP1-DUP16 (16 opcodes)
0x90-0x9f: SWAP1-SWAP16 (16 opcodes)

Total: 66 opcodes

References