Skip to main content
Zig implementation notes: The EVM core is implemented in Zig under src/evm. Public Zig examples here focus on bytecode-level primitives (Opcode, Bytecode) and ABI integration. Full runnable EVM examples will be added once the Zig EVM surface stabilizes.

Overview

The Ethereum Virtual Machine is a stack-based virtual machine that executes smart contract bytecode. Tevm provides type-first EVM primitives - strongly-typed execution types, instruction handlers, and precompiled contracts in both TypeScript and Zig. Every execution primitive is a branded type:
  • Opcode - Branded number (0x00-0xFF)
  • Instruction - Opcode + offset + immediate data
  • BrandedFrame - Execution frame (stack, memory, gas, state)
  • InstructionHandler - Opcode handler function signature
  • CallParams / CallResult - Cross-contract call types
  • CreateParams / CreateResult - Contract deployment types
This section documents 166 instruction handlers across 11 categories plus 21 precompiled contracts, all implemented with:
  • Type safety - Branded types prevent passing wrong values
  • Zero-copy operations - Direct Uint8Array manipulation
  • Tree-shakeable exports - Import only what you need
  • WASM compilation support - High-performance native execution
  • Comprehensive test coverage - Every opcode tested against official vectors
For complete spec-compliant EVM implementations that use these primitives, see evmts/guillotine and evmts/tevm-monorepo.

EVM Components

Types

Strongly-typed execution primitives:
  • Opcode - Branded number type for instructions (0x00-0xFF)
  • Instruction - Opcode with offset and immediate data
  • BrandedFrame - Complete execution state (stack, memory, gas, context)
  • BrandedHost - Pluggable state backend interface
  • InstructionHandler - Function signature for opcode implementations
  • CallParams/CallResult - Cross-contract call types
  • CreateParams/CreateResult - Contract deployment types
  • EvmError - Execution error types
See EVM Types for complete type reference with examples.

Instructions

166 opcode handlers organized by function:
  • Arithmetic (11): ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND
  • Comparison (6): LT, GT, SLT, SGT, EQ, ISZERO
  • Bitwise (8): AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR
  • Keccak (1): SHA3
  • Context (16): ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD, etc.
  • Block (11): BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, etc.
  • Stack (86): POP, PUSH0-32, DUP1-16, SWAP1-16
  • Memory (4): MLOAD, MSTORE, MSTORE8, MCOPY
  • Storage (4): SLOAD, SSTORE, TLOAD, TSTORE
  • Control Flow (7): STOP, JUMP, JUMPI, PC, JUMPDEST, RETURN, REVERT
  • Log (5): LOG0-4
  • System (7): CREATE, CALL, CALLCODE, DELEGATECALL, CREATE2, STATICCALL, SELFDESTRUCT

Precompiles

21 precompiled contracts at addresses 0x01-0x13:
  • Cryptography (1): ECRECOVER
  • Hashing (3): SHA256, RIPEMD160, BLAKE2F
  • Data (1): IDENTITY
  • Mathematics (1): MODEXP
  • zkSNARKs (3): BN254_ADD, BN254_MUL, BN254_PAIRING
  • Blob Data (1): POINT_EVALUATION (EIP-4844)
  • BLS12-381 (9): G1/G2 operations for Ethereum 2.0 consensus

Architecture

Type-First Design

All EVM operations use strongly-typed primitives.
const std = @import("std");
const primitives = @import("primitives");
const Opcode = @import("primitives").Opcode;
const Bytecode = primitives.Bytecode;

pub fn analyzeExample(allocator: std.mem.Allocator) !void {
    const code_hex = "0x6001600201"; // PUSH1 1; PUSH1 2; ADD
    const bytes = try primitives.Hex.fromHex(allocator, code_hex);
    defer allocator.free(bytes);

    var bc = try Bytecode.init(allocator, bytes);
    defer bc.deinit();

    var pc: u32 = 0;
    while (pc < bc.len()) : (pc += 1) {
        const op = bc.getOpcodeEnum(pc) orelse break;
        switch (op) {
            .PUSH1 => {
                const imm = bc.readImmediate(pc, 1) orelse 0;
                std.debug.print("PUSH1 {x}\n", .{imm});
                pc += 1;
            },
            .ADD => std.debug.print("ADD\n", .{}),
            else => {},
        }
    }
}

TypeScript Implementation

import * as EVM from '@tevm/voltaire/evm';

// Execute instruction
const result = EVM.Instructions.Arithmetic.add(stack);

// Execute precompile
const precompileResult = EVM.Precompiles.execute(
  PrecompileAddress.ECRECOVER,
  input,
  gasLimit,
  Hardfork.CANCUN
);

Opcode Categories

Computational Operations

Stack manipulation and arithmetic form the foundation of EVM computation:
  • Stack: 1024 elements max, 256-bit words
  • Arithmetic: Big-integer operations with overflow semantics
  • Bitwise: Bit manipulation for flags, masks, compression

State Access

Instructions for reading/modifying blockchain state:
  • Storage: Persistent (SLOAD/SSTORE) and transient (TLOAD/TSTORE)
  • Memory: Volatile scratch space within transaction
  • Context: Access to msg.sender, msg.value, block data

Control Flow

Program counter manipulation and execution flow:
  • Jumps: JUMP, JUMPI require JUMPDEST validation
  • Termination: STOP, RETURN, REVERT for execution halting
  • PC: Program counter inspection for dynamic code

External Interactions

Cross-contract calls and logging:
  • Calls: CALL, STATICCALL, DELEGATECALL with gas forwarding
  • Creation: CREATE, CREATE2 for contract deployment
  • Logs: LOG0-4 for event emission
  • Destruction: SELFDESTRUCT for contract removal

Gas Metering

All operations have precise gas costs defined in the Yellow Paper:
CategoryCost RangeExamples
Zero0STOP, RETURN (base)
Base2ADDRESS, ORIGIN, CALLER
Very Low3ADD, SUB, NOT, LT, GT
Low5MUL, DIV, MOD, BYTE
Mid8ADDMOD, MULMOD
High10JUMPI, balance check
Ext20-700BALANCE, SLOAD, LOG
Memory3/word + expansionMLOAD, MSTORE, CREATE
Storage100-20000SLOAD, SSTORE (complex)

Dynamic Gas

Some operations have variable costs:
  • Memory expansion: Quadratic growth prevents DoS
  • Storage changes: SSTORE pricing based on cold/warm, zero/nonzero transitions
  • Call gas: 63/64 rule for subcall forwarding
  • Precompiles: Dynamic based on input size (MODEXP, MSM)

Hardfork Evolution

EVM instructions and precompiles introduced over time:
HardforkInstructionsPrecompilesNotable Additions
Frontier1400x01-0x04Core opcodes, ECRECOVER, SHA256
Homestead1400x01-0x04DELEGATECALL behavior change
Byzantium1430x01-0x08RETURNDATASIZE, STATICCALL, MODEXP, BN254
Istanbul1440x01-0x09CHAINID, SELFBALANCE, BLAKE2F
Berlin1440x01-0x09Access list gas changes
London1450x01-0x09BASEFEE
Shanghai1470x01-0x09PUSH0, transient storage (TLOAD/TSTORE)
Cancun1480x01-0x0AMCOPY, BLOBHASH, BLOBBASEFEE, POINT_EVALUATION
Prague1480x01-0x13BLS12-381 precompiles (9 new)

Implementation Status

Zig: Complete

All 166 instructions and 21 precompiles fully implemented with:
  • Native C library integration (blst, c-kzg-4844, arkworks)
  • Comprehensive test coverage
  • WASM compilation support

TypeScript: Functional

  • Instructions: All 166 handlers implemented in pure TypeScript
  • Precompiles: Production-ready for most, stubs for BLS12-381 (use WASM)
For security-critical operations, always use Zig/WASM implementations.

Security

Validation

All implementations validate:
  • Stack depth (1024 max)
  • Memory bounds
  • Gas sufficiency
  • Input lengths
  • Opcode validity for hardfork

Constant-Time Operations

Cryptographic operations use constant-time algorithms to prevent timing attacks on sensitive data.

DoS Protection

Gas metering prevents computational DoS:
  • Memory expansion costs grow quadratically
  • Storage operations priced to prevent abuse
  • Call depth limited to 1024
  • Precompile gas checked before execution

References