Euler Finance Exploit
A missing health check in the mint() function allowed an attacker to create leveraged positions and exploit accounting inconsistencies to drain $197M — most of which was later returned.
Background
Euler Finance was a permissionless lending protocol on Ethereum that allowed users to lend and borrow various tokens with leveraged positions. It was audited multiple times and considered secure. The exploit came from a subtle interaction between two features: mint() and donateToReserves().
The Vulnerability
The mint() function allowed creating synthetic leveraged positions — it created both collateral tokens (eTokens) and debt tokens (dTokens) simultaneously. The critical bug: no health check after minting. A healthy position check would have prevented undercollateralized positions from being created.
// mint() created leveraged positions without a health check
function mint(uint subAccountId, uint amount) external {
// Creates eToken (collateral) AND dToken (debt) simultaneously
eToken.mint(amount);
dToken.mint(amount);
// BUG: No health check here!
// A healthy position check would have prevented the attack
// checkLiquidity(msg.sender); <-- this line was missing
}The Attack — Simplified
- Flash loan 30M DAI from Aave
- Deposit 20M DAI into Euler as collateral (receive eDAI)
- Call
mint()to create 200M eDAI + 200M dDAI debt (10x leverage, no health check) - Call
donateToReserves(100M eDAI)— this reduces attacker's collateral dramatically - Position is now massively undercollateralized and liquidatable
- Self-liquidate: claim the donated collateral as a liquidation "bonus" at a steep discount
- Repay flash loan, keep the profit from the accounting discrepancy
- Repeat with different assets. Total drained: ~$197M across DAI, USDC, WBTC, stETH
Impact & Recovery
This was one of the largest DeFi exploits of 2023. However, through on-chain negotiation and off-chain communication, the attacker returned almost all of the funds within weeks — reportedly motivated by concerns about legal exposure.
Euler Finance has since relaunched with a new codebase.
Lessons Learned
Any function that modifies collateral or debt balances must verify that resulting positions are still healthy. This is especially critical for functions that create debt.
- Health checks everywhere: After every state change involving debt or collateral, verify position health
- Beware of donate-style functions: Any function that removes collateral should trigger a health check
- Test feature interactions: Security audits must consider how features combine, not just each feature in isolation
- Fuzz accounting invariants: Use Foundry's fuzzer to verify that no sequence of operations can break collateralization ratios