Skip to main content

Try it Live

Run Bytecode examples in the interactive playground

    Instruction Object

    Each iteration yields an Instruction object matching the OpcodeData union type:
    interface Instruction {
      pc: number              // Program counter (byte offset)
      type: InstructionType   // Instruction category
      opcode: Opcode | SyntheticOpcode
      size: number            // Instruction size in bytes
    
      // Type-specific fields (present based on type)
      value?: bigint          // For PUSH instructions
      gas?: number            // Gas cost (if withGas option)
      stackEffect?: {         // Stack effects (if withStack option)
        input: number
        output: number
      }
    }
    

    Options

    interface ScanOptions {
      /** Include gas cost for each instruction (default: false) */
      withGas?: boolean
    
      /** Include stack effect metadata (default: false) */
      withStack?: boolean
    
      /** Detect and yield fusion patterns (default: true) */
      detectFusions?: boolean
    
      /** Start iteration at specific PC (default: 0) */
      startPc?: number
    
      /** Stop iteration at specific PC (default: end) */
      endPc?: number
    }
    

    Usage Patterns

    Basic Iteration

    // Iterate through all instructions
    for (const inst of code.scan()) {
      console.log(`${inst.pc}: ${inst.opcode}`);
    }
    

    With Gas Costs

    let totalGas = 0;
    
    for (const inst of code.scan({ withGas: true })) {
      totalGas += inst.gas;
      console.log(`${inst.opcode}: ${inst.gas} gas`);
    }
    
    console.log(`Total: ${totalGas} gas`);
    

    With Stack Tracking

    let stackDepth = 0;
    
    for (const inst of code.scan({ withStack: true })) {
      stackDepth -= inst.stackEffect.input;
      stackDepth += inst.stackEffect.output;
      console.log(`${inst.opcode}: stack depth ${stackDepth}`);
    }
    

    Filter by Type

    // Find all JUMP instructions
    const jumps = [];
    for (const inst of code.scan()) {
      if (inst.type === 'jump' || inst.type === 'jumpi') {
        jumps.push(inst);
      }
    }
    

    Detect PUSH Values

    // Extract all PUSH values
    const pushValues = [];
    for (const inst of code.scan()) {
      if (inst.type === 'push') {
        pushValues.push({ pc: inst.pc, value: inst.value });
      }
    }
    

    Fusion Detection

    // Find all fusion opportunities
    for (const inst of code.scan({ detectFusions: true })) {
      if (inst.type.endsWith('_fusion')) {
        console.log(`Fusion at PC ${inst.pc}: ${inst.type}`);
      }
    }
    

    Partial Iteration

    // Scan specific range
    for (const inst of code.scan({ startPc: 100, endPc: 200 })) {
      // Only processes bytes 100-199
    }
    

    Early Exit

    // Stop at first JUMPDEST
    for (const inst of code.scan()) {
      if (inst.type === 'jumpdest') {
        console.log(`First JUMPDEST at PC ${inst.pc}`);
        break;
      }
    }
    

    Collect to Array

    // Materialize all instructions
    const instructions = Array(code.scan());
    
    // Or with options
    const withGas = Array(code.scan({ withGas: true }));
    

    Performance

    scan() uses lazy iteration for memory efficiency:
    • Lazy evaluation - Instructions computed on demand
    • O(1) memory - Only current instruction in memory
    • Streaming - Supports early exit without parsing entire bytecode
    • Efficient PUSH handling - Skips over immediate data bytes
    • Bitmap lookups - O(1) JUMPDEST validation
    For large bytecode (10KB+), use scan() instead of parseInstructions() which materializes the entire array.

    PUSH Data Handling

    scan() correctly handles PUSH immediate data:
    const code = Bytecode("0x60016002605b"); // PUSH1 1, PUSH1 2, PUSH1 0x5b
    
    for (const inst of code.scan()) {
      console.log(inst);
    }
    // Output:
    // { pc: 0, type: 'push', opcode: 'PUSH1', value: 1n, size: 2 }
    // { pc: 2, type: 'push', opcode: 'PUSH1', value: 2n, size: 2 }
    // { pc: 4, type: 'push', opcode: 'PUSH1', value: 91n, size: 2 }
    // Note: 0x5b (JUMPDEST) in last PUSH is treated as data, not instruction
    

    Fusion Patterns

    When detectFusions: true (default), multi-instruction patterns are detected:
    const code = Bytecode("0x60016001015b");
    // PUSH1 1, PUSH1 1, ADD, JUMPDEST
    
    for (const inst of code.scan({ detectFusions: true })) {
      console.log(`${inst.pc}: ${inst.type}`);
    }
    // Output may include:
    // 0: push_add_fusion (if PUSH+ADD pattern detected)
    // Or:
    // 0: push
    // 2: push
    // 4: regular (ADD)
    // 5: jumpdest
    
    See Fusion Detection for all 20+ patterns.

    Error Handling

    try {
      for (const inst of code.scan()) {
        // Process instruction
      }
    } catch (error) {
      if (error instanceof BytecodeError) {
        console.error(`Invalid bytecode at PC ${error.pc}`);
      }
    }
    
    scan() validates bytecode structure during iteration. Malformed PUSH instructions (truncated data) will throw during iteration at the problematic position.

    Comparison with parseInstructions

    Use scan() when:
    • Processing large bytecode (memory efficiency)
    • Need early exit capability
    • Streaming analysis
    • Real-time disassembly
    Use parseInstructions() when:
    • Need random access to instructions
    • Multiple passes over same data
    • Small bytecode (<1KB)
    • Caching full instruction list
    // Memory efficient (lazy)
    for (const inst of code.scan()) { /* process */ }
    
    // Materializes all instructions
    const instructions = code.parseInstructions();
    instructions.forEach(inst => { /* process */ });
    

    See Also