Overview — Security: Reentrancy, MEV & Famous Hacks
The Smart Contracts part built a program that untrusting strangers can trust without trusting its author: public code, run identically by every node, folded into a stateRoot everyone checks. That is the triumph. This part is about its shadow. The very properties that make a contract trustworthy — it is public, it is immutable, anyone can call it — are the same properties that make it a target. On a private server a bug is an embarrassment you patch on Tuesday. On a public state machine holding other people’s money, a bug is an open safe with the combination printed on the door.
This is the part where the book’s one question — how do untrusting strangers agree on the state of a shared world computer? — turns adversarial. Everything so far assumed the participants merely mistrust each other’s honesty. Now we assume they actively mistrust each other’s intent: some caller, right now, is running your code not to use it but to break it, because breaking it pays. The state transition you designed so carefully is no longer just the mechanism of agreement. It is the attack surface.
Code is law, and law has no undo button
Section titled “Code is law, and law has no undo button”Start from the smallest uncomfortable truth. On Ethereum there is no administrator, no support ticket, no “reverse the transaction” button, and — outside a chain-splitting hard fork — no way to claw back stolen funds. Whatever your deployed code does when called is, by definition, what was supposed to happen. That is the meaning of “code is law”: the contract’s behavior is not what you intended, it is what you wrote.
Three properties, each a gift in the previous part, become a threat model here:
property the gift (smart-contracts part) the threat (this part) ───────────────────────────────────────────────────────────────────────────────── immutable nobody can quietly change the rules you can't patch a live bug composable anyone can build on your contract anyone can call you mid-call adversarial econ. a public market with no gatekeeper a profitable exploit WILL be found- Immutability. Once bytecode is at an address it cannot be edited (see the contract lifecycle). A fix is not an edit; it is a migration — deploy a new contract, convince the world to move to it, and hope the funds in the old one survive until they do. There is no hotfix.
- Composability. A call to your contract can, mid-execution, hand control to code the attacker wrote. Your function is not a closed procedure; it is a door the caller can walk back through before you have finished closing it. This single fact is the seed of reentrancy.
- Adversarial economics. A contract holding $10M is a $10M bounty on its own bugs, standing in public, forever, with the source and bytecode available to every attacker on Earth. You are not fixing bugs before users hit them; you are racing an unbounded set of adversaries who are paid to find the one you missed.
Put them together and you get the defining sentence of on-chain security: every bug is directly, immediately, and irreversibly exploitable for money. There is no gap between “vulnerability” and “loss.” The exploit is the disclosure.
Two families of failure
Section titled “Two families of failure”The incidents this part covers split cleanly into two families, and keeping them separate is the whole mental model.
Contract-level bugs. The contract’s own code is wrong, and calling it in a legal-but-unintended way moves funds it should not have moved. These are failures of a single program’s logic:
- Reentrancy — the contract hands control to the attacker before updating its own bookkeeping, and the attacker re-enters to spend a balance that, on paper, is already gone.
- Integer overflow — arithmetic wraps around silently (in old Solidity), so
0 - 1becomes a colossal positive balance. - Access control — a function that should be privileged isn’t guarded, so anyone can call the “only the owner may do this” path.
- Oracle manipulation — the contract trusts a price it reads on-chain, and the attacker distorts that price for the length of a single transaction.
Protocol-level value extraction (MEV). Here no contract is buggy at all. The loss comes from the state machine’s own rules: transactions are public before they are final, and whoever orders them into a block captures value from that ordering. The victim’s code did exactly what it should. The system’s freedom to reorder is what was exploited.
contract-level protocol-level (MEV) ───────────────────────────── ────────────────────────────────── the code is WRONG the code is FINE attacker calls it "legally" attacker reorders/inserts txs fix = write correct code fix = change how blocks are built e.g. reentrancy, overflow, access e.g. front-running, sandwichingBoth are security failures; both cost real money; but they are defended in completely different places — one in your Solidity, the other in the consensus and block-building layer. This part teaches you to feel, on sight, which kind of failure you are looking at.
Why we teach it through incidents
Section titled “Why we teach it through incidents”Every rule in Solidity’s security folklore was written in the past tense. The “checks-effects-interactions” pattern exists because The DAO didn’t follow it. Solidity’s built-in overflow checks exist because contracts kept minting fake tokens. Nobody sat down and derived these rules; the network lost money, and the rule is the scar. So we teach each defense by first re-running the heist that forced it — because a rule you can only recite is a rule you will misapply, but a rule you watched cost $50M is one you will never forget.
Roadmap for this part
Section titled “Roadmap for this part”Read these in order. Each page walks one incident from root cause → exploit → the defensive lesson it forced, then generalizes the lesson into a rule you apply to your own code.
| # | Page | The incident | Root cause | The lesson it forced |
|---|---|---|---|---|
| 2 | Reentrancy and The DAO | The DAO, 2016 (~3.6M ETH) | State updated after an external call, so the attacker re-entered mid-withdrawal | Checks-Effects-Interactions; reentrancy guards; a hard fork split ETH / ETC |
| 3 | Integer Overflow Before Solidity 0.8 | The 2018 ERC-20 “batchOverflow” mints | Unchecked arithmetic wrapped past 2²⁵⁶, minting astronomical balances | SafeMath, then checked arithmetic by default in Solidity ≥ 0.8 |
| 4 | Access Control and the Parity Multisig | Parity multisig, 2017 (freeze of ~513K ETH) | An unprotected initWallet / library selfdestruct left privileged functions callable by anyone | Guard every state-changing entry point; initialize once; beware shared library code |
| 5 | Oracle Manipulation and Flash Loans | DeFi price-oracle drains, 2020– | A contract trusted a spot price that a flash-loaned trade could move for one transaction | Use manipulation-resistant prices (TWAPs, aggregated feeds); never trust a single-block spot price |
| 6 | MEV: Front-running, Sandwiching & PBS | Ongoing; not a bug at all | Public pending transactions + freedom to order them = extractable value | Slippage limits, private orderflow, and PBS/Flashbots to structure the extraction |
| 900 | Revision · Security | — | — | A one-page recap of the whole part, ready for self-testing |
Pages 2–5 are the contract-level family; page 6 is the protocol-level (MEV) family. If you read only one to get the shape of the whole part, read Reentrancy and The DAO — it is the incident that taught Ethereum, in its first year, that a bug and a heist are the same event.
The one bug in one picture
Section titled “The one bug in one picture”Every contract-level exploit in this part is a variation on a single betrayal: your code trusted the world to behave, and the world was under no obligation to. Reentrancy is the purest form — you called out before you were done, and “out” was the attacker.
what you WROTE (looks fine) what the ATTACKER runs ───────────────────────────── ─────────────────────────────── withdraw(): fallback(): ← runs during send() 1. check caller has 1 ETH └─ call withdraw() again! 2. send 1 ETH to caller ───────────► (step 1 still passes: 3. effect set balance = 0 balance not zeroed yet) ▲ └─ control left YOUR contract at step 2, before step 3 protected youThe fix is not clever; it is an ordering discipline — do all your checks, commit all your effects, and only then interact with the outside world. That the world’s most expensive early bug came down to swapping two lines is exactly why this part exists: on a shared world computer, the cheapest-looking mistakes have the highest ceiling.
The thread
Section titled “The thread”Tie it back to the one question. Consensus (earlier parts) makes untrusting strangers agree on which state transition happened. Security is what happens when a stranger crafts a transition that is perfectly valid by the rules and yet ruinous by intent — the network will faithfully, unanimously, irreversibly agree on your loss. So “agree on shared state” has a dark corollary: the more perfectly the world computer agrees, the more perfectly it agrees to the exploit too. There is no referee to appeal to; the only defense is code that is correct against an adversary who is paid to prove it isn’t. And the book’s second half — what does it cost to run one? — gets its sharpest answer here: the cost of a world computer is not only the gas to run it, but the standing bounty every deployed line places on its own correctness.
Start with the incident that taught all of this at once: Reentrancy and The DAO →
Check your understanding
Section titled “Check your understanding”- On a public state machine, the text claims there is “no gap between vulnerability and loss.” Explain what that means and why it is not true for a bug in an ordinary private web server.
- Name the three properties of smart contracts that together form this part’s threat model, and for each give the one-line way it turns from a gift into a threat.
- Distinguish the two families of failure this part covers. For each, say where the failure lives (whose code is wrong) and where the fix is applied.
- MEV is listed as a security failure even though “no contract is buggy at all.” Reconcile that: what, exactly, is being exploited if not a bug in some contract?
- Using the reentrancy picture, state the defensive rule it motivates in one phrase, and explain why re-ordering just two lines of a
withdrawfunction defeats the attack.
Show answers
- It means the moment a vulnerability exists in a live contract holding funds, it is immediately and irreversibly exploitable — the exploit is the disclosure, and there is no patch, rollback, or refund. On a private web server there is a gap: a discovered bug can be patched before (or after) anyone abuses it, access can be restricted, transactions reversed, and the source isn’t handed to every attacker. On-chain, the code is public, immutable, callable by anyone, and the loss is final, so the gap collapses to zero.
- Immutability — a live bug can’t be patched, only migrated away from. Composability — anyone can call you, and your external call can hand control to the attacker mid-execution. Adversarial economics — a contract holding value is a standing, public bounty on its own bugs, so a profitable exploit will eventually be found.
- Contract-level bugs: the contract’s own code is wrong (reentrancy, overflow, missing access control, naive oracle use); the fix lives in your Solidity/bytecode. Protocol-level value extraction (MEV): no contract is buggy; the loss comes from the system’s rules — public pending transactions plus freedom to order them; the fix lives in the block-building/consensus layer (slippage limits, private orderflow, PBS/Flashbots), not in the victim’s contract.
- What is exploited is the state machine’s own freedom: transactions are visible in the mempool before they are final, and whoever builds the block chooses their order. Reordering, inserting, or sandwiching those transactions captures value from an otherwise-correct trade. The victim’s contract did exactly what it should; the extractable value comes from ordering, which is a property of consensus and block building, not a defect in any single contract.
- Rule: Checks-Effects-Interactions — do all checks, commit all state changes (effects), and only then interact with untrusted external code. Reentrancy works because control leaves your contract at the external send before the balance is zeroed, so the attacker’s fallback re-enters and passes the (still-true) balance check again. If you zero the balance before sending, the re-entrant call’s check fails, and the loop drains nothing — the same two operations, safe purely by order.