Revision · The EVM
This part took Ethereum from a ledger to a computer. The overview promised that two words — deterministic and metered — would explain almost every design choice in the machine, and by now you have seen both earn their keep on every page. This recap walks the whole part back through in prose, with no new machinery. The goal is that you can close the page and re-derive the EVM from its two obligations: agree with every other node, and never let a stranger’s code run without paying.
Read it as a single argument. Each section is one page of the part, and each depends on the one before it — you cannot price opcodes you have not defined, and you cannot separate one contract’s storage from another’s until you know what storage is.
The machine — a 256-bit stack of words
Section titled “The machine — a 256-bit stack of words”Start with the stack machine. The EVM has no registers and no variables. Every operand lives on a single last-in-first-out stack of 256-bit words, and every instruction is defined by what it pops off the top and what it pushes back on. ADD pops two words and pushes their sum; PUSH pushes one literal; SSTORE pops a key and a value and writes nothing back to the stack at all. That is the entire computational model — a discipline so small it fits in a single match.
The key idea from that page is that on-chain code has two views, and they are the same bytes seen two ways:
how you WRITE it how it's STORED how it RUNS (typed ops) (raw bytecode) (decode loop) ┌───────────────┐ ┌──────────────────┐ ┌──────────────┐ Op::Push(2) ─assemble─▶ 60 00 00 00 00 00 ─decode▶ read one op, Op::Push(3) 00 00 02 01 ... run it, advance Op::Add (0x01 = ADD) pc past its Op::Return (0xf3 = RETURN) immediate bytes └───────────────┘ └──────────────────┘ └──────────────┘In our companion crate ethmini, Op is the typed view — a Rust enum you write and reason about — and a contract’s code is a flat Vec<u8> of the raw view, exactly how real Ethereum stores it. assemble turns ops into bytes; Op::decode turns bytes back into an op plus the position of the next instruction, so the interpreter walks the array without ever mistaking a PUSH’s immediate data for an opcode. The opcode numbers are not simplified — ADD is 0x01, SSTORE is 0x55, RETURN is 0xf3, exactly as in the yellow paper. What we shrank is the word width (64-bit u64 instead of 256-bit) and the instruction count (about a dozen instead of ~140). Every mechanism is faithful in shape; only the scale is teaching-sized.
Two safety properties on that page will matter for the rest: the stack has a hard limit (1024 words in real EVM, mirrored in ours), so a runaway push loop dies of gas before it dies of memory; and jump targets are validated against a pre-computed set of JUMPDEST positions, so you cannot smuggle a jump into the middle of a push immediate. Control flow can only land where the code author explicitly said it could.
The four data locations
Section titled “The four data locations”The data-locations page is the one that most confuses new EVM programmers, so hold it clearly. A running contract can put data in exactly four places, and they differ in lifetime, cost, and who may write them:
location lifetime cost who writes it ───────── ────────────────────── ───────── ─────────────────── stack this instruction burst ~cheapest the running op memory this call (then erased) cheap* the running contract calldata this call (read-only) cheap read the *caller* set it storage PERMANENT (per account) DEAREST SSTORE, survives callThree of the four are transient — they exist only for the duration of one call and vanish when it returns. The stack holds operands for the instructions immediately around it. Memory is a scratchpad the contract grows and writes freely during a call, then it is wiped. Calldata is the read-only input the caller handed in; the contract can read its own arguments but cannot alter them. Because all three disappear when the call ends, they never touch the thing every node must keep.
Storage is the exception, and the whole point. Storage is a per-account key→value map that is permanent: a write with SSTORE outlives the call, outlives the transaction, and becomes part of the account’s state — which means it changes the state root, the single hash that commits to the entire world. Everything a node must store forever and agree on byte-for-byte lives here. That is why, in ethmini, SLoad/SStore read and write the account’s storage map while the stack and memory are just local Vecs thrown away when run returns. The distinction is not a style choice; it is the line between “free scratch space” and “a burden imposed on every full node on Earth, permanently.”
Gas — a price on every opcode
Section titled “Gas — a price on every opcode”That last sentence is exactly what the gas page turns into money. Gas is the per-opcode meter that prices the real resource each instruction consumes, and its central discipline is charge before you act: the machine subtracts an instruction’s cost from the remaining budget before executing it, so the meter can never go negative and no work is ever done for free.
The ratios between costs are where the truth lives. In ethmini:
pub fn gas_cost(&self) -> u64 { match self { Op::Stop | Op::Return => 0, Op::JumpDest => 1, Op::Push(_) | Op::Pop | Op::Add | Op::Sub => 3, Op::Mul => 5, Op::Jump => 8, Op::JumpI => 10, Op::SLoad => 100, // a storage READ is dear Op::SStore => 200, // a storage WRITE is dearest of all }}An ADD touches a CPU register for a nanosecond and vanishes; it costs 3. An SSTORE grows state every full node must keep forever; it costs 200. The absolute numbers are simplified — real EVM’s storage pricing is far larger and depends on zero-vs-nonzero writes, refunds, and warm-vs-cold access — but the ordering is the real economic signal, and it maps straight onto the four data locations: the transient things are cheap, the permanent thing is expensive.
Gas is also the economic answer to the halting problem. You cannot statically decide whether a stranger’s program halts, but you do not need to: give it a finite budget, charge every step, and when the budget hits zero the machine performs an exceptional halt and reverts all state. In ethmini, run executes against a clone of storage and only hands the new map back on a clean STOP/RETURN; out of gas returns Err and the caller keeps the original untouched. State changes are all-or-nothing:
// Two pushes cost 3 each = 6, but we only allow 5 → the second push never runs.let code = assemble(&[Op::Push(1), Op::Push(2), Op::Add]);let err = run(&code, &empty(), 5).unwrap_err();assert!(matches!(err, VmError::OutOfGas { limit: 5, .. }));An infinite loop — JUMPDEST; PUSH 0; JUMP — does not hang a node; it burns gas each time around and halts as OutOfGas. That is the sentence to keep: gas is the price of Turing-completeness, and an out-of-gas revert is what makes it safe to let anyone deploy anything.
Execution context and the CALL family
Section titled “Execution context and the CALL family”A single transaction rarely runs one contract alone; contracts call contracts. The execution-context page makes precise the context every execution runs inside — who called me, how much value came with the call, and whose storage am I touching — and how the CALL family sets that context. This is where the four data locations stop being abstract: the whole danger is a call reaching into the wrong account’s storage.
The three call variants differ in exactly two questions — whose storage the callee executes against, and whose identity (msg.sender) it sees:
variant runs the callee's code, but uses... ──────────── ──────────────────────────────────────────────────── CALL callee's storage, callee's identity (a normal call) DELEGATECALL CALLER's storage, CALLER's identity (borrowed code) STATICCALL callee's storage, read-only (no SSTORE, no state change)CALLis the ordinary case: you run another contract’s code against its own storage, and from its point of view you are the caller. Value can travel with it. This is one contract asking another to do something on the other’s behalf.DELEGATECALLis the subtle one and the source of many real exploits. It runs the callee’s code but against the caller’s storage and with the caller’s identity — the callee is a borrowed library operating on your state as if its code were pasted into you. This is how upgradeable proxies work, and it is why a proxy and its logic contract must agree exactly on storage layout: aDELEGATECALLwriting slot 0 writes your slot 0.STATICCALLisCALLwith a promise of no side effects — anySSTOREor other state change inside it faults. It exists so a contract can safely read from another (a price oracle, a view function) with a machine-enforced guarantee that the read cannot mutate anything.
In ethmini, state.rs models the top-level TxKind::Call invoking vm::run against the target account’s own storage — the CALL case — and keeps each account’s storage a separate map, which is the whole discipline in miniature: identity and storage are properties of the context you run in, not of the code being run.
Determinism — why every node agrees
Section titled “Determinism — why every node agrees”Finally, the determinism page makes explicit the property everything above quietly assumed. The EVM must be deterministic: given the same bytecode, the same inputs, and the same starting state, every honest node must compute the identical result — the identical returned word and the identical new storage, and therefore the identical state root. If two nodes could disagree, the network would fork, and “agree on the state of a shared world computer” would be a lie.
Determinism is not a feature you add; it is a set of things you forbid. A normal runtime is full of nondeterminism, and the EVM bans all of it:
- No wall-clock time and no
now()— two nodes execute at different instants, so a clock would give different answers. Time enters only through block-level fields all nodes already agree on. - No true randomness —
rand()would diverge across nodes. On-chain “randomness” must be derived from agreed-upon chain data, with all the caveats that implies. - No floating point — IEEE rounding differs across hardware and compilers. The EVM is pure integer arithmetic, and it wraps on overflow (mod 2²⁵⁶ in real EVM, mod 2⁶⁴ in ours) rather than trapping, so the result is fully specified.
- No threads, no concurrency, no I/O, no OS calls — a single deterministic instruction stream, executed one op at a time, with no way to observe anything outside the agreed state.
ethmini’s run is the enforcement in miniature: it is a pure function of (code, storage, gas_limit) with no access to a clock, a random source, or the outside world, and its arithmetic wraps rather than traps. The snapshot-and-revert discipline you met with gas belongs here too — a reverted call leaves no partial write, so the state that every node hashes into the root is exactly the state a clean execution would have produced, or exactly the state before, and never anything in between. Determinism and atomicity together are what let strangers hash their worlds and get the same 32 bytes.
The thread
Section titled “The thread”Step back and the whole part is one machine defined by two obligations, and every page is one of them made concrete. The stack model and opcodes give you a computation so simple it can be specified exactly. The four data locations draw the line between free, transient scratch and the permanent state every node must keep — and that line is why gas prices a storage write two orders of magnitude above an addition, charging before it acts and reverting everything on out-of-gas so no hostile program can run forever or impose unbounded cost. The execution context and the CALL family let contracts compose while keeping each one’s storage and identity straight, because a call that crossed those lines would corrupt the shared state. And determinism is the property that makes the whole thing worth agreeing on: forbid clocks, randomness, floats, and threads, and every honest node walking the same bytecode reaches the same word and the same root.
That is the answer to the book’s question, sharpened for a computer. “How do untrusting strangers agree on the state of a shared world computer, and what does it cost to run one?” They agree because the machine is deterministic — same code, same inputs, same result, everywhere. And it costs, quite literally and by design, one gas price at a time, because metering is the only way to let anyone on Earth deploy code to a machine everyone else must faithfully run. The EVM is not a fast computer or a cheap one. It is exactly what it costs to make one shared, verifiable, and open to strangers — and now you have built one and can point at every price on the bill.
Next the book lifts its eyes from running the machine to running the network of machines: how nodes propose blocks, reach consensus, and decide whose version of this world state is canonical.