This project is a from-scratch educational EVM interpreter written in Rust and exposed as a CLI binary.
The assignment sample shows Gas used: 6 for 0x6001600201.
Under Yellow Paper static gas costs, the correct value is 9:
PUSH1= 3PUSH1= 3ADD= 3
Total: 9.
This implementation intentionally follows Yellow Paper and
ethereum/execution-specs behavior for gas accounting.
- No existing EVM engine/library is used (
revm,evm,etk, etc. are not dependencies). Cargo.tomlonly usesprimitive-types,tiny-keccak,clap, andhex.
- 256-bit stack (
U256) with max size 1024 - Byte-addressable expandable memory (zero-padded)
- Persistent storage (
U256 -> U256) - Program counter and fetch-decode-execute loop
- Gas accounting with static + dynamic charging
- Pre-scanned
JUMPDESTset for jump validation
- Arithmetic:
ADD MUL SUB DIV SDIV MOD SMOD ADDMOD MULMOD EXP SIGNEXTEND - Comparison:
LT GT SLT SGT EQ ISZERO - Bitwise:
AND OR XOR NOT BYTE SHL SHR SAR - Signed behavior uses two's-complement edge-case handling
- Stack:
PUSH1-32 POP DUP1-16 SWAP1-16 - Memory:
MLOAD MSTORE MSTORE8 MSIZE MCOPY - Storage:
SLOAD SSTORE - Calldata:
CALLDATALOAD CALLDATASIZE CALLDATACOPY - Code:
CODESIZE CODECOPY EXTCODESIZE EXTCODECOPY EXTCODEHASH - Return data:
RETURNDATASIZE RETURNDATACOPY
- Control flow:
JUMP JUMPI JUMPDEST PC STOP RETURN REVERT INVALID - Transaction context:
ADDRESS BALANCE ORIGIN CALLER CALLVALUE GASPRICE GAS - Block context:
BLOCKHASH COINBASE TIMESTAMP NUMBER PREVRANDAO GASLIMIT CHAINID SELFBALANCE BASEFEE
- Hashing:
SHA3/KECCAK256 - Logging:
LOG0 LOG1 LOG2 LOG3 LOG4 - Calls:
CALL CALLCODE DELEGATECALL STATICCALLwith nested frames, returndata propagation, gas forwarding cap, and rollback on failure - Contracts:
CREATE CREATE2 SELFDESTRUCTwith init-code execution and runtime deployment
evm run --code 0x...evm run --file path.binevm run --trace- 23 integration tests
Implemented spec-aligned behavior for the covered opcodes using the Yellow Paper + execution-specs and relevant EIPs:
- Dynamic memory expansion gas:
C_mem(a) = 3a + floor(a^2 / 512) - Dynamic copy gas for copy-family opcodes and
MCOPY - Dynamic
EXPgas by exponent byte length - Dynamic
SHA3andLOG*data gas CALL*gas forwarding cap (all but one 64th, EIP-150 style)CREATE2hash-word gasRETURN/REVERTmemory expansion charging- Exceptional halts consume remaining gas (
InvalidOpcode, bad jump, OOG paths) CREATEaddress derivation from RLP(sender,nonce)- EIP-2929 warm/cold access-list charging for account and storage accesses
- EIP-3529 refund accounting (including refund cap at 1/5 of gas used)
evm/
|-- Cargo.toml
|-- README.md
|-- examples/
| `-- add.bin
|-- src/
| |-- main.rs
| |-- lib.rs
| |-- evm.rs
| |-- opcodes.rs
| |-- stack.rs
| |-- memory.rs
| `-- storage.rs
`-- tests/
`-- integration.rs
cargo build --releaseBinary path:
./target/release/evmRun from hex:
cargo run -- run --code 0x600160020100or with built binary:
./target/release/evm run --code 0x600160020100Run from file:
cargo run -- run --file examples/add.binTrace mode:
cargo run -- run --code 0x600160020100 --traceExample output:
Stack: [0x3]
Return: 0x
Gas used: 9
Status: STOP
For
0x600160020100, gas used is9(PUSH1=3,PUSH1=3,ADD=3,STOP=0) under Yellow Paper static gas.
0x6001600201(without trailingSTOP) also uses9gas. The sample that shows6is not Yellow-Paper gas accounting.
Rules:
- Use exactly one input source:
--codeor--file. - Optional modifiers are
--trace,--gas, and--calldata.
All valid run command shapes:
evm run --code <BYTECODE_HEX>
evm run --code <BYTECODE_HEX> --trace
evm run --code <BYTECODE_HEX> --gas <N>
evm run --code <BYTECODE_HEX> --calldata <CALLDATA_HEX>
evm run --code <BYTECODE_HEX> --trace --gas <N>
evm run --code <BYTECODE_HEX> --trace --calldata <CALLDATA_HEX>
evm run --code <BYTECODE_HEX> --gas <N> --calldata <CALLDATA_HEX>
evm run --code <BYTECODE_HEX> --trace --gas <N> --calldata <CALLDATA_HEX>
evm run --file <PATH_TO_BIN>
evm run --file <PATH_TO_BIN> --trace
evm run --file <PATH_TO_BIN> --gas <N>
evm run --file <PATH_TO_BIN> --calldata <CALLDATA_HEX>
evm run --file <PATH_TO_BIN> --trace --gas <N>
evm run --file <PATH_TO_BIN> --trace --calldata <CALLDATA_HEX>
evm run --file <PATH_TO_BIN> --gas <N> --calldata <CALLDATA_HEX>
evm run --file <PATH_TO_BIN> --trace --gas <N> --calldata <CALLDATA_HEX>If evm is not on your PATH, the shell cannot find that command.
That is why cargo run -- run --code ... works reliably in this project.
You can run it in any of these ways:
cargo run -- run --code 0x6001600201
./target/debug/evm run --code 0x6001600201
./target/release/evm run --code 0x6001600201On Windows PowerShell, use:
.\target\debug\evm.exe run --code 0x6001600201
.\target\release\evm.exe run --code 0x6001600201Optional install for global command access:
cargo install --path .What this does:
- Builds this local project and installs its binary globally as
evm. - Installs to Cargo's bin directory:
- Linux/macOS:
~/.cargo/bin - Windows:
%USERPROFILE%\\.cargo\\bin
- Linux/macOS:
- If needed, add that directory to your
PATH. - Reinstall after local code changes with:
cargo install --path . --forceAfter installation, evm run --code ... should work from anywhere.
stack.rs: bounded stack + DUP/SWAP helpersmemory.rs: expandable byte memory + typed word operationsstorage.rs: persistent key-value storeopcodes.rs: opcode names/helpers + static gas base tableevm.rs: VM state machine, opcode dispatch, gas logic, nested executionmain.rs: CLI parser + code loading + output formatting
Run:
cargo testCurrent output:
running 23 tests
test calldatacopy_charges_memory_and_copy_gas ... ok
test calldata_copy_and_mload_work ... ok
test call_executes_external_contract_code ... ok
test call_revert_sets_returndata ... ok
test balance_uses_cold_then_warm_access_cost ... ok
test create_deploys_runtime_code ... ok
test delegatecall_uses_caller_storage_context ... ok
test exp_charges_dynamic_gas_by_exponent_size ... ok
test gas_exhaustion_is_reported ... ok
test integer_overflow_wraps ... ok
test invalid_jump_destination_fails ... ok
test invalid_opcode_consumes_all_gas ... ok
test jumpi_to_valid_jumpdest ... ok
test multi_opcode_program_executes ... ok
test revert_returns_data ... ok
test returndatacopy_out_of_bounds_fails ... ok
test sar_sign_extends ... ok
test sdiv_handles_negative_values ... ok
test simple_add_program ... ok
test sload_uses_cold_then_warm_slot_cost ... ok
test sstore_dynamic_gas_is_enforced ... ok
test sstore_refund_is_capped_at_one_fifth ... ok
test stack_overflow_is_reported ... ok
test result: ok. 23 passed; 0 failed