Euler Finance (2023)
A $197M exploit against the Euler Finance lending protocol in March 2023. The attacker eventually returned essentially all the funds after extensive negotiation. The bug was a missing health-check after a donate-to-reserves call, combined with an unusual self-liquidation pattern that produced bad debt that was then absorbed by the attacker.
Timeline
- March 13, 2023: Attacker drained $197M across multiple tokens (DAI, WBTC, stETH, USDC). The attack used multiple flash loans and self-liquidations.
- March 14–April 4, 2023: Negotiations between Euler and the attacker, conducted partly via on-chain messages. Bounty offered.
- April 4, 2023: Attacker returned the bulk of the funds (~$240M including some additional appreciation).
- Postmortem published: root cause analysis, with credit to Halborn and others for the forensic work.
Root Cause
Euler had a function donateToReserves that let a user donate eTokens (Euler's deposit receipts) to the protocol's reserves. The function deducted the user's balance but did not trigger a liquidity / health check.
Separately, Euler's liquidation logic allowed a position to be liquidated by another position controlled by the same user. The liquidation logic computed an "yield" — a bonus to the liquidator based on the size of the bad debt being absorbed.
Combining these:
- Attacker created two accounts: A and B.
- A deposits collateral and borrows up to the protocol's limit, becoming maximally leveraged but still healthy.
- A calls
donateToReserveswith a large amount of its eTokens. Because no health check, A is now technically insolvent (its collateral has been donated away). - B (the attacker's other account) liquidates A. Because A is deeply insolvent (the donation was huge), the "liquidation yield" formula gives B a very large bonus.
- B's bonus exceeds the actual collateral being seized — the protocol effectively pays out more than it should.
- Net: attacker walks away with the difference. Repeat with bigger flash loans.
Exploit Path
Per iteration (simplified):
1. Flash-loan large amount of DAI from Aave.
2. Deposit into Euler as account A. Borrow up to limit.
3. Call donateToReserves on account A with large amount, making A
deeply insolvent.
4. From account B, call liquidate on A. Receive disproportionate yield.
5. Withdraw B's now-inflated balance.
6. Repay flash loan; pocket the difference.
The "donate" was the critical step. Without a health check, A could become arbitrarily insolvent in one transaction.
What an Audit Should Have Caught
The Euler protocol had been audited multiple times by reputable firms (Halborn, Solidified, Sherlock, ZK Labs, and others) — eight audits or more. The bug survived all of them. In retrospect:
-
Missing health check on
donateToReserves. Every other state-changing function in Euler triggered acheckLiquiditycall. This one didn't. The asymmetry was visible in the code but not flagged. -
Self-liquidation pattern. The ability to liquidate a position controlled by the same user (via different accounts) is an unusual pattern. The liquidation yield formula was designed for adversarial liquidations and behaved badly when the "victim" was actually the liquidator.
-
Liquidation yield formula's edge case. The formula was designed assuming bounded insolvency. Deep insolvency (only possible via the donate bug) produced unbounded yield. An auditor should ask: "what happens when this input is much larger than expected?"
Lessons
-
Every state-changing function must be matched against the protocol's invariant. If the protocol's invariant is "every account is healthy after every operation," then every operation must include a health check or be provably unable to violate the invariant. Asymmetric checks are bugs.
-
The donate / charity pattern is suspect. Functions that let a user transfer value to a third party (including "the protocol") without obvious consideration are unusual and warrant scrutiny. Why does this exist? What can it be abused for?
-
Self-interaction is part of the threat model. Many protocols assume distinct adversaries (liquidator vs. liquidated user). A single attacker controlling multiple accounts can break these assumptions. Test cases should include "what if the same attacker controls both sides?"
-
Multiple audits do not provide multiplicative coverage. Eight audits found different things, but none found the donate-bug. Audits are not statistically independent — auditors look at similar things, miss similar things. Diversity of approach (formal verification, manual review, fuzzing, economic modeling) is more valuable than count.
-
Liquidation math is load-bearing and full of edge cases. Most major lending-protocol exploits in 2020-2023 have liquidation math in their root cause. The math is complex; the edge cases are many; formal verification or extensive fuzzing pays off.
-
Negotiation is a possible outcome. Euler successfully negotiated return. The attacker — whether motivated by guilt, fear of legal exposure, or strategic calculation — chose to return. This is not the typical outcome; planning for it is unwise. But the social / negotiation skills of the protocol team mattered in this case.
The Euler exploit is one of the most-studied cases in DeFi audit literature because it shows that even heavily-audited protocols can have bugs that, in hindsight, are visible. The improvements since (formal verification of Euler v2's logic, broader use of Certora/Halmos, structured invariant testing) reflect industry response.