Skip to main content

Try it Live

Run Opcode examples in the interactive playground

Opcode.disassemble()

Disassemble bytecode into array of formatted assembly strings.
import { disassemble } from 'tevm/Opcode'

const bytecode = new Uint8Array([
  0x60, 0x01,  // PUSH1 1
  0x60, 0x02,  // PUSH1 2
  0x01,        // ADD
])

const lines = disassemble(bytecode)
// ["0000: PUSH1 0x01", "0002: PUSH1 0x02", "0004: ADD"]

Parameters

  • bytecode: Uint8Array - Raw bytecode to disassemble

Returns

string[] - Array of formatted instruction strings Each line format: "<offset>: <mnemonic> [immediate]"

Output Format

  • Offset: 4-digit hex with leading zeros (e.g., 0000, 002A)
  • Mnemonic: Opcode name (e.g., PUSH1, ADD, SSTORE)
  • Immediate: Hex value for PUSH operations (e.g., 0x80, 0x0102)

Use Cases

import * as Opcode from 'tevm/Opcode'

function printDisassembly(bytecode: Uint8Array): void {
  const lines = Opcode.disassemble(bytecode)

  console.log('=== Disassembly ===')
  for (const line of lines) {
    console.log(line)
  }
  console.log(`\nTotal instructions: ${lines.length}`)
}

Annotated Disassembly with Gas

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

  for (const inst of instructions) {
    const formatted = Opcode.format(inst)
    const info = Opcode.info(inst.opcode)
    lines.push(`${formatted}  // gas: ${info?.gasCost ?? 'unknown'}`)
  }

  return lines
}

// Example output:
// 0000: PUSH1 0x80  // gas: 3
// 0002: PUSH1 0x40  // gas: 3
// 0004: MSTORE  // gas: 3

Side-by-Side Comparison

function compareDisassembly(
  bytecode1: Uint8Array,
  bytecode2: Uint8Array
): string[] {
  const lines1 = Opcode.disassemble(bytecode1)
  const lines2 = Opcode.disassemble(bytecode2)
  const maxLen = Math.max(lines1.length, lines2.length)
  const output: string[] = []

  output.push('Original'.padEnd(40) + ' | ' + 'Optimized')
  output.push('-'.repeat(80))

  for (let i = 0; i < maxLen; i++) {
    const left = (lines1[i] ?? '').padEnd(40)
    const right = lines2[i] ?? ''
    output.push(`${left} | ${right}`)
  }

  return output
}

Find Pattern Locations

function findPattern(
  bytecode: Uint8Array,
  pattern: string
): number[] {
  const lines = Opcode.disassemble(bytecode)
  const matches: number[] = []

  lines.forEach((line, index) => {
    if (line.includes(pattern)) {
      matches.push(index)
    }
  })

  return matches
}

// Find all SSTORE operations
const sstoreLocations = findPattern(bytecode, 'SSTORE')

Export to File

import * as fs from 'fs'

function exportDisassembly(
  bytecode: Uint8Array,
  filename: string
): void {
  const lines = Opcode.disassemble(bytecode)
  const content = lines.join('\n')
  fs.writeFileSync(filename, content, 'utf-8')
}

exportDisassembly(contractBytecode, 'contract.asm')

Highlight Instructions

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

  for (const inst of instructions) {
    let line = Opcode.format(inst)

    if (Opcode.isJump(inst.opcode)) {
      line += '  <-- JUMP'
    }
    if (jumpDests.has(inst.offset)) {
      line += '  <-- JUMPDEST'
    }

    lines.push(line)
  }

  return lines
}

Performance

  • O(n) time where n is number of instructions
  • String allocation for each instruction line
  • Single pass through parsed instructions
  • Uses parse() internally, so includes parsing overhead