BuildBot

State on the Chain

The EVM mental model

Lesson 1 of 5

What you'll learn

  • Distinguish an externally owned account from a contract account and say what each can hold
  • Describe a contract call as a pure state transition: world state in, world state out
  • Trace msg.sender and msg.value through a call and explain why contracts trust them

The Ethereum Virtual Machine (EVM) is the runtime every node executes. Think of the whole network as a single computer with one giant key-value database called world state. The keys are addresses. Every address is an account with four fields: a nonce, a balance in wei, a codeHash, and a storageRoot pointing at that account's own key-value storage.

There are two kinds of account. An externally owned account (EOA) is controlled by a private key; it has a balance and a nonce but no code. A contract account has code and storage, and no private key: nobody "owns" it, its code decides what it does when called. Deploying a contract is a transaction that creates a new address and puts bytecode there. From then on anyone can send a transaction to that address and the code runs.

world state
├── 0xd8dA…6045 (EOA)       balance 6.7 ETH, nonce 5956, no code
├── 0xA0b8…eB48 (USDC)      balance 0, code 4 KB, storage: balances mapping…
└── 0xB4e1…9Dc (Uniswap)    balance 0, code, storage: reserve0, reserve1…

A transaction is a state transition

Every transaction is a function applied to the world state: state' = apply(state, tx). The EVM loads the target's code, runs it with the transaction's data as input, and every SSTORE instruction modifies that contract's storage. If the code runs out of gas or hits a revert, all changes from that call are discarded and the state is as if it never ran, except the gas is still paid. This all-or-nothing rule is what makes on-chain logic composable: a call either fully happens or fully does not.

Inside a call, the code sees a small context: msg.sender (the address that made this call), msg.value (wei sent with it), msg.data (the calldata), and block.timestamp among others. msg.sender is trustworthy because the network verified the signature before running anything; a contract never has to authenticate the caller itself. That single fact is the basis of every access-control pattern you will write.

Storage is expensive on purpose

Writing a new 32-byte storage slot costs about 20,000 gas, roughly the price of a whole ETH transfer. Reading costs a fraction. Contract design is mostly the art of storing as little as possible and doing the rest with events, which the next lesson introduces.

The challenge models the machine: a world state, a contract with storage, and a call() that applies a function under msg.sender and rolls back on revert.

Model the EVM: state, calls, revert

Run it. A tiny world state holds two EOAs and one Counter contract. Calls change storage; the failing call reverts and leaves storage untouched. Add a decrement function that reverts when the count would go below zero.

Loading editor…
Knowledge check

A contract function reverts halfway through after writing two storage slots. What is the state afterwards?

Next: your first real contract. A notes registry with create, read, update and delete, in actual Solidity.

Saved on this device. Sign in to sync your progress everywhere.