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
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.
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
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:
| Category | Cost Range | Examples |
|---|
| Zero | 0 | STOP, RETURN (base) |
| Base | 2 | ADDRESS, ORIGIN, CALLER |
| Very Low | 3 | ADD, SUB, NOT, LT, GT |
| Low | 5 | MUL, DIV, MOD, BYTE |
| Mid | 8 | ADDMOD, MULMOD |
| High | 10 | JUMPI, balance check |
| Ext | 20-700 | BALANCE, SLOAD, LOG |
| Memory | 3/word + expansion | MLOAD, MSTORE, CREATE |
| Storage | 100-20000 | SLOAD, 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:
| Hardfork | Instructions | Precompiles | Notable Additions |
|---|
| Frontier | 140 | 0x01-0x04 | Core opcodes, ECRECOVER, SHA256 |
| Homestead | 140 | 0x01-0x04 | DELEGATECALL behavior change |
| Byzantium | 143 | 0x01-0x08 | RETURNDATASIZE, STATICCALL, MODEXP, BN254 |
| Istanbul | 144 | 0x01-0x09 | CHAINID, SELFBALANCE, BLAKE2F |
| Berlin | 144 | 0x01-0x09 | Access list gas changes |
| London | 145 | 0x01-0x09 | BASEFEE |
| Shanghai | 147 | 0x01-0x09 | PUSH0, transient storage (TLOAD/TSTORE) |
| Cancun | 148 | 0x01-0x0A | MCOPY, BLOBHASH, BLOBBASEFEE, POINT_EVALUATION |
| Prague | 148 | 0x01-0x13 | BLS12-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