Skip to main content
Voltaire provides powerful bytecode analysis and EVM execution capabilities not found in other TypeScript Ethereum libraries.

Bytecode Analysis

Parse and analyze EVM bytecode without executing it. Extract ABIs from unverified contracts, detect patterns, and debug at the opcode level.

Extract ABI from Bytecode

Recover function selectors and events from any deployed contract—even without source code:
import { Bytecode } from '@tevm/voltaire'

// Fetch bytecode from any contract (verified or not)
const bytecode = Bytecode('0x608060405234801561001057600080fd5b50...')

// Extract ABI from bytecode patterns
const abi = Bytecode.toAbi(bytecode)

console.log(abi)
// [
//   { type: "function", selector: "0xa9059cbb", stateMutability: "nonpayable", payable: false },
//   { type: "function", selector: "0x70a08231", stateMutability: "view", payable: false },
//   { type: "event", hash: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" }
// ]

Parse Instructions

Disassemble bytecode into individual opcodes:
const instructions = Bytecode.parseInstructions(bytecode)

for (const inst of instructions) {
  console.log(`${inst.pc}: ${inst.opcode} ${inst.pushData ?? ''}`)
}
// 0: PUSH1 0x80
// 2: PUSH1 0x40
// 4: MSTORE
// 5: CALLVALUE
// 6: DUP1
// ...

Pretty Print

Human-readable bytecode disassembly:
console.log(Bytecode.prettyPrint(bytecode))
// 0x0000: PUSH1 0x80
// 0x0002: PUSH1 0x40
// 0x0004: MSTORE
// 0x0005: CALLVALUE
// ...

Analyze Jump Destinations

Find all valid jump targets for control flow analysis:
const jumpDests = Bytecode.analyzeJumpDestinations(bytecode)
console.log('Valid JUMPDEST locations:', [...jumpDests])
// [16, 42, 128, 256, ...]

// Check specific location
const isValid = Bytecode.isValidJumpDest(bytecode, 0x10)

Gas Analysis

Estimate gas costs per instruction:
const gasAnalysis = Bytecode.analyzeGas(bytecode)
console.log('Total static gas:', gasAnalysis.totalGas)
console.log('Instructions:', gasAnalysis.instructions.length)

Stack Analysis

Analyze stack depth and effects:
const stackAnalysis = Bytecode.analyzeStack(bytecode)
console.log('Max stack depth:', stackAnalysis.maxDepth)

Strip Metadata

Remove Solidity compiler metadata for cleaner analysis:
if (Bytecode.hasMetadata(bytecode)) {
  const stripped = Bytecode.stripMetadata(bytecode)
  console.log('Removed', bytecode.length - stripped.length, 'metadata bytes')
}

Hash Bytecode

Compute keccak256 hash (useful for CREATE2 address prediction):
const codeHash = Bytecode.hash(bytecode)
console.log('Code hash:', codeHash)

EVM Execution

Execute bytecode directly in TypeScript. Build execution frames, manipulate the stack, and run opcodes.

Create Execution Frames

import { Frame } from '@tevm/voltaire/evm'

// Create a frame with bytecode that pushes 1 + 2
const frame = Frame({
  bytecode: new Uint8Array([
    0x60, 0x01,  // PUSH1 1
    0x60, 0x02,  // PUSH1 2
    0x01,        // ADD
  ]),
  gas: 100000n,
})

console.log(frame.pc)           // 0
console.log(frame.gasRemaining) // 100000n
console.log(frame.stack)        // []

Execute Opcodes

Run individual opcodes with full gas accounting:
import { Frame, Arithmetic } from '@tevm/voltaire/evm'

const frame = Frame({
  bytecode: new Uint8Array([0x60, 0x01, 0x60, 0x02, 0x01]),
  gas: 100000n,
})

// Push values onto stack
Frame.pushStack(frame, 1n)
Frame.pushStack(frame, 2n)

// Execute ADD
const error = Arithmetic.add(frame)

console.log(frame.stack) // [3n]
console.log(error)       // null (success)

Stack Operations

Direct stack manipulation:
// Push values
Frame.pushStack(frame, 42n)
Frame.pushStack(frame, 100n)

// Pop and peek
const top = Frame.popStack(frame)     // 100n
const next = Frame.peekStack(frame)   // 42n (doesn't remove)

Memory Operations

Read and write EVM memory:
// Write 32 bytes to memory offset 0
const data = new Uint8Array(32).fill(0xff)
Frame.writeMemory(frame, 0, data)

// Read 32 bytes from offset 0
const result = Frame.readMemory(frame, 0, 32)

// Calculate memory expansion cost
const cost = Frame.memoryExpansionCost(frame, 64)

Gas Accounting

Track gas consumption:
const frame = Frame({ gas: 1000n })

// Consume gas (returns error if insufficient)
const error = Frame.consumeGas(frame, 21n)

console.log(frame.gasRemaining) // 979n
console.log(error)              // null (success)

// Attempt to consume more than available
const outOfGas = Frame.consumeGas(frame, 2000n)
console.log(outOfGas) // { type: "OutOfGas" }

Full Opcode Categories

All EVM opcodes are implemented:
import {
  Arithmetic,  // ADD, MUL, SUB, DIV, MOD, EXP, etc.
  Bitwise,     // AND, OR, XOR, NOT, SHL, SHR, SAR
  Comparison,  // LT, GT, SLT, SGT, EQ, ISZERO
  Memory,      // MLOAD, MSTORE, MSTORE8, MCOPY
  Stack,       // PUSH1-32, POP, DUP1-16, SWAP1-16
  Control,     // JUMP, JUMPI, JUMPDEST, STOP, RETURN
  System,      // CALL, CREATE, DELEGATECALL, SELFDESTRUCT
  Log,         // LOG0-LOG4
  Block,       // BLOCKHASH, COINBASE, TIMESTAMP, etc.
  Context,     // ADDRESS, CALLER, CALLVALUE, etc.
  Storage,     // SLOAD, SSTORE, TLOAD, TSTORE
  Keccak,      // SHA3
} from '@tevm/voltaire/evm'

Bytecode Documentation

Complete bytecode analysis API reference