Topic lesson Sun, Aug 23, 2026 · 8 min read
How the EVM Runs Code
A stack machine, four places data can live, and why one opcode explains both proxies and EIP-7702.
Where this sits
- Lesson one: how a block gets proposed and finalized. Lesson two: what accounts and transactions are.
- This one covers what happens between those two — a transaction arrives at a contract, and something runs it.
- That something is the EVM, the virtual machine every Ethereum client implements identically. Identically is the point: consensus means every node must reach byte-for-byte the same result.
A stack machine
- The EVM has no registers and no variables. Operands are pushed onto a stack and opcodes consume them from the top.
- The stack holds at most 1024 items. Each item is a 256-bit word — chosen to match the output size of Keccak-256 and elliptic-curve arithmetic.
ADDpops two words and pushes their sum. That is the whole shape of the instruction set.
PUSH1 0x02 // stack: [2]
PUSH1 0x03 // stack: [3, 2]
ADD // stack: [5]
Solidity, Vyper and every other language compile down to this. Nothing else runs.
Four places data can live
| Where | Lifetime | Writable | Cost |
|---|---|---|---|
| Stack | One call frame | Push/pop only | ~3 gas per op |
| Memory | One call frame | Yes | 3 gas, plus expansion |
| Calldata | One call frame | No | 3 gas to read a word |
| Storage | Forever | Yes | Hundreds to thousands of gas |
- Memory is a byte array that starts empty at every call and vanishes when the call ends. Growing it costs gas, and the cost grows quadratically, so a large buffer is not a linear expense.
- Calldata is the transaction's
datafield: read-only input, cheaper than memory because it is already there. - Storage is the account's permanent key-value map — a 256-bit key to a 256-bit value — and it is the account's
storageRootfrom the previous lesson.
The cost gap between memory and storage is the single biggest fact about writing contracts. Arithmetic is 3–5 gas. A memory write is 3. A storage write is three to four orders of magnitude more. Storage is expensive because every node on Earth must keep the result forever, whereas memory is discarded microseconds later. Almost every real gas optimisation is some version of touching storage less.
Gas
- Each opcode has a price.
ADDis 3,MULis 5,JUMPDESTis 1,MLOADandMSTOREare 3 plus any memory expansion. SLOADandSSTOREare dynamic: the price depends on whether that slot has already been touched in this transaction (warm versus cold) and on whether a write changes a zero to a non-zero value.- Execution counts gas down from the transaction's
gasLimit. Hitting zero raises out-of-gas. - Gas prices are not a fee schedule for users. They are a model of what each operation costs the network to execute and to remember, and they get repriced when that model drifts from reality.
Calls, and who "self" is
A contract calling another contract creates a new call frame: fresh stack, fresh memory, its own gas allowance. Which storage that frame writes to depends on the opcode.
| Opcode | Code that runs | Storage written | msg.sender inside |
|---|---|---|---|
CALL | Target's | Target's | The caller |
DELEGATECALL | Target's | Caller's | The caller's caller |
STATICCALL | Target's | None — writes revert | The caller |
DELEGATECALLborrows code and keeps its own state. That one line is how every upgradeable proxy works, and how EIP-7702 delegation works.- It is also the sharpest edge in the EVM: the borrowed code writes into the caller's storage using the borrowed code's idea of the layout. If the two disagree about which slot means what, the caller's state is silently corrupted.
STATICCALLis the enforcement behind aviewfunction — the guarantee is at the VM level, not the compiler's.- Call frames nest at most 1024 deep, and a call is given at most 63/64 of the remaining gas, so a caller always keeps enough to handle the result.
Reverting
- A frame either finishes or reverts. Reverting undoes every state change made in that frame and everything below it, as if none of it happened.
- Gas already spent is not returned. The work was really done by every node.
- A revert propagates only as far as the frame that made the call. The caller sees a failed call and may ignore it and continue — which is why an unchecked call result is a real bug rather than a style issue.
Why it matters
- Cost intuition comes from the table above, not from lines of code. A loop doing arithmetic over memory is cheap. The same loop writing a storage slot each pass is not.
- Proxies and delegation are one opcode. Understanding
DELEGATECALLcovers upgradeable contracts, most proxy exploits, and what an EIP-7702 wallet actually does when it runs. - "It reverted" is a specific event with specific rules — state rolled back, gas kept, failure visible only to the immediate caller.
- Gas costs are policy and they move. Later lessons cover repricings that change these numbers; this table is where they land.
Where to go deeper
- The opcode reference — every opcode with its gas cost and stack effect. Worth skimming once end to end; it is shorter than expected.
- ethereum.org on the EVM for the machine-state versus world-state framing.
- evm.codes — an EVM playground. Stepping through ten instructions teaches the stack faster than reading about it.
execution-specson GitHub, the executable Python spec, for exactly what a client must do.
Threads left open
- Storage layout and packing — how Solidity assigns slots
- The actual warm/cold constants and where they get repriced
- Precompiles and contract creation via CREATE/CREATE2 at the EVM level
The next topic lesson picks these up.