Skip to content

Build Your Own EVM in Rust: The World Computer, From Scratch

The Bitcoin part left us with a ledger that can do exactly one thing: track who owns coins. Its state is a set of unspent outputs, each one created once and destroyed once, and the only question the network ever answers is “can these keys unlock these coins?” That is enough to move money. It is not enough to run a program.

This part rebuilds the ledger around a different noun. Instead of coins that are spent, we track accounts that are mutated: a map from address to a record that has a balance you increment in place, a counter, some persistent memory, and — for some accounts — code. Once an account can hold code and remember things between calls, the ledger stops being a calculator for money and becomes a shared world computer that untrusting strangers all run and all agree on. The book’s throughline, stated in this part’s terms: how do untrusting strangers agree on the state of a shared world computer — and what does it cost to run one? The answer to the second half is one word we will build from scratch: gas.

We build this over a small, runnable Rust crate called ethmini (rust/ethmini). By the end you will deploy a real contract onto your own chain, call it three times, and watch two things move with every call: the gas it burns and the state root — a single hash that fingerprints the entire world.

The single most important idea in this whole part is the shift from a stateless model to a stateful one. It is worth seeing side by side.

BITCOIN (UTXO) ETHEREUM (accounts)
───────────── ───────────────────
state = set of unspent outputs state = map[address → account]
a coin is created once, spent once an account is mutated in place
"balance" = coins your keys unlock balance is a field you edit
no memory between transactions storage persists between calls
scripts validate a spend code runs and changes state
stateless: outputs don't "remember" stateful: a contract remembers

In Bitcoin, a transaction consumes old outputs and produces new ones; nothing carries forward except the coins themselves. In Ethereum, a transaction reaches into a mutable map and edits it — and because a contract is just an account that holds code plus storage, a contract can remember. That memory is the whole point of a world computer: a program whose state lives in the chain, not in any one process, and that every node re-runs identically. (For the conceptual contrast on the real chains, see the Bitcoin book’s UTXOs vs. accounts.)

An Account in ethmini has exactly four fields, and each earns its place:

pub struct Account {
pub nonce: u64, // replay protection + contract-address seed
pub balance: u128, // native currency, smallest unit
pub storage: BTreeMap<u64, u64>, // the contract's persistent memory
pub code: Vec<u8>, // bytecode; empty for a plain wallet
}

The presence of code is the only thing that separates a contract from a wallet. Send a call to a codeless account and it is a plain value transfer. Send the same call to an account with code and the chain runs that code. That single branch is the difference between a ledger and a computer.

Read the pages in order. Each one adds one component to ethmini and, more importantly, forces one idea you cannot get around. Build the crate as you go — every page’s claim is something you can run.

#PageWhat you build in ethminiThe idea it forces
2Accounts and AddressesAccount, Address, the nonceA mutable ledger, not spent-once coins; the nonce as replay protection
3World State and the State-Transition FunctionWorldState, Transaction, apply()A blockchain is a state machine: stateₙ + tx → stateₙ₊₁; reject vs. revert
4The State Rootstate_root() over sorted accountsOne hash commits to the whole world, so strangers can compare states in 32 bytes
5A Tiny Stack VMOp, assemble, the interpreter loopBytecode + a stack machine turns the ledger into something you can program
6Gas and the Halting Problemgas_cost, the gas meter, out-of-gasEvery step costs gas, so a public machine can safely run untrusted code
7Deploying and Calling a ContractDeploy/Call, the counter contractBytecode that remembers: state that survives between calls and lives in the chain
900RevisionThe whole build recapped, from accounts to a running world computer

Each row is a rung. Accounts give us a mutable ledger; the state-transition function gives us the rule that moves it; the state root lets strangers agree on where it moved to; the VM lets a transaction do more than move money; gas makes that safe to expose to anyone; and deploy-and-call ties it together into a contract that remembers. Pull any rung out and the ladder stops reaching the top.

ethmini is a teaching chain. It keeps the shape of every real Ethereum idea while shrinking the parts that would drown the lesson in ceremony. Wherever it diverges, it says so — and so will these pages:

  • 256-bit math becomes 64-bit. Real EVM words and balances are 256-bit. We use u64 words and u128 balances. Arithmetic still wraps (mod 2⁶⁴ instead of mod 2²⁵⁶), so overflow behaves the same in spirit.
  • Keccak becomes SHA-256. We hash with SHA-256 (the sha2 crate); real Ethereum uses Keccak-256. Same job, a hash we already depend on.
  • The Merkle-Patricia trie becomes sort-and-hash. Our state root sorts the accounts by address and hashes the serialised bytes. Real Ethereum uses a trie that also gives efficient proofs and incremental updates — the reason it exists at all is exactly what the flat version can’t do, and page 4 explains the gap it leaves.
  • No signatures, blocks, or networking. A transaction’s from field stands in for “whoever a valid signature recovered to”. We model the state machine, not consensus. Blocks, proof-of-stake, and the peer-to-peer network all exist only to agree on which transactions to feed this function, and in what order — that is the subject of other parts, not this one.

None of these change the ideas. They change the recipe, and every page is upfront about which line it is cutting and why.

Here is where we are headed. The ethmini binary funds two accounts, transfers value, deploys a counter contract (a program whose only job is to keep a number in storage and add one to it each call), then calls it three times and reads the result back out of the chain:

// Deploy the counter, then call it three times.
let deploy = Transaction::deploy(alice, state.nonce(&alice), counter_code(), 0);
let counter = state.apply(&deploy)?.created.expect("deploy creates an address");
for _ in 0..3 {
let call = Transaction::call(alice, state.nonce(&alice), counter, 0, 100_000);
let r = state.apply(&call)?;
println!("status {:?}, gas used {}, count now {}",
r.status, r.gas_used, state.storage(&counter, COUNTER_SLOT));
}

Run it and you can watch the world computer work: the count climbs 1 → 2 → 3, each call reports the gas it burned, and the state root changes after every transaction that touched state. The count does not live in this program — it lives in the chain, in the contract’s storage, and would read back the same on any node that replayed the same transactions. Then the demo starves a call of gas and shows it revert: the storage write is undone, but the sender’s nonce still advances, so the transaction can never be replayed. That single scenario contains the entire thesis of the part.

You can run it right now, before reading another page — seeing the target output makes every later mechanism concrete:

Terminal window
cd rust/ethmini && cargo run

Every page in this part answers the same two-part question the book keeps asking, now scoped to a computer instead of a currency: how do untrusting strangers agree on the state of a shared world computer, and what does it cost to run one? Accounts and the state-transition function are how the state is defined and moved; the state root is how strangers agree they are looking at the same world in 32 bytes; the VM is what makes it a computer rather than a calculator; and gas is what it costs — the mechanism that makes running arbitrary strangers’ code on your machine safe instead of suicidal. Keep that thread in hand and none of the pieces will feel arbitrary.

  1. In one sentence each, contrast Bitcoin’s UTXO state with Ethereum’s account state. Which one lets a program remember between transactions, and which field of Account makes that possible?
  2. An Account has four fields: nonce, balance, storage, and code. Which single field decides whether sending a call to that account runs code versus just moving value?
  3. The roadmap builds five ideas in order (accounts, state-transition function, state root, VM, gas). Pick any one and say what would break in the final demo if you removed it.
  4. ethmini makes four honest simplifications. Name two, and for each, say what real-Ethereum capability the simpler version gives up.
  5. Restate the book’s throughline in this part’s terms, and name the one mechanism that answers its “what does it cost?” half.
Show answers
  1. Bitcoin’s state is a set of unspent outputs, each created once and spent once — stateless, with nothing carried forward. Ethereum’s state is a map from address to a mutable account record that is edited in place. Ethereum lets a program remember, and the storage field (persistent key/value memory that survives between calls) is what makes it possible.
  2. The code field. A call to an account with empty code is a plain value transfer; a call to an account holding code makes the chain run that code. is_contract() is just !code.is_empty().
  3. Any valid answer: e.g. remove the state root and strangers can no longer cheaply agree they hold the same world (they’d have to compare every account); remove gas and the counter’s loop-capable VM could run forever, so no one could safely accept a call; remove the VM and a “call” could only move value, never increment a counter; remove the state-transition function and there is no rule turning one state into the next; remove accounts and there is no mutable storage for the count to live in.
  4. Any two: 256-bit math → u64/u128 (gives up the full 256-bit range, though wrapping arithmetic still behaves in spirit); Keccak-256 → SHA-256 (gives up compatibility with real Ethereum hashes/addresses); Merkle-Patricia trie → sort-and-hash (gives up efficient membership proofs and incremental updates); no signatures/blocks/networking (gives up consensus and real replay authentication — from is trusted rather than recovered from a signature).
  5. How do untrusting strangers agree on the state of a shared world computer, and what does it cost to run one? The mechanism that answers the cost half is gas: every VM step is priced, so running arbitrary untrusted code on your own machine is bounded and safe.