Skip to main content
Type-first EVM architecture. Every execution primitive is a strongly-typed branded Uint8Array or interface.
These are low-level primitives for EVM operations, not a full EVM implementation. For complete EVM implementations, see evmts/guillotine-mini (lightweight) or evmts/guillotine (full-featured).

Core Types

Opcode

Branded number type for EVM instructions (0x00-0xFF).
import { Opcode } from "@tevm/voltaire";

// Opcode type is a branded number
type OpcodeType = number & { readonly __tag: "Opcode" };

// Create opcodes
const add: Opcode = Opcode.ADD;        // 0x01
const push1: Opcode = Opcode.PUSH1;    // 0x60
const sload: Opcode = Opcode.SLOAD;    // 0x54

// Type safety prevents passing arbitrary numbers
// ❌ const invalid: Opcode = 0x01; // Type error!
See Opcode documentation for complete reference.

Instruction

Opcode with program counter offset and optional immediate data.
import { Instruction, Opcode } from "@tevm/voltaire";

type Instruction = {
  /** Program counter offset */
  offset: number;
  /** The opcode */
  opcode: OpcodeType;
  /** Immediate data for PUSH operations */
  immediate?: Uint8Array;
};

// PUSH1 0x42 at offset 0
const push: Instruction = {
  offset: 0,
  opcode: Opcode.PUSH1,
  immediate: new Uint8Array([0x42]),
};

// ADD at offset 2
const add: Instruction = {
  offset: 2,
  opcode: Opcode.ADD,
};
Instructions are returned by bytecode analysis:
import { Bytecode } from "@tevm/voltaire";

const code = Bytecode.from("0x6001600201");
const analysis = code.analyze();

// analysis.instructions: Instruction[]
// [
//   { offset: 0, opcode: 0x60, immediate: Uint8Array([0x01]) },  // PUSH1 1
//   { offset: 2, opcode: 0x60, immediate: Uint8Array([0x02]) },  // PUSH1 2
//   { offset: 4, opcode: 0x01 }                                  // ADD
// ]

Execution Types

Frame

EVM execution frame with stack, memory, and execution state.
import type { BrandedFrame } from "@tevm/voltaire/evm";
import type { Address } from "@tevm/voltaire";

type BrandedFrame = {
  readonly __tag: "Frame";

  // Stack (max 1024 items, 256-bit words)
  stack: bigint[];

  // Memory (sparse map, byte-addressable)
  memory: Map<number, number>;
  memorySize: number; // Word-aligned size

  // Execution state
  pc: number;           // Program counter
  gasRemaining: bigint;
  bytecode: Uint8Array;

  // Call context
  caller: Address;
  address: Address;
  value: bigint;
  calldata: Uint8Array;
  output: Uint8Array;
  returnData: Uint8Array;

  // Flags
  stopped: boolean;
  reverted: boolean;
  isStatic: boolean;

  // Block context (for block opcodes)
  blockNumber?: bigint;
  blockTimestamp?: bigint;
  blockGasLimit?: bigint;
  chainId?: bigint;
  blockBaseFee?: bigint;
  blobBaseFee?: bigint;

  // Logs (LOG0-LOG4 opcodes)
  logs?: Array<{
    address: Address;
    topics: bigint[];
    data: Uint8Array;
  }>;
};
Frame represents the complete execution state at any point in EVM execution.

Host

Interface for external state access (accounts, storage, code) and nested execution.
import type { BrandedHost, CallParams, CallResult, CreateParams, CreateResult } from "@tevm/voltaire/evm";
import type { Address } from "@tevm/voltaire";

type BrandedHost = {
  readonly __tag: "Host";

  // Account balance
  getBalance: (address: Address) => bigint;
  setBalance: (address: Address, balance: bigint) => void;

  // Contract code
  getCode: (address: Address) => Uint8Array;
  setCode: (address: Address, code: Uint8Array) => void;

  // Persistent storage
  getStorage: (address: Address, slot: bigint) => bigint;
  setStorage: (address: Address, slot: bigint, value: bigint) => void;

  // Account nonce
  getNonce: (address: Address) => bigint;
  setNonce: (address: Address, nonce: bigint) => void;

  // Transient storage (EIP-1153, transaction-scoped)
  getTransientStorage: (address: Address, slot: bigint) => bigint;
  setTransientStorage: (address: Address, slot: bigint, value: bigint) => void;

  // Nested execution (optional - for CALL/CREATE opcodes)
  call?: (params: CallParams) => CallResult;
  create?: (params: CreateParams) => CreateResult;
};
The call and create methods are optional. When not provided, system opcodes (CALL, CREATE, etc.) return a NotImplemented error. For full EVM execution with nested calls, use:
  • guillotine: Production EVM with async state, tracing, full EIP support
  • guillotine-mini: Lightweight synchronous EVM for testing
Host provides pluggable state backend - implement for custom chains or test environments.

InstructionHandler

Function signature for opcode implementations.
import type { InstructionHandler, BrandedFrame, BrandedHost, EvmError } from "@tevm/voltaire/evm";

type InstructionHandler = (
  frame: BrandedFrame,
  host: BrandedHost,
) => EvmError | { type: "Success" };
Example handler implementation:
// ADD opcode (0x01): Pop two values, push sum
const addHandler: InstructionHandler = (frame, host) => {
  // Check stack depth
  if (frame.stack.length < 2) {
    return { type: "StackUnderflow" };
  }

  // Check gas
  if (frame.gasRemaining < 3n) {
    return { type: "OutOfGas" };
  }

  // Execute
  const b = frame.stack.pop()!;
  const a = frame.stack.pop()!;
  const result = (a + b) % 2n**256n; // Mod 2^256 for overflow
  frame.stack.push(result);
  frame.gasRemaining -= 3n;
  frame.pc += 1;

  return { type: "Success" };
};
All 166 EVM opcodes follow this pattern. See Instructions.

Call Types

CallParams

Parameters for cross-contract calls (CALL, STATICCALL, DELEGATECALL).
import type { CallParams, CallType, Address } from "@tevm/voltaire";

type CallType = "CALL" | "STATICCALL" | "DELEGATECALL" | "CALLCODE";

type CallParams = {
  callType: CallType;
  target: Address;
  value: bigint;
  gasLimit: bigint;
  input: Uint8Array;
  caller: Address;
  isStatic: boolean;
  depth: number;
};
Example usage:
// STATICCALL to view function
const params: CallParams = {
  callType: "STATICCALL",
  target: contractAddress,
  value: 0n,
  gasLimit: 100000n,
  input: encodedCalldata,
  caller: myAddress,
  isStatic: true,
  depth: 1,
};

CallResult

Result of call operation.
import type { CallResult, Address } from "@tevm/voltaire";

type CallResult = {
  success: boolean;       // False if reverted
  gasUsed: bigint;
  output: Uint8Array;     // Return data or revert reason
  logs: Array<{
    address: Address;
    topics: bigint[];
    data: Uint8Array;
  }>;
  gasRefund: bigint;
};
Example:
// Successful call
const result: CallResult = {
  success: true,
  gasUsed: 21000n,
  output: returnData,
  logs: [],
  gasRefund: 0n,
};

// Reverted call
const revertResult: CallResult = {
  success: false,
  gasUsed: 50000n,
  output: revertReason, // Revert message
  logs: [],
  gasRefund: 0n,
};

Creation Types

CreateParams

Parameters for contract deployment (CREATE, CREATE2).
import type { CreateParams, Address } from "@tevm/voltaire";

type CreateParams = {
  caller: Address;
  value: bigint;
  initCode: Uint8Array;
  gasLimit: bigint;
  salt?: bigint;        // For CREATE2
  depth: number;
};
Example:
// CREATE2 deployment with deterministic address
const params: CreateParams = {
  caller: deployerAddress,
  value: 0n,
  initCode: contractBytecode,
  gasLimit: 1000000n,
  salt: 0x1234n,  // Determines address
  depth: 1,
};

CreateResult

Result of contract creation.
import type { CreateResult, Address } from "@tevm/voltaire";

type CreateResult = {
  success: boolean;
  address: Address | null;  // Null if failed
  gasUsed: bigint;
  output: Uint8Array;       // Runtime code or revert reason
  gasRefund: bigint;
};
Example:
// Successful deployment
const result: CreateResult = {
  success: true,
  address: newContractAddress,
  gasUsed: 200000n,
  output: runtimeCode,
  gasRefund: 0n,
};

Error Types

EvmError

Execution errors that halt the EVM.
type EvmError =
  | { type: "StackOverflow" }
  | { type: "StackUnderflow" }
  | { type: "OutOfGas" }
  | { type: "OutOfBounds" }
  | { type: "InvalidJump" }
  | { type: "InvalidOpcode" }
  | { type: "RevertExecuted" }
  | { type: "CallDepthExceeded" }
  | { type: "WriteProtection" }
  | { type: "InsufficientBalance" }
  | { type: "NotImplemented"; message: string };
The NotImplemented error is returned by system opcodes (CALL, CREATE, etc.) when the host doesn’t provide call or create methods. This is intentional - these low-level primitives don’t include an execution engine. Use guillotine or guillotine-mini for full EVM execution.
Error handling:
const result = instructionHandler(frame, host);

if (result.type !== "Success") {
  // Handle error
  switch (result.type) {
    case "StackUnderflow":
      console.error("Stack underflow - not enough items");
      break;
    case "OutOfGas":
      console.error("Insufficient gas");
      break;
    case "RevertExecuted":
      console.error("Execution reverted");
      break;
    case "NotImplemented":
      console.error("Feature not implemented:", result.message);
      break;
    // ... handle other errors
  }
}

Opcode Metadata

Info

Opcode metadata (gas cost, stack effect).
import { Opcode } from "@tevm/voltaire";

type Info = {
  gasCost: number;      // Base gas cost (may be dynamic)
  stackInputs: number;  // Items consumed from stack
  stackOutputs: number; // Items pushed to stack
  name: string;         // Opcode name
};

// Get opcode info
const info = Opcode.info(Opcode.ADD);
// {
//   gasCost: 3,
//   stackInputs: 2,
//   stackOutputs: 1,
//   name: "ADD"
// }

Type Safety Benefits

Prevents Type Confusion

import { Opcode, Address, Bytecode } from "@tevm/voltaire";

// ❌ Cannot pass wrong type
const opcode: Opcode = 0x01;              // Type error!
const addr: Address = "0x123...";         // Type error!

// ✅ Must use constructors
const opcode = Opcode.ADD;                // Correct
const addr = Address("0x123...");         // Correct

Compile-Time Validation

// ❌ Type mismatch caught at compile time
function executeOpcode(op: Opcode) { /*...*/ }
executeOpcode(0x60);  // Type error!

// ✅ Type-safe
executeOpcode(Opcode.PUSH1);  // Correct

IDE Autocomplete

TypeScript provides full IntelliSense:
import { Opcode } from "@tevm/voltaire";

const op = Opcode.
//              ^ Shows all opcode names with documentation

Zero Runtime Overhead

Branded types are compile-time only:
// TypeScript
const op: OpcodeType = Opcode.ADD;

// Compiles to JavaScript
const op = 0x01;  // No wrapper, just the number

Architecture

Execution Flow

import type { BrandedFrame, BrandedHost, InstructionHandler } from "@tevm/voltaire/evm";

// 1. Initialize frame
const frame: BrandedFrame = {
  stack: [],
  memory: new Map(),
  memorySize: 0,
  pc: 0,
  gasRemaining: 1000000n,
  bytecode: code,
  // ... other fields
};

// 2. Initialize host
const host: BrandedHost = {
  getBalance: (addr) => balances.get(addr) || 0n,
  setBalance: (addr, bal) => balances.set(addr, bal),
  // ... other methods
};

// 3. Execute instructions
while (!frame.stopped && !frame.reverted) {
  const opcode = frame.bytecode[frame.pc];
  const handler = getHandler(opcode);
  const result = handler(frame, host);

  if (result.type !== "Success") {
    // Handle error
    break;
  }
}

Pluggable Backend

Host interface allows custom state implementations:
// In-memory state for testing
class MemoryHost implements BrandedHost {
  private balances = new Map<Address, bigint>();
  private storage = new Map<string, bigint>();

  getBalance(addr: Address) { return this.balances.get(addr) || 0n; }
  setBalance(addr: Address, bal: bigint) { this.balances.set(addr, bal); }
  // ... implement other methods
}

// Database-backed state for production
class DatabaseHost implements BrandedHost {
  async getBalance(addr: Address) { return await db.getBalance(addr); }
  // ... implement with DB queries
}

References