Part I · Chapter 2 of 43

The EVM: turning an account into a computer

Chapter 1 gave us accounts and unforgeable, signed transactions — but a transaction could still only do one hard-coded thing: move value. This chapter derives the machine that lets an account run *any* program: a stack, a way to pay for computation, and three places to keep data. Build it and the EVM falls out.

Updated Jun 23, 2026 · 14 min
Assumed
  • accounts & signed transactions

By the end of chapter 1 we had a shared ledger, accounts, and transactions that are unforgeable — signed by a key only their owner holds. But look closely at what a transaction can actually do: it moves value from one account to another. That’s it. The rule is welded into the protocol. If you want anything richer — an escrow that releases when two people agree, a token that isn’t ETH, an auction — you can’t express it, because there’s nowhere to put the logic and nothing to run it. This chapter fixes exactly that, and the thing we build to fix it is the Ethereum Virtual Machine. We’ll invent it the same way as everything else: name the missing piece, add the smallest thing that supplies it, and see what breaks next.

1 step Store code in the account

The problem: the only verb is “transfer”

A transaction names a recipient and an amount, and the protocol applies one fixed rule: subtract here, add there. There is no way to say “only move this if X” or “do this other thing entirely,” because the account holds just a balance and a nonce — no behaviour. To make accounts programmable, the idea writes itself: give an account a code field, and give the protocol a machine that runs that code whenever the account is called.

before: one fixed action if transfer: balance[to] += v the protocol hard-codes it add code after: arbitrary code PUSH 2 PUSH 3 ADD MUL … the account runs your program give the account a code field and a machine to run it that machine is the EVM
Before: the protocol hard-codes a single action (transfer). After: the account carries a code field holding arbitrary instructions, and the protocol runs them when the account is called. That runtime is what we have to design.

An account with code is a contract account An account whose behaviour is defined by code stored on-chain. When it's called, the EVM executes that code deterministically on every node; it has its own balance and storage but no private key. . Now the question is what that machine looks like. It has to be dead simple, because every node on Earth must run it and get bit-for-bit the same answer — otherwise they’d disagree on the new state and the chain would split.

→ Step 2: the simplest computer that can compute anything.

2 step A stack machine

Derive the machine: a stack, and nothing else

A CPU with registers is fiddly to specify and easy to disagree on. So reach for the most minimal model that’s still Turing-complete: a stack machine A computer with no named registers or variables. Instructions take their operands from the top of a last-in-first-out stack and push results back onto it. Simple to specify exactly — which is why the EVM is one. . There are no variables and no registers — just a last-in-first-out stack of values. Each instruction (an opcode A one-byte EVM instruction, e.g. 0x01 = ADD, 0x60 = PUSH1, 0x02 = MUL. The contract's code is just a string of these bytes; the EVM reads them one at a time. ) is one byte: it pops what it needs off the top and pushes what it produces back on.

That sounds abstract until you watch it. Here’s the program for (2 + 3) × 4 — five opcodes — running one step at a time. Watch the stack:

PUSH1 2 — push the value 2 PUSH1 3 — push 3 on top ADD — pop 3 and 2, push 5 PUSH1 4 — push 4 on top MUL — pop 4 and 5, push 20 bytecode 6002 PUSH1 2 6003 PUSH1 3 01 ADD 6004 PUSH1 4 02 MUL the stack top 2 3 5 4 20 every opcode pushes or pops the stack — no registers, no variables (2 + 3) × 4 = 20 — the result is left on the stack
The bytecode 60 02 · 60 03 · 01 · 60 04 · 02 running opcode by opcode. PUSH puts a value on top; ADD pops the top two (3 and 2) and pushes their sum (5); PUSH 4; MUL pops 4 and 5 and pushes 20. No variables — every value lives on the stack, and the answer is whatever's left on top.

Notice what just happened. ADD didn’t need to be told where 2 and 3 were — they were simply the top two items, and it replaced them with 5. That’s the whole trick: because operands always come from the top of the stack, each opcode’s behaviour is trivial to define, and any node running the same bytes gets the same result. Chain enough of these one-byte instructions together and you can compute anything.

→ Step 3: make every step cost something.

3 step Pay per step: gas

Gas: computation you pay for, and that always halts

You cannot, in general, look at a program and decide whether it will ever stop — that’s the halting problem, and it’s not a gap we can engineer around. So don’t try. Instead, attach a price to every opcode and make each transaction carry a budget. Executing an instruction spends from that budget; when the budget hits zero, execution stops immediately. That budget is gas A unit metering EVM work. Every opcode has a fixed gas cost; a transaction supplies a gas limit and pays for the gas it burns. When gas runs out, execution halts and reverts — which is what makes unbounded loops safe. .

every opcode costs gas; the budget only falls spent remaining PUSH -3 PUSH -3 ADD -3 SSTORE -20000 out of gas → revert, all changes undone you can't tell if a program halts, so charge per step and cap it computation is paid for and always terminates
Each opcode subtracts its cost from the gas budget, which only ever falls. Cheap ops (PUSH, ADD) cost a little; touching storage (SSTORE) costs a lot. If the budget hits zero mid-execution, everything reverts — so even an infinite loop simply runs until it runs out of gas, then stops.

This one move solves two problems at once. It makes computation paid-for, so spamming the network with heavy work costs real money; and it makes execution bounded, because a finite budget can only buy finitely many steps. An infinite loop is no longer dangerous — it just burns through its gas and halts.

Formula
gas_used = Σ cost(opcode) 1 gas_limit 2
  1. 1 a fixed price per instruction — 3 for ADD, 20000 to write a fresh storage slot
  2. 2 the budget the sender attaches; hit it and execution reverts
Total cost is just the sum of the per-opcode prices. Bounded budget → bounded steps → guaranteed to terminate.

→ Step 4: three places for data, by lifetime.

4 step Where data lives

Three data locations, priced by how long they last

Different data has different lifetimes, and lifetime is what should set the price. So the EVM gives code three distinct places to put values, and the whole design is that cost tracks permanence.

three places to put data, priced by how long they last stack 2 3 5 LIFO scratch +3 gas · wiped memory 2a 1f 00 c4 07 9b 3d e1 linear scratch cheap · wiped storage 42 permanent 20000 gas · survives ephemeral data is cheap; data that outlives the call costs about 1000x more
The stack holds the working values an opcode needs right now — tiny, LIFO, and free-ish, but gone the moment the call ends. Memory is a linear scratchpad of bytes — cheap, expandable, and wiped after the call. Storage is a key→value map that lives on every node's disk forever — which is why writing it is the most expensive thing the EVM does.
  • The stack The working surface where opcodes take operands and leave results. Max 1024 items, each a 256-bit word. Effectively free, but exists only for the current call. is for the values an instruction needs this very moment.
  • memory A linear, byte-addressable scratch space that starts empty each call and grows as needed. Cheap to use, but discarded when the call returns. is a scratchpad for the duration of a call — building up a return value, hashing a blob of bytes.
  • storage The contract's permanent key→value store, committed to the state trie and held on every node's disk. Reads and especially writes (SSTORE) are the priciest common operations, because everyone stores it forever. is the contract’s permanent memory — the only place data survives from one transaction to the next.

Because storage is written to every node’s disk and kept forever, it’s deliberately the most expensive resource in the machine — orders of magnitude pricier than touching the stack. That single price gap is the seed of a surprising number of later upgrades: pricing cold vs. warm access so first-touch reflects the real disk cost, and transient storage for data that only needs to live for one transaction. Keep the gap in mind — we’ll come back to it in Part III.

→ Step 5: run some bytecode yourself.

5 live Run it yourself

The real thing, one step at a time

Everything above is animated to teach the shape. This is the actual machine: a real EVM implementation running real bytecode. Step through the presets — the same (2 + 3) × 4, a store-and-load, a RETURN, and a countdown loop — and watch the stack, memory, storage, gas, and program counter move on every instruction. Edit the bytecode and it re-runs.

Interactive

EVM bytecode stepper

Step or play through real EVM execution: each opcode updates the stack, memory, storage, gas, and program counter. Try the countdown loop to watch gas fall as the loop spins.

1 / 6
Program
  1. 00PUSH10x02
  2. 02PUSH10x03
  3. 04ADD
  4. 05PUSH10x04
  5. 07MUL
gas3 used · −3 this op
Stack (top first)
  • 00x22
Storage

— empty —

Memory

— empty —

A real (faithful-subset) EVM, executed in your browser. Every instruction pops and pushes the 256-bit stack, can touch memory (cheap, transient) or storage(expensive, persistent), and burns gas until it halts or runs out. Edit the bytecode or pick a program and step through it.

A real EVM interpreter (stack, memory, storage, gas, jumps) executing the bytecode byte-for-byte.

04 Go Deeper Where to take it from here