| An EVM bytecode interpreter extracted from Filecoin's builtin-actors. Running on Filecoin mainnet since March 2023. | ![]() |
|---|
evmi interprets EVM bytecode. It does not include gas metering, state management, or transaction processing. These are responsibilities of the embedding engine, and are either exposed via two traits (Platform and Storage), or transparently performed.
This separation exists because evmi was built for the Filecoin VM, where gas is metered at the Wasm level and state is managed by the client. The same separation makes evmi re-usable in other contexts: zkVM guests, alternative L1s, rollups, or anywhere you need EVM semantics without Ethereum's full state model.
- Size: around 4,400 LoC
- Opcodes: 142/145 (through Cancun, missing blob opcodes and CLZ)
- Precompiles: 9 standard + 7 BLS12-381 (optional)
- Targets: native,
wasm32,riscv64gc - Dependencies:
no_stdcompatible, no async
evmi provides the execution core for a zkEVM guest program. The proving-friendly infrastructure around it remains to be built.
| Component | evmi | evmi (needed) | Guest program |
|---|---|---|---|
| Opcode execution | ✓ | ||
| Stack, memory | ✓ | ||
| Precompiles (including BLS12-381) | ✓ | ||
| JUMPDEST validation | ✓ | ||
| Call semantics (63/64 rule) | ✓ | ||
| Deterministic execution | ✓ | ||
| Gas metering | ✓ | ||
| State access and commitment | ✓ | ||
| Transaction validation | ✓ | ||
| Receipts, state roots | ✓ | ||
| Witness generation | ✓ |
A complete zkEVM guest requires roughly twice what evmi currently provides. evmi is the interpreter half.
Evolving evmi into a zkEVM guest is not trivial. Gas metering needs to be added to evmi. The guest program needs a state commitment scheme suited to the proof system, witness generation, and transaction processing.
The upside: building up from a minimal core rather than adapting a stack designed for native execution. Every component can be optimized for proving from the start. State commitments can use Poseidon or other circuit-friendly hashes instead of Keccak. Witness structures can match the zkVM's memory model. No abstractions inherited from existing clients that made sense for different goals but add overhead in a SNARK.
Forking clients and making them prover-friendly is viable as various teams have demonstrated. But that path inherits design decisions made for unrelated constraints. Starting from ~4,400 lines of interpreter and building exactly what's might involve less work than adapting large codebases lines built for a different purpose.
The Platform trait provides block context, account lookups, and external calls. The Storage trait provides contract storage with EIP-2929 access tracking. evmi calls these during execution; the platform decides what happens under the hood.
| Fork | Date | Status | Notes |
|---|---|---|---|
| Cancun | Mar 2024 | Partial | Missing blob opcodes (0x49, 0x4a) |
| Pectra | May 2025 | ✓ | No new opcodes |
| Osaka/Fusaka | Dec 2025 | ✗ | Missing CLZ (0x1e) |
All opcodes through Cancun except blob-related:
| Range | Opcodes |
|---|---|
| 0x00 | STOP |
| 0x01-0x0b | ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND |
| 0x10-0x1d | LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR |
| 0x20 | KECCAK256 |
| 0x30-0x3f | ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, GASPRICE, EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY, EXTCODEHASH |
| 0x40-0x48 | BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE |
| 0x50-0x5f | POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, MSIZE, GAS, JUMPDEST, TLOAD, TSTORE, MCOPY, PUSH0 |
| 0x60-0x7f | PUSH1-PUSH32 |
| 0x80-0x8f | DUP1-DUP16 |
| 0x90-0x9f | SWAP1-SWAP16 |
| 0xa0-0xa4 | LOG0-LOG4 |
| 0xf0-0xff | CREATE, CALL, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT (note: CALLCODE 0xf2 not implemented) |
| Opcode | Hex | EIP | Fork | Notes |
|---|---|---|---|---|
| CALLCODE | 0xf2 | — | Frontier | Deprecated; use DELEGATECALL |
| BLOBHASH | 0x49 | 4844 | Cancun | Blob versioned hash |
| BLOBBASEFEE | 0x4a | 7516 | Cancun | Blob base fee |
| CLZ | 0x1e | 7939 | Osaka | Count leading zeros |
Blob opcodes require Platform trait extensions. If your target doesn't use blob transactions, these can be safely ignored.
| Address | Name | Status |
|---|---|---|
| 0x01 | ecrecover | ✓ |
| 0x02 | SHA-256 | ✓ |
| 0x03 | RIPEMD-160 | ✓ |
| 0x04 | identity | ✓ |
| 0x05 | modexp | ✓ |
| 0x06 | ecAdd | ✓ |
| 0x07 | ecMul | ✓ |
| 0x08 | ecPairing | ✓ |
| 0x09 | blake2f | ✓ |
| 0x0a | KZG point eval | ✗ (EIP-4844) |
| 0x0b-0x11 | BLS12-381 | ✓ (feature flag) |
Two traits define the boundary between evmi and your runtime. Implement these to provide execution context:
pub trait Platform {
type Error: Error + Send + Sync + 'static;
fn block_number(&self) -> u64;
fn block_timestamp(&self) -> u64;
fn balance(&self, address: EthAddress) -> Result<U256, Self::Error>;
fn extcode(&self, address: EthAddress) -> Result<Vec<u8>, Self::Error>;
fn call(&mut self, gas: u64, to: EthAddress, value: U256, input: &[u8])
-> Result<CallOutput, Self::Error>;
fn create(&mut self, gas: u64, value: U256, init_code: &[u8])
-> Result<CreateOutput, Self::Error>;
// See src/platform.rs for full interface
}
pub trait Storage {
type Error;
fn get(&mut self, key: U256) -> Result<U256, Self::Error>;
fn set(&mut self, key: U256, value: U256) -> Result<(), Self::Error>;
fn get_transient(&mut self, key: U256) -> Result<U256, Self::Error>;
fn set_transient(&mut self, key: U256, value: U256) -> Result<(), Self::Error>;
// See src/storage.rs for full interface
}use evmi::{execute, ExecutionState, System, StandardPrecompiles};
let precompiles = StandardPrecompiles;
let mut system = System::new(platform, storage, precompiles, bytecode, false);
let mut state = ExecutionState::new(input_data, caller, value);
let output = execute(&mut system, &mut state)?;| Flag | Default | Description |
|---|---|---|
std |
✓ | Standard library; disable for no_std |
bls12-381 |
✓ | BLS12-381 precompiles via blst crate |
# no_std without BLS
evmi = { version = "0.1", default-features = false }
# no_std with BLS
evmi = { version = "0.1", default-features = false, features = ["bls12-381"] }Requires just. Install cross-compilation targets with just install-targets.
| Command | Description |
|---|---|
just |
Build for host (default) |
just test |
Run tests |
just wasm |
Build for wasm32 (no_std) |
just riscv |
Build for riscv64gc (no_std) |
just riscv-bls |
Build for riscv64gc with BLS |
just release |
Release build for host |
just wasm-release |
Release build for wasm32 |
just riscv-release |
Release build for riscv64gc |
just check-all |
Verify all targets compile |
just clean |
Remove build artifacts |
- Gas metering
- Opcode gas cost table (static costs)
- Dynamic costs (memory expansion, EXP bit length, LOG data size)
- SSTORE accounting (EIP-2200 refunds, EIP-2929 warm/cold)
- Call stipends (2300 for value transfers)
- Out-of-gas error propagation
- Missing opcodes: BLOBHASH, BLOBBASEFEE, CLZ
- KZG point evaluation precompile (EIP-4844)
- Code size limit enforcement (24KB, EIP-170)
- Call depth limit (1024) (this is enforced client-side in Filecoin)
- Jumpdest table as bitset (8x memory reduction)
- Validation against ethereum/tests
- Benchmarks
Components needed beyond the interpreter. These could be separate crates or implemented in the guest program itself.
- Transaction processing
- Nonce validation and increment
- Signature verification and sender recovery (ECDSA)
- Intrinsic gas (21000 base + calldata costs)
- Access list processing (EIP-2930)
- Balance validation (value + gas × price)
- State layer
- Account model (nonce, balance, code hash, storage root)
- State commitment scheme (MPT, or proving-friendly variants)
- Storage proofs for witness generation
- State root computation
- Post-execution
- State mutation application (storage, balances, contract creation)
- Receipt generation (status, cumulative gas, logs)
- Logs bloom filter
- Block-level state root update
Extracted from the FEVM runtime in filecoin-project/builtin-actors. Credits to Filecoin Core Devs.
MIT OR Apache-2.0
