Skip to main content

Overview

Opcode: 0x5f Introduced: Shanghai (EIP-3855) PUSH0 pushes the constant value 0 onto the stack. Introduced in Shanghai hardfork as a gas optimization - previously required PUSH1 0x00 (3 gas).

Specification

Stack Input:
[]
Stack Output:
0 (uint256)
Gas Cost: 2 (GasQuickStep) Operation:
stack.push(0)

Behavior

PUSH0 pushes constant zero without reading from bytecode. Unlike PUSH1-32, no immediate bytes follow the opcode. Key characteristics:
  • No bytecode reading (pure constant)
  • Cheaper than PUSH1 0x00 (2 vs 3 gas)
  • Only available Shanghai hardfork onwards
  • InvalidOpcode error on earlier hardforks
  • Most efficient way to push zero

Examples

Basic Usage

import { handler_0x5f_PUSH0 } from '@tevm/voltaire/evm/stack/handlers';
import { createFrame } from '@tevm/voltaire/evm/Frame';

// Push zero
const frame = createFrame({
  stack: [],
  gasRemaining: 1000n
});

const err = handler_0x5f_PUSH0(frame);

console.log(frame.stack); // [0n]
console.log(frame.gasRemaining); // 998n (2 gas consumed)

Solidity Compilation

contract Example {
    function getZero() public pure returns (uint256) {
        return 0;
    }

    // Pre-Shanghai:
    // PUSH1 0x00  (3 gas)

    // Shanghai+:
    // PUSH0       (2 gas)
}

Assembly Usage

assembly {
    // Most efficient zero initialization
    push0                // 2 gas

    // Old way (still works)
    push1 0x00           // 3 gas

    // Use for memory initialization
    push0
    push0
    mstore               // Store 0 at memory offset 0
}

Gas Cost

Cost: 2 gas (GasQuickStep) Comparison:
OpcodeGasBytesNote
PUSH021Shanghai+ only
PUSH1 0x0032All hardforks
Savings:
  • 1 gas per zero value
  • 1 byte per zero value in bytecode
  • Significant for contracts with many zero constants

Common Usage

Memory Initialization

assembly {
    // Clear memory slots
    push0
    push0
    mstore  // mem[0] = 0

    push0
    push1 0x20
    mstore  // mem[32] = 0
}

Default Return Values

function maybeValue(bool condition) public pure returns (uint256) {
    if (!condition) {
        assembly {
            push0
            push0
            mstore
            return(0, 32)  // Return 0
        }
    }
    return 42;
}

Array Length Initialization

assembly {
    // Create empty array in memory
    let ptr := mload(0x40)  // Free memory pointer
    push0
    mstore(ptr, 0)          // Length = 0
    mstore(0x40, add(ptr, 0x20))  // Update free pointer
}

Comparison Operations

assembly {
    // Check if value is zero
    let x := calldataload(0)
    push0
    eq  // x == 0
}

Hardfork Compatibility

Shanghai Check

// Zig implementation checks hardfork
if (push_size == 0) {
    const evm = frame.getEvm();
    if (evm.hardfork.isBefore(.SHANGHAI)) {
        return error.InvalidOpcode;
    }
    try frame.consumeGas(GasConstants.GasQuickStep);
}

Safe Fallback

// Pre-Shanghai compatible code
assembly {
    // Use PUSH1 for compatibility
    push1 0x00
}

// Shanghai+ optimized code
assembly {
    // Use PUSH0 for efficiency
    push0
}

EIP-3855 Rationale

Problem:
  • PUSH1 0x00 wastes gas (3 instead of 2)
  • PUSH1 0x00 wastes bytecode space (2 bytes instead of 1)
  • Zero is extremely common in EVM code
Solution:
  • Dedicated opcode for pushing zero
  • Same gas as other constant operations (ADDRESS, CALLER, etc.)
  • Saves ~0.1% gas on typical contracts
Impact:
// Example contract
contract Token {
    mapping(address => uint256) balances;

    function transfer(address to, uint256 amount) public {
        // Many zero comparisons and initializations
        // Each PUSH0 saves 1 gas compared to PUSH1 0x00
    }
}

// Aggregate savings: ~100-500 gas per transaction

Security

Hardfork Detection

// UNSAFE: Assumes Shanghai
assembly {
    push0  // May revert pre-Shanghai!
}
// SAFE: Check hardfork or use PUSH1
function safeZero() public pure returns (uint256) {
    assembly {
        // Use PUSH1 0x00 for compatibility
        push1 0x00
    }
}

Gas Calculation

// Account for hardfork differences
function estimateGas(bool isShanghai) public pure returns (uint256) {
    if (isShanghai) {
        return 2;  // PUSH0
    } else {
        return 3;  // PUSH1 0x00
    }
}

Optimization

Replace PUSH1 0x00

// BEFORE (Pre-Shanghai or conservative)
assembly {
    push1 0x00
    push1 0x00
    push1 0x00
    // Total: 9 gas, 6 bytes
}

// AFTER (Shanghai+)
assembly {
    push0
    push0
    push0
    // Total: 6 gas, 3 bytes
}

Memory Clearing

// Efficient memory initialization
assembly {
    // Clear 5 slots
    push0
    dup1
    dup1
    dup1
    dup1
    // Cost: 2 (PUSH0) + 4*3 (DUP) = 14 gas
    // vs PUSH1 0x00 version: 3 + 4*3 = 15 gas
}

Implementation

import { consumeGas } from "../../Frame/consumeGas.js";
import { pushStack } from "../../Frame/pushStack.js";
import { QuickStep } from "../../../primitives/GasConstants/BrandedGasConstants/constants.js";

/**
 * PUSH0 opcode (0x5f) - Push 0 onto stack
 * EIP-3855: Introduced in Shanghai hardfork
 *
 * Stack: [] => [0]
 * Gas: 2 (GasQuickStep)
 */
export function handler_0x5f_PUSH0(frame: FrameType): EvmError | null {
  // Note: Add hardfork validation when Hardfork module is available
  // if (evm.hardfork.isBefore(.SHANGHAI)) {
  //   return { type: "InvalidOpcode" };
  // }

  const gasErr = consumeGas(frame, QuickStep);
  if (gasErr) return gasErr;

  const pushErr = pushStack(frame, 0n);
  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),
  gasRemaining: 10n
});

const err = handler_0x5f_PUSH0(frame);
console.log(err); // { type: "StackOverflow" }

Out of Gas

// Insufficient gas
const frame = createFrame({
  stack: [],
  gasRemaining: 1n
});

const err = handler_0x5f_PUSH0(frame);
console.log(err); // { type: "OutOfGas" }

Pre-Shanghai Error

// Would error on pre-Shanghai hardfork
// (hardfork check not yet implemented in TS)
const frame = createFrame({
  hardfork: 'london',
  stack: []
});

// Note: Once hardfork check is wired, this should return InvalidOpcode
const err = handler_0x5f_PUSH0(frame);

References