This comprehensive guide will help you understand, contribute to, and build upon the Zig EVM implementation. Whether you're new to blockchain development, EVM internals, or the Zig programming language, this guide provides everything you need to get started.
- Quick Start
- Understanding the EVM
- Codebase Architecture
- Development Environment Setup
- Building and Testing
- Contributing Guidelines
- API Reference
- Common Use Cases
- Performance Optimization
- Troubleshooting
- Zig 0.15.1 or later (Download here)
- Git for version control
- Basic knowledge of blockchain concepts (recommended)
# 1. Clone the repository
git clone https://github.com/your-org/zig-evm
cd zig-evm
# 2. Run the basic EVM demo
zig run src/main.zig
# Output: Execution completed successfully, Result: 14
# 3. Run comprehensive tests
zig build test
# Output: All 145+ tests pass
# 4. Try the parallel execution demo
zig run src/benchmark_demo.zig
# Output: Performance benchmark results
# 5. Explore the codebase
ls -la src/
ls -la docs/🎉 Congratulations! You now have a working EVM implementation.
The Ethereum Virtual Machine (EVM) is a stack-based virtual machine that executes smart contracts on the Ethereum blockchain. Think of it as a decentralized computer that runs the same program (smart contract) across thousands of nodes worldwide.
Stack (LIFO - Last In, First Out)
┌─────────────┐
│ 42 │ ← Top of stack
├─────────────┤
│ 100 │
├─────────────┤
│ 7 │ ← Bottom
└─────────────┘
Operations like ADD pop two values, add them, and push the result back.
- Stack: 1024 items maximum, for temporary computation
- Memory: Linear, expandable byte array for temporary data
- Storage: Persistent key-value store (survives between transactions)
Gas = Computational Resource Unit
┌─────────────────┬──────────┐
│ Operation │ Gas Cost │
├─────────────────┼──────────┤
│ ADD, SUB, MUL │ 3 │
│ SLOAD (storage) │ 200 │
│ SSTORE (storage)│ 5000 │
└─────────────────┴──────────┘
Bytecode: [0x60, 0x03, 0x60, 0x04, 0x01]
PUSH1 3, PUSH1 4, ADD
Execution:
1. PUSH1 3 → Stack: [3]
2. PUSH1 4 → Stack: [4, 3]
3. ADD → Stack: [7] (4 + 3 = 7)
graph TD
A[Transaction] --> B[Load Bytecode]
B --> C[Initialize EVM State]
C --> D[Execute Opcodes]
D --> E{Gas Available?}
E -->|Yes| F[Next Opcode]
E -->|No| G[Out of Gas Error]
F --> H{More Opcodes?}
H -->|Yes| D
H -->|No| I[Execution Complete]
G --> J[Revert State]
I --> K[Update State]
zig-evm/
├── src/
│ ├── main.zig # Core EVM implementation
│ ├── bigint.zig # 256-bit arithmetic engine
│ ├── stack.zig # EVM stack implementation
│ ├── memory.zig # EVM memory management
│ ├── parallel.zig # Parallel execution framework
│ ├── parallel_optimized.zig # Advanced optimizations
│ └── opcodes/ # Individual opcode implementations
│ ├── add.zig # ADD opcode (0x01)
│ ├── mul.zig # MUL opcode (0x02)
│ └── ... # 80+ opcodes
├── tests/ # Comprehensive test suite
├── docs/ # Documentation
└── build.zig # Build configuration
pub const EVM = struct {
allocator: Allocator,
stack: Stack, // Computation stack
memory: Memory, // Temporary memory
pc: usize, // Program counter
gas: u64, // Remaining gas
gas_limit: u64, // Transaction gas limit
code: []const u8, // Bytecode to execute
accounts: HashMap, // Account states
// ... environmental context
};pub const BigInt = struct {
data: [4]u64, // 4 × 64-bit = 256-bit
// Core operations
pub fn add(a: BigInt, b: BigInt) BigInt
pub fn mul(a: BigInt, b: BigInt) BigInt
pub fn div(a: BigInt, b: BigInt) BigInt
// ... 20+ operations
};// Each opcode follows this pattern
pub fn getImpl() struct { code: u8, impl: OpcodeImpl } {
return .{
.code = 0x01, // ADD opcode
.impl = .{ .execute = execute },
};
}
fn execute(evm: *EVM) !void {
const b = try evm.stack.pop();
const a = try evm.stack.pop();
const result = a.add(b);
try evm.stack.push(result);
}┌─────────────────────────────────────────────────────────────┐
│ Parallel EVM System │
├─────────────────────────────────────────────────────────────┤
│ Input: Transaction Batch │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ TX 1 │ │ TX 2 │ │ TX N │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Dependency Analysis Layer (O(n) optimization) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Conflict │ │ Address │ │ Dependency │ │
│ │ Detection │ │ Mapping │ │ Graph │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Work-Stealing Thread Pool │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Thread 1 │ │ Thread 2 │ │ Thread N │ │
│ │ Local Queue │ │ Local Queue │ │ Local Queue │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Speculative Execution Engine │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Checkpoint │ │ Execute │ │ Rollback │ │
│ │ Creation │ │ Transaction │ │ on Conflict │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Output: Execution Results (5-6x faster) │
└─────────────────────────────────────────────────────────────┘
# Option A: Download from official site
curl -L https://ziglang.org/download/0.15.1/zig-linux-x86_64-0.15.1.tar.xz | tar -xJ
export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.15.1
# Option B: Use package manager (may not have latest version)
# Ubuntu/Debian
sudo apt install zig
# macOS
brew install zig
# Verify installation
zig version # Should show 0.15.1 or later# Install Zig extension
code --install-extension ziglang.vscode-zig
# Configure settings.json
{
"zig.path": "/path/to/zig",
"zig.zls.enable": true,
"zig.initialSetupDone": true
}- Vim/Neovim: Use
zig.vimplugin - Emacs: Use
zig-mode - CLion: Zig plugin available
# Clone with development branches
git clone --recurse-submodules https://github.com/your-org/zig-evm
cd zig-evm
# Set up git hooks (optional)
git config core.hooksPath .githooks
# Install additional tools
# Zig Language Server (ZLS) for better IDE support
git clone https://github.com/zigtools/zls
cd zls && zig build -Doptimize=ReleaseSafe# Basic EVM demo
zig build run
# or
zig run src/main.zig
# All tests (145+ tests)
zig build test
# Parallel execution demo
zig run src/benchmark_demo.zig
# Individual test files
zig test src/bigint.zig
zig test tests/test_opcodes.zig
# Release build (optimized)
zig build -Doptimize=ReleaseFast
# Debug build with extra checks
zig build -Doptimize=Debugtests/
├── test_bigint.zig # 256-bit arithmetic (20+ tests)
├── test_stack.zig # Stack operations (15+ tests)
├── test_opcodes.zig # Basic opcodes (25+ tests)
├── test_arithmetic.zig # Arithmetic ops (15+ tests)
├── test_memory.zig # Memory management (12+ tests)
├── test_parallel_execution.zig # Parallel system (15+ tests)
└── ... # 145+ total tests
# Run specific test file
zig test tests/test_bigint.zig
# Run specific test function
zig test tests/test_bigint.zig --test-filter "addition"
# Run with verbose output
zig test tests/ --verbose
# Run tests with different optimization levels
zig test tests/ -Doptimize=ReleaseFast# Quick performance overview
zig run src/benchmark_demo.zig
# Detailed benchmarks (when compilation issues are resolved)
zig build benchmark
# Memory usage analysis
valgrind --tool=massif zig-out/bin/zig-evm
# Profile execution
perf record zig-out/bin/zig-evm
perf report// Constants: SCREAMING_SNAKE_CASE
const MAX_STACK_SIZE = 1024;
// Types: PascalCase
pub const BigInt = struct { ... };
// Functions and variables: camelCase
pub fn calculateGasCost(opcode: Opcode) u64 { ... }
const gasUsed = evm.gas_limit - evm.gas;
// Files: snake_case
// bigint.zig, parallel_execution.zig/// Adds two 256-bit integers with overflow detection.
///
/// # Arguments
/// * `a` - First operand
/// * `b` - Second operand
///
/// # Returns
/// Sum of a and b, wrapping on overflow
///
/// # Example
/// ```
/// const result = BigInt.add(BigInt.init(5), BigInt.init(3));
/// // result represents 8
/// ```
pub fn add(a: BigInt, b: BigInt) BigInt {
// Implementation...
}// Use Zig's error system
const EVMError = error{
StackUnderflow,
StackOverflow,
OutOfGas,
InvalidOpcode,
};
// Functions that can fail return error unions
pub fn pop(self: *Stack) EVMError!BigInt {
if (self.items.len == 0) return EVMError.StackUnderflow;
return self.items.pop();
}
// Handle errors explicitly
const value = stack.pop() catch |err| switch (err) {
EVMError.StackUnderflow => return err,
else => unreachable,
};- Create the opcode file
# Example: Adding MULMOD opcode (0x09)
touch src/opcodes/mulmod.zig- Implement the opcode
// src/opcodes/mulmod.zig
const EVM = @import("../main.zig").EVM;
const BigInt = @import("../bigint.zig").BigInt;
pub fn getImpl() struct { code: u8, impl: @import("../main.zig").OpcodeImpl } {
return .{
.code = 0x09,
.impl = .{ .execute = execute },
};
}
fn execute(evm: *EVM) !void {
// Pop three values: a, b, N
const N = try evm.stack.pop();
const b = try evm.stack.pop();
const a = try evm.stack.pop();
// Calculate (a * b) % N
if (N.isZero()) {
try evm.stack.push(BigInt.init(0));
} else {
const product = a.mul(b);
const result = product.mod(N);
try evm.stack.push(result);
}
}- Register the opcode
// In src/main.zig, add to loadOpcodes()
const mulmod_impl = @import("opcodes/mulmod.zig").getImpl();
try self.opcodes.put(@enumFromInt(mulmod_impl.code), mulmod_impl.impl);- Add to the enum
// In src/main.zig, add to Opcode enum
pub const Opcode = enum(u8) {
// ... existing opcodes
MULMOD = 0x09,
// ...
};- Add gas cost
// In getGasCost() function
.MULMOD => 8, // Standard modular arithmetic cost- Write tests
// tests/test_mulmod.zig
test "MULMOD basic operation" {
var evm = try EVM.init(testing.allocator);
defer evm.deinit();
// Test: (3 * 4) % 5 = 2
try evm.stack.push(BigInt.init(5)); // N
try evm.stack.push(BigInt.init(4)); // b
try evm.stack.push(BigInt.init(3)); // a
const mulmod_impl = @import("../src/opcodes/mulmod.zig").getImpl();
try mulmod_impl.impl.execute(evm);
const result = try evm.stack.pop();
try testing.expect(result.eql(BigInt.init(2)));
}- Fork and clone
# Fork the repository on GitHub
git clone https://github.com/your-username/zig-evm
cd zig-evm
git remote add upstream https://github.com/original-org/zig-evm- Create feature branch
git checkout -b feature/your-feature-name- Make changes and test
# Make your changes
# Run tests
zig build test
# Run specific tests
zig test tests/test_your_feature.zig
# Check formatting
zig fmt --check src/- Commit and push
git add .
git commit -m "feat: add MULMOD opcode implementation
- Implement MULMOD opcode (0x09) with modular arithmetic
- Add comprehensive tests for edge cases
- Update gas cost calculations
- Add documentation and examples"
git push origin feature/your-feature-name- Create pull request
- Use the GitHub web interface
- Fill out the PR template
- Link any related issues
- Add reviewers
// Initialize EVM
var evm = try EVM.init(allocator);
defer evm.deinit();
// Set gas limit
evm.setGasLimit(21000);
// Load bytecode
const bytecode = &[_]u8{ 0x60, 0x03, 0x60, 0x04, 0x01, 0x00 };
evm.code = bytecode;
// Execute
try evm.execute();
// Get results
const gas_info = evm.getGasInfo();
std.debug.print("Gas used: {}\n", .{gas_info.used});// Push values
try evm.stack.push(BigInt.init(42));
try evm.stack.push(BigInt.fromHex("0x1234567890ABCDEF"));
// Pop values
const value = try evm.stack.pop();
// Peek without removing
const top = try evm.stack.peek();
// Stack state
const size = evm.stack.size();
const is_empty = evm.stack.isEmpty();// Store data
const data = &[_]u8{ 0x12, 0x34, 0x56, 0x78 };
try evm.memory.store(allocator, 0, data);
// Load data
const loaded = try evm.memory.load(0, 4);
// Memory size
const size = evm.memory.size();// Creation
const a = BigInt.init(42);
const b = BigInt.fromHex("0x123456789ABCDEF0");
const c = BigInt.fromBytes(&[_]u8{ 0x01, 0x02, 0x03, 0x04 });
// Arithmetic
const sum = a.add(b);
const product = a.mul(b);
const quotient = a.div(b);
const remainder = a.mod(b);
// Comparisons
const is_equal = a.eql(b);
const is_less = a.lt(b);
const is_zero = a.isZero();
// Conversions
const hex_string = a.toHex();
const bytes = a.toBytes();
const u64_val = a.toU64(); // If fits in u64// Create parallel scheduler
const config = ParallelConfig{
.max_threads = 4,
.enable_speculative_execution = true,
};
var scheduler = try OptimizedParallelScheduler.init(allocator, config);
defer scheduler.deinit();
// Execute transaction batch
const results = try scheduler.executeTransactionBatch(transactions);
// Process results
for (results) |result| {
if (result.success) {
std.debug.print("TX {}: Success (Gas: {})\n", .{ result.tx_id, result.gas_used });
} else {
std.debug.print("TX {}: Failed ({})\n", .{ result.tx_id, result.error_msg });
}
}// Test a simple contract that adds two numbers
test "smart contract addition" {
var evm = try EVM.init(testing.allocator);
defer evm.deinit();
// Contract bytecode: PUSH1 5, PUSH1 3, ADD, STOP
const contract_code = &[_]u8{ 0x60, 0x05, 0x60, 0x03, 0x01, 0x00 };
evm.code = contract_code;
try evm.execute();
// Result should be 8 (5 + 3)
const result = try evm.stack.pop();
try testing.expect(result.eql(BigInt.init(8)));
}// Analyze gas consumption of operations
fn analyzeGasUsage(allocator: Allocator, bytecode: []const u8) !GasReport {
var evm = try EVM.init(allocator);
defer evm.deinit();
evm.code = bytecode;
evm.setGasLimit(1000000);
try evm.execute();
const gas_info = evm.getGasInfo();
return GasReport{
.total_gas = gas_info.limit,
.gas_used = gas_info.used,
.gas_remaining = gas_info.remaining,
.efficiency = @as(f64, @floatFromInt(gas_info.used)) / @as(f64, @floatFromInt(gas_info.limit)),
};
}// Process a batch of transactions efficiently
fn processBatch(allocator: Allocator, transactions: []Transaction) !BatchResult {
var scheduler = try OptimizedParallelScheduler.init(allocator, .{
.max_threads = @max(std.Thread.getCpuCount() catch 4, 4),
.enable_speculative_execution = true,
});
defer scheduler.deinit();
const results = try scheduler.executeTransactionBatch(transactions);
var successful: u32 = 0;
var total_gas: u64 = 0;
for (results) |result| {
if (result.success) {
successful += 1;
total_gas += result.gas_used;
}
}
return BatchResult{
.total_transactions = transactions.len,
.successful_transactions = successful,
.total_gas_used = total_gas,
.average_gas_per_tx = total_gas / successful,
};
}// Example: Implementing a custom FIBONACCI opcode
const FibonacciOpcode = struct {
pub fn getImpl() struct { code: u8, impl: OpcodeImpl } {
return .{
.code = 0xF0, // Custom opcode range
.impl = .{ .execute = execute },
};
}
fn execute(evm: *EVM) !void {
const n = try evm.stack.pop();
if (n.lt(BigInt.init(2))) {
try evm.stack.push(n);
return;
}
var a = BigInt.init(0);
var b = BigInt.init(1);
var i = BigInt.init(2);
while (i.lte(n)) {
const temp = a.add(b);
a = b;
b = temp;
i = i.add(BigInt.init(1));
}
try evm.stack.push(b);
}
};// Add timing to your operations
const Timer = struct {
start: i128,
fn init() Timer {
return Timer{ .start = std.time.nanoTimestamp() };
}
fn elapsed(self: Timer) f64 {
const end = std.time.nanoTimestamp();
return @as(f64, @floatFromInt(end - self.start)) / 1_000_000.0; // ms
}
};
// Usage
const timer = Timer.init();
try evm.execute();
const execution_time = timer.elapsed();
std.debug.print("Execution took {d:.2}ms\n", .{execution_time});// Use arena allocators for batch operations
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
// All allocations will be freed at once when arena is deinited
var evms = try arena_allocator.alloc(*EVM, batch_size);
for (evms, 0..) |*evm, i| {
evm.* = try EVM.init(arena_allocator);
// No need for individual deinit calls
}// Optimal thread configuration
const optimal_threads = @min(@max(std.Thread.getCpuCount() catch 4, 2), 8);
// Batch size optimization
const optimal_batch_size = @max(@min(transactions.len / optimal_threads, 100), 10);
// Configuration for different workloads
const high_conflict_config = ParallelConfig{
.max_threads = 2, // Fewer threads for high conflict
.enable_speculative_execution = false,
};
const low_conflict_config = ParallelConfig{
.max_threads = 8, // More threads for independent transactions
.enable_speculative_execution = true,
};Issue: error: struct has no member 'init'
# Solution: Update to Zig 0.15.1
zig version
# If older version, download latest from ziglang.orgIssue: error: use of undeclared identifier
// Common cause: Missing imports
const std = @import("std");
const BigInt = @import("bigint.zig").BigInt;Issue: Stack overflow during execution
// Check stack size before operations
if (evm.stack.size() >= MAX_STACK_SIZE - 1) {
return error.StackOverflow;
}Issue: Out of gas errors
// Monitor gas usage
const gas_before = evm.gas;
try evm.execute();
const gas_used = gas_before - evm.gas;
std.debug.print("Operation used {} gas\n", .{gas_used});Issue: Slow dependency analysis
// Use optimized analyzer for large batches
if (transactions.len > 50) {
var optimized_analyzer = OptimizedDependencyAnalyzer.init(allocator);
defer optimized_analyzer.deinit();
// Use optimized version
} else {
// Use standard version for small batches
}Issue: Memory leaks
# Use Valgrind to detect leaks
valgrind --leak-check=full ./zig-out/bin/your-program
# Use Zig's built-in leak detection
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const leaked = gpa.deinit();
if (leaked) {
std.debug.print("Memory leak detected!\n", .{});
}
}// Enable debug mode for verbose output
const evm = try EVM.init(allocator);
evm.debug_mode = true; // If implemented
// Manual debugging
std.debug.print("PC: {}, Opcode: 0x{X}, Gas: {}\n", .{ evm.pc, evm.code[evm.pc], evm.gas });- Check the documentation in
/docs/ - Run the test suite to verify your setup:
zig build test - Look at examples in the codebase
- Create an issue on GitHub with:
- Zig version:
zig version - Operating system
- Minimal reproduction case
- Expected vs actual behavior
- Zig version:
- Start small: Fix documentation typos or add tests
- Pick an issue: Look for "good first issue" labels
- Read the code: Understand the existing patterns
- Ask questions: Don't hesitate to ask for clarification
- Extend the EVM: Add new opcodes or features
- Optimize performance: Profile and improve bottlenecks
- Add integrations: Connect to other blockchain tools
- Research applications: Explore new use cases
- Ethereum Yellow Paper: Formal EVM specification
- Zig Language Reference: Complete Zig documentation
- EVM Opcodes: Interactive opcode reference
- This codebase: Well-documented, production-ready implementation
Happy coding! 🚀 Welcome to the Zig EVM community!