Skip to main content

Try it Live

Run Opcode examples in the interactive playground

Opcode.parse()

Parse bytecode into array of instructions with offsets and immediates.
import { parse } from 'tevm/Opcode'

const bytecode = new Uint8Array([
  0x60, 0x80,           // PUSH1 0x80
  0x60, 0x40,           // PUSH1 0x40
  0x52,                 // MSTORE
])

const instructions = parse(bytecode)
// [
//   { offset: 0, opcode: 0x60, immediate: Uint8Array([0x80]) },
//   { offset: 2, opcode: 0x60, immediate: Uint8Array([0x40]) },
//   { offset: 4, opcode: 0x52 }
// ]

Parameters

  • bytecode: Uint8Array - Raw contract bytecode to parse

Returns

Instruction[] - Array of parsed instructions
type Instruction = {
  offset: number           // Program counter offset
  opcode: BrandedOpcode    // Opcode byte
  immediate?: Uint8Array   // Immediate data for PUSH instructions
}

Behavior

  • Automatic PUSH skipping: Correctly skips PUSH immediate bytes (1-32 bytes depending on PUSH opcode)
  • Incomplete data handling: If bytecode ends mid-PUSH, returns instruction with partial immediate data
  • No validation: Does not validate if opcodes are valid - use isValid() to check
  • Zero-copy: Returns views into original bytecode for immediate data (efficient)

Use Cases

Disassemble Bytecode

import * as Opcode from 'tevm/Opcode'

function disassemble(bytecode: Uint8Array): string[] {
  const instructions = Opcode.parse(bytecode)
  const lines: string[] = []

  for (const inst of instructions) {
    const name = Opcode.name(inst.opcode)
    let line = `${inst.offset.toString(16).padStart(4, '0')}: ${name}`

    if (inst.immediate) {
      const hex = Array(inst.immediate)
        .map(b => b.toString(16).padStart(2, '0'))
        .join('')
      line += ` 0x${hex}`
    }

    lines.push(line)
  }

  return lines
}

Count Instruction Types

function countInstructionTypes(bytecode: Uint8Array) {
  const instructions = Opcode.parse(bytecode)

  const counts = {
    push: 0,
    dup: 0,
    swap: 0,
    jump: 0,
    storage: 0,
    other: 0
  }

  for (const inst of instructions) {
    if (Opcode.isPush(inst.opcode)) counts.push++
    else if (Opcode.isDup(inst.opcode)) counts.dup++
    else if (Opcode.isSwap(inst.opcode)) counts.swap++
    else if (Opcode.isJump(inst.opcode)) counts.jump++
    else if (inst.opcode === Opcode.SLOAD || inst.opcode === Opcode.SSTORE) counts.storage++
    else counts.other++
  }

  return counts
}

Extract All Constants

function extractConstants(bytecode: Uint8Array): bigint[] {
  const instructions = Opcode.parse(bytecode)
  const constants: bigint[] = []

  for (const inst of instructions) {
    if (Opcode.isPush(inst.opcode) && inst.immediate) {
      const hex = '0x' + Array(inst.immediate)
        .map(b => b.toString(16).padStart(2, '0'))
        .join('')
      constants.push(BigInt(hex))
    }
  }

  return constants
}

Track Stack Depth

function trackStackDepth(bytecode: Uint8Array): { max: number, trace: number[] } {
  const instructions = Opcode.parse(bytecode)
  let depth = 0
  let max = 0
  const trace: number[] = []

  for (const inst of instructions) {
    const effect = Opcode.getStackEffect(inst.opcode) ?? 0
    depth += effect

    if (depth < 0) {
      throw new Error(`Stack underflow at offset ${inst.offset}`)
    }
    if (depth > 1024) {
      throw new Error(`Stack overflow at offset ${inst.offset}`)
    }

    max = Math.max(max, depth)
    trace.push(depth)
  }

  return { max, trace }
}

Performance

  • O(n) time complexity where n is bytecode length
  • Zero allocation for non-PUSH opcodes (just offset tracking)
  • Single pass through bytecode
  • No recursion - simple linear scan