“Smart contract” suggests a legal document that executes itself. What actually exists on Ethereum and its many compatible chains is more modest and more interesting: a program stored on the blockchain that every node runs identically, whose only inputs are transactions and whose only outputs are changes to a shared state. Understanding the machine that runs it explains almost every surprising thing about contracts, from why loops are expensive to why a single missing check can drain a treasury.
Two kinds of account
Ethereum state is a map from addresses to accounts. An externally owned account is controlled by a private key and has a balance and a nonce. A contract account has, in addition, immutable code and a persistent storage: 2256 slots of 32 bytes each, almost all of them zero. Nobody has a key for a contract account; it acts only when a transaction or another contract calls it. That is the entire security model in one line: a contract can only do what its code allows, and its code is public.
What the EVM is
The Ethereum Virtual Machine is a deliberately simple, deterministic stack machine. Every operation, called an opcode, pops arguments from a stack of 256-bit words and pushes results. There are opcodes for arithmetic, for comparisons, for jumping within the code, for reading the transaction (who called, how much ether, what data), for reading and writing three kinds of memory, and for calling other contracts. There are no floating-point numbers, no threads, no clock beyond the block timestamp, no randomness, and no access to anything outside the chain. Those omissions are what make thousands of nodes produce the same result from the same input.
Three places data lives
| Area | Lifetime | Cost | Use |
|---|---|---|---|
| Stack | Current execution | Almost free | Working values; depth limit 1,024 |
| Memory | Current call | Cheap, grows quadratically | Temporary arrays, building return data |
| Storage | Forever (until overwritten) | Expensive: writing a new non-zero value costs tens of thousands of gas | Balances, owners, any state that must survive |
Calldata, the transaction's input bytes, is a fourth, read-only area. Reading from it is cheap, which is why well-written functions take large arguments as calldata instead of copying them into memory.
Gas: why every instruction has a price
Every opcode has a gas cost, roughly proportional to the work it imposes on every node in the world: adding two numbers costs 3 gas, writing a fresh storage slot costs 20,000, and simply sending a transaction costs a base 21,000. A transaction declares a gas limit; execution stops with an out of gas error if it is exceeded, and everything the transaction did is reverted, but the gas consumed is still paid. This is not a fee model bolted on for revenue. Without it a single infinite loop would freeze every node, so gas is the mechanism that makes an untrusted program safe to run on a shared computer. It also explains the design instincts of contract developers: loops over unbounded arrays are dangerous, storage writes are minimised, and events are used instead of storage when data only needs to be read off chain.
Following a transaction
Take a call to a token contract's transfer(address to, uint256 amount).
- Encoding. The wallet computes the function selector: the first four bytes of keccak-256 of the signature string
transfer(address,uint256), which is0xa9059cbb. It appends the two arguments, each padded to 32 bytes, following the ABI encoding rules. That is the “input data” a block explorer shows. - Dispatch. The contract's bytecode begins with a dispatcher generated by the compiler: it reads the first four bytes of calldata, compares them against every public function's selector and jumps to the matching code. No match runs the fallback function if there is one, or reverts.
- Execution. The transfer function loads the caller's balance from a storage slot, checks it is sufficient, subtracts, loads the recipient's slot, adds, and writes both back. Solidity computes the slot for
balances[addr]as the hash of the address and the mapping's position, which is how a mapping can address 2256 keys without allocating anything. - Event. The function emits
Transfer(from, to, amount). Logs are written to the transaction receipt, not to contract storage; contracts cannot read them, but they are cheap and indexed, so wallets and explorers rely on them. - Commit or revert. If execution reaches the end without error, the storage changes become part of the new world state. If any
requirefails or gas runs out, every change in this transaction is discarded as if it never ran. Atomicity is the property that lets complex multi-step DeFi transactions be safe to attempt: either the whole swap-and-repay happens, or nothing does.
Contracts calling contracts
A contract can call another with CALL, which runs the callee's code with the callee's storage and can send ether, or with STATICCALL, which forbids state changes and is what view functions use. The third form, DELEGATECALL, runs the callee's code in the caller's storage context, as if the code had been pasted into the caller. It exists to share library code, and it is the mechanism behind upgradeable proxies: a thin proxy contract holds the storage and delegates every call to an implementation address it can change. Users interact with the proxy's address forever while the logic behind it evolves. The cost is that “immutable code” becomes “code the admin can replace”, which is why the question who holds the upgrade key matters as much as the audit.
The bugs that cost the most
Most catastrophic contract failures are not exotic. They come from a handful of patterns, all consequences of the machine described above.
- Reentrancy. A contract sends ether to an address before updating its own bookkeeping. If the recipient is a contract, its code runs immediately and can call back into the sender, which still shows the old balance, and withdraw again. The DAO was drained this way in 2016. The fix is a discipline, checks-effects-interactions: verify, update state, and only then call out; or use a reentrancy guard.
- Unchecked arithmetic. EVM integers wrap around silently. Before Solidity 0.8, a subtraction below zero produced a huge number unless the code checked it; several token contracts minted effectively unlimited supply this way. Modern compilers revert on overflow by default, but hand-written
uncheckedblocks reintroduce the risk. - Missing access control. A function that should be owner-only but is public. It sounds too simple to happen and it happens every year, including in initialiser functions of proxies that anyone can call once to become the owner.
- Price oracle manipulation. A contract reads a price from an on-chain pool that an attacker can move with a large trade in the same transaction, often funded by a flash loan, then uses the distorted price to borrow or liquidate. The machine did exactly what it was told; the mistake was trusting a manipulable input.
- Delegatecall to untrusted code. Since delegatecall runs foreign code against your storage, a delegatecall to an attacker-controlled address hands over the contract. A famous 2017 wallet library incident locked hundreds of thousands of ether permanently through a related mistake.
How to read a contract with this in mind
When you open a verified contract's source, look first for where ether or tokens leave the contract and check what state was updated before that line. Find every function that can change an owner, an implementation address or a fee, and see who can call it. Trace every external price or balance the contract reads and ask whether the caller could have changed it in the same transaction. Those three passes cover the majority of historical exploits and take less time than reading the marketing site.
Frequently asked questions
What is the EVM? The Ethereum Virtual Machine is the deterministic stack-based computer that every Ethereum node runs to execute contract code. It has no access to anything outside the chain, which is what lets thousands of independent nodes reach exactly the same result.
Why does writing to storage cost so much gas? Every storage write must be kept by every full node forever, so the protocol prices it far above computation. Writing a new non-zero value to an empty slot costs on the order of twenty thousand gas, versus a few gas for arithmetic.
Can a smart contract be changed after deployment? The code at an address is immutable. Upgradeable contracts work around this with a proxy that delegates calls to an implementation address the admin can change, so in practice the behaviour users see can change. Check who controls the upgrade key.
What is reentrancy? A bug where a contract calls an external address before updating its own state, letting the called contract call back in and repeat the action using stale data. It is prevented by updating state before making external calls or by using a reentrancy guard.