Skip to content

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.

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.

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 - 1 becomes 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, sandwiching

Both 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.

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.

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.

#PageThe incidentRoot causeThe lesson it forced
2Reentrancy and The DAOThe DAO, 2016 (~3.6M ETH)State updated after an external call, so the attacker re-entered mid-withdrawalChecks-Effects-Interactions; reentrancy guards; a hard fork split ETH / ETC
3Integer Overflow Before Solidity 0.8The 2018 ERC-20 “batchOverflow” mintsUnchecked arithmetic wrapped past 2²⁵⁶, minting astronomical balancesSafeMath, then checked arithmetic by default in Solidity ≥ 0.8
4Access Control and the Parity MultisigParity multisig, 2017 (freeze of ~513K ETH)An unprotected initWallet / library selfdestruct left privileged functions callable by anyoneGuard every state-changing entry point; initialize once; beware shared library code
5Oracle Manipulation and Flash LoansDeFi price-oracle drains, 2020–A contract trusted a spot price that a flash-loaned trade could move for one transactionUse manipulation-resistant prices (TWAPs, aggregated feeds); never trust a single-block spot price
6MEV: Front-running, Sandwiching & PBSOngoing; not a bug at allPublic pending transactions + freedom to order them = extractable valueSlippage limits, private orderflow, and PBS/Flashbots to structure the extraction
900Revision · SecurityA 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.

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 you

The 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.

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 →

  1. 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.
  2. 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.
  3. 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.
  4. 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?
  5. Using the reentrancy picture, state the defensive rule it motivates in one phrase, and explain why re-ordering just two lines of a withdraw function defeats the attack.
Show answers
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.