The DAO (2016)
The exploit that defined re-entrancy as a bug class. ~$60M (3.6M ETH) drained from a smart-contract DAO holding crowdfunded ETH. The attack succeeded against code that had been publicly reviewed by many people, and the response — a controversial hard-fork — split Ethereum into ETH and ETC.
Timeline
- April–May 2016: The DAO's crowdsale raised ~$150M in ETH, becoming the largest crowdfund in history at the time.
- June 5, 2016: Researchers (Peter Vessenes, Christian Reitwiessner) publicly noted that re-entrancy was theoretically possible in The DAO's split function. The DAO team acknowledged but planned to address in a future upgrade.
- June 17, 2016: Attacker began draining ETH via the documented re-entrancy. Drain continued for hours; the attacker stopped voluntarily after ~3.6M ETH.
- June 17–July 20, 2016: Community deliberation. A "soft fork" attempted to freeze the funds was deployed then withdrawn after a DoS vulnerability was found.
- July 20, 2016: Hard fork executed, restoring funds. A minority of miners refused the fork; Ethereum Classic (ETC) was born.
Root Cause
The DAO's splitDAO function (and the underlying withdrawRewardFor it called) sent ETH to the caller before updating the caller's internal balance. The callback during the ETH transfer let the caller re-enter and call splitDAO again, draining funds repeatedly before the balance was set to zero.
Exploit Path
The attacker's contract:
- Held a DAO token balance (gained legitimately).
- Called
splitDAO, which transferred the corresponding ETH to a new "child DAO" (an address the attacker controlled). - The ETH transfer triggered the attacker's fallback function.
- The fallback called
splitDAOagain, while the original call's balance update had not yet executed. - The second
splitDAOsaw the original balance as still intact and transferred the same ETH again. - Repeated dozens of times per top-level call.
The classical re-entrancy pattern: external call before state update.
What an Audit Should Have Caught
The pattern was already known to be dangerous in 2016. The check: any function that sends ETH (or makes an external call to an untrusted address) must update internal state before the external call, not after. The Checks-Effects-Interactions pattern (CEI) was the prescription, and it was known.
The DAO's code did the opposite: Interactions before Effects. A reviewer applying CEI explicitly would have flagged the function.
The auditor's question that should fire: "What can the recipient of this transfer do?" If the answer includes "re-enter this contract," that's the bug.
Lessons
-
Checks-Effects-Interactions is non-negotiable. Any function that makes an external call (especially ETH transfer or call to a token that may have a hook) must complete all state updates first.
-
Documented vulnerabilities must be fixed before deployment, not after. The DAO team knew about re-entrancy days before the exploit. The "we'll fix it in v2" posture is, in retrospect, untenable for a contract holding $150M.
-
Re-entrancy guards (
nonReentrantmodifier) are cheap and load-bearing. Modern OpenZeppelin contracts include this by default. Custom code that handles ETH or makes external calls should use them. -
The auditor must think like an attacker. "Can the called party take control of execution and re-enter?" is now table-stakes. In 2016, it was a new question; today, the same question applied to read-only re-entrancy, cross-contract re-entrancy, and ERC-777/ERC-1155 hooks is still finding bugs.
-
A protocol's contracts and its social/governance layer are coupled. The DAO hard fork was an extraordinary intervention that resolved this specific incident; subsequent exploits have generally not received the same treatment. The audit takeaway: do not rely on rollbacks. Assume the loss is permanent.
The re-entrancy pattern persists. The 2023 Curve incident (§4.16.10) was a re-entrancy bug. The 2016 lesson was not learned once and for all; it must be re-applied to every codebase.