ToyEVM

I wanted to understand the Ethereum Virtual Machine — not at the Solidity level, but at the opcode level. How does a JUMP actually work? Why does storage cost so much gas? What does a control flow graph look like for a while loop compiled to bytecode?

The only way I could answer these questions was to build one myself. These are my notes from that process: an EVM interpreter built from scratch in Rust, opcode by opcode, with zero external dependencies. It is a toy, obviously — but enough of one to run real Solidity contracts and get a fair understanding of what production implementations like revm do under the hood.

The material is organized into seven modules. You are welcome to follow along, work through the exercises, and build your own.

Why I chose to build an EVM

Reading the Yellow Paper is one thing. Implementing it is another. Building the interpreter forced me to confront every detail I would have glossed over: how carries propagate in 256-bit addition, why JUMPDEST validation is a security property, what the 63/64 gas rule actually prevents, and how a basic block analysis reveals structure hidden in flat bytecode.

Along the way I also learned Rust patterns I now use everywhere — newtypes for type safety, repr(u8) enums for jump table dispatch, trait objects for pluggable backends, and why Result<T, E> is the right way to model EVM errors.

What we build

ModuleThemeWhat we implementSolidity milestone
1The Machine Exists256-bit integers, a bounded stack, byte-addressable memory
2The Interpreter LoopOpcode decoding, the fetch-decode-execute loop, gas meteringReturn42 runs
3State and Control FlowPersistent storage (EIP-2929), JUMP/JUMPI, loops in bytecodeStore42, Loop, Add, Counter run
4Contracts and DeploymentCREATE/CREATE2 address derivation, the Host trait, LOG eventsEventEmitter runs
5The Call StackCALL/DELEGATECALL/STATICCALL, the 63/64 gas rule, call frames
6From Bytecode to IRBasic blocks, control flow graphs, stack-height analysis, liveness
7Optimization PassesConstant folding, dead code elimination, speculative pre-execution, interpreter performance

By the end, we have a working EVM interpreter that can execute real Solidity-compiled contracts, plus a compiler backend for bytecode analysis and optimization.

How each module works

Each module has three parts:

  1. Notes — what I learned about the concept, with code snippets pulled from the source files
  2. Exercises — how I tested my understanding, with cargo test commands and hints
  3. Deep Dive — review questions to test and deepen understanding

The exercises use a todo_exercise!() macro — every function signature is already in place, every test is already written. The work is to fill in the implementations until cargo test goes green.

What you need

Comfortable with basic Rust — ownership, traits, enums, pattern matching, Result/Option. If you have completed 100 Exercises to Learn Rust or equivalent, you are ready. No prior Ethereum, blockchain, or compiler knowledge is required.

References

These are the sources I used throughout. You do not need to read them before starting — I introduce concepts as they come up — but they are valuable companions:

Ready? Head to the Getting Started page to set up the environment and begin Module 1.