evm.zig is an embeddable Ethereum Virtual Machine written in Zig. It is a VM
library first: small enough to inspect, strict enough to run Ethereum Execution
Spec Tests, and fast enough to benchmark against evmone and revm.
Current state: the VM is functionally close to complete for the downloaded EEST state-test corpus, with performance in parity territory against the comparison engines used by the local benchmark lab.
| Area | State |
|---|---|
| Correctness | Latest full state-test run: 44,039/44,039 vectors passed across 2,681 files, with 0 failures, skips, timeouts, or crashes. |
| Fork coverage | Fork rules are modeled through osaka (Spec.latest). The passing EEST set includes Frontier through Osaka buckets. |
| Performance | Portable-release reports compare evmz with evmone baseline, evmone advanced, and revm. Realistic VM-loop fixtures are close to evmone-baseline, broadly comparable to evmone-advanced, and generally ahead of the revm interpreter path used by the lab. |
| Public surface | Zig API, executor/state layer, EVMC-compatible C entrypoint, EEST runner, benchmark sidecar, and local report generator. |
This is not an execution client. Networking, block sync, trie/root validation, receipts, and production database integration are outside the current library scope.
Interpreter: opcode execution, gas accounting, memory, stack, control flow, storage, calls, logs, and system instructions.Executor: transaction-oriented execution over a state backend plus overlay.Host: native Zig host interface and EVMC bridge.- Fork-aware
Spechandling through Osaka. - Ethereum transaction, transaction-envelope, RLP, address, and
uint256helpers. - Native-backed precompiles for the supported fork set.
- EVMC-compatible C ABI via
evmc_create_evmz. - EEST sidecar for state-test and benchmark fixtures.
- Benchmark lab for VM-loop, opcode-kernel, host-boundary, evmone, and revm comparisons.
Build the library and C artifacts:
zig build -Doptimize=ReleaseFastRun unit tests:
zig build testRun the basic Zig example:
zig build exampleUse the package from Zig code:
const evmz = @import("evmz");
var executor = evmz.Executor.init(allocator, tx_context, .cancun);
defer executor.deinit();See examples/basic.zig for a complete in-memory call transaction.
The EEST sidecar owns fixture fetching, parsing, classification, and execution:
cd eest
scripts/fetch-eest-fixtures.sh
zig build eest-scope
zig build eest-classify
zig build eest -- ../.eest/fixtures/v5.4.0/fixtures/state_tests/path/to/test.jsonRoot delegates are available too:
zig build eest-test
zig build eest-scope
zig build eest-classifyThe latest full local summary reports:
files_seen=2681 completed=2681 timeouts=0 crashes=0
vectors=44039 passed=44039 failed=0 skipped=0 unchecked=0
That result is the main correctness signal for the current implementation. The
EEST benchmark runner also consumes decoded blockchain_tests benchmark
fixtures, but raw engine payloads, trie/root validation, receipts, and benchmark
genesis/stateful setup remain later-phase work.
Benchmarking lives in bench/ and writes ignored artifacts under output/.
The default comparison lane is portable release: Zig ReleaseFast, evmone
compiled into the Zig bench binary, and revm cargo --release, without native
CPU flags.
Generate a comparison report:
zig build bench-report -- --out-dir ../output/bench-reportUseful direct comparisons:
zig build bench-vm-loop -- --fixture fixtures/vm-loop/erc20-transfer --summary
zig build bench-vm-loop -- --engine evmone --fixture fixtures/vm-loop/erc20-transfer --summary
zig build bench-revm-vm-loop -- --fixture fixtures/vm-loop/erc20-transfer --summary
zig build bench-kernel -- --engine evmz --engine evmone-baseline --engine evmone --case add
zig build bench-revm-kernel -- --case addCurrent portable-release snapshot:
Speed relative to evmz for the same workload group: evmz = 1.00x; higher is
faster.
| Workload group | evmz | evmone-base | evmone-adv | revm-int |
|---|---|---|---|---|
| ERC20 mint | 1.00x |
~1.22x |
~0.85x |
~0.50x |
| ERC20 transfer | 1.00x |
~1.12x |
~0.93x |
~0.77x |
| Arithmetic loop | 1.00x |
~3.1x |
~1.0x |
~0.63x |
| Memory store loop | 1.00x |
~2.2x |
~0.91x |
~0.50x |
| Keccak loop | 1.00x |
~2.0x |
~1.1x |
~0.53x |
| Taken jumps | 1.00x |
~3.0x |
~0.59x |
~1.4x |
The VM-loop rows measure deployed runtime calls. Deployment, fixture loading,
and report generation are outside the timed section. evmz times
Interpreter.execute(). evmone rows time EVMC execute() on a fixture-scoped
VM with the same fixture protocol. revm rows use the lab's low-level interpreter
runner, so they are useful context but not a byte-for-byte identical execution
boundary.
See bench/README.md for benchmark commands and report format. Local generated
reports stay under ignored output/.
| Path | Purpose |
|---|---|
src/ |
Core VM, executor, state, host, precompiles, RLP, transaction, and C API. |
include/ |
Public C header for the EVMC-compatible entrypoint. |
examples/ |
Small runnable examples. |
eest/ |
Ethereum Execution Spec Tests sidecar. |
bench/ |
Micro, VM-loop, kernel, host-boundary, evmone, and revm benchmark lab. |
output/ |
Ignored local benchmark reports and checkpoints. |
.eest/ |
Ignored downloaded fixtures and local EEST summaries. |
- Full client duties are out of scope: networking, consensus, block sync, persistent state, receipts, and trie/root validation.
- The EEST raw engine payload path is intentionally later-phase.
- Performance work is now mostly interpreter-shape work: predecode, analyzed bytecode, or basic-block execution.
- Public API polish is still ongoing; the Zig API is the primary surface, with the C ABI available for EVMC-style embedding.
MIT.