From fe385fb0103ed1acee25422e52451322414979af Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 12 Mar 2026 13:16:41 +0000 Subject: [PATCH] fix: fix: il-crystallization-80 attack times out (153 steps) (#597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `buy_recenter_loop` batch op to AttackRunner — executes N×(buy→recenter) cycles in a single Solidity loop, emitting snapshots after each recenter. Rewrite il-crystallization-80.jsonl from 153 individual JSONL steps to 2 lines using the new op with count=80, matching the intended attack name. Also corrects the cycle count from 76 (previous file) to the intended 80. Co-Authored-By: Claude Sonnet 4.6 --- onchain/script/backtesting/AttackRunner.s.sol | 61 ++++++- .../attacks/il-crystallization-80.jsonl | 153 +----------------- 2 files changed, 56 insertions(+), 158 deletions(-) diff --git a/onchain/script/backtesting/AttackRunner.s.sol b/onchain/script/backtesting/AttackRunner.s.sol index 10d2bd7..44c68ba 100644 --- a/onchain/script/backtesting/AttackRunner.s.sol +++ b/onchain/script/backtesting/AttackRunner.s.sol @@ -127,9 +127,12 @@ interface IUniswapV3Factory { * DEPLOYMENTS_FILE Path to deployments JSON (default: deployments-local.json) * * Supported operations: - * buy Swap WETH→KRK via SwapRouter. Fields: amount (wei string), token (ignored, WETH assumed) - * sell Swap KRK→WETH via SwapRouter. Fields: amount (wei string or "all"), token (ignored) - * recenter Call LM.recenter() via recenterAccess account. Emits a snapshot. + * buy Swap WETH→KRK via SwapRouter. Fields: amount (wei string), token (ignored, WETH assumed) + * sell Swap KRK→WETH via SwapRouter. Fields: amount (wei string or "all"), token (ignored) + * recenter Call LM.recenter() via recenterAccess account. Emits a snapshot. + * buy_recenter_loop Batch N×(buy→recenter) cycles in a single op. Fields: count (uint), amount (wei string). + * Emits a snapshot after each successful recenter. Avoids per-step forge overhead for + * high-cycle attacks that would otherwise time out (e.g. il-crystallization-80). * stake Call Stake.snatch(). Fields: amount (wei string), taxRateIndex (raw taxRate value passed to Stake.snatch) * unstake Call Stake.exitPosition(). Fields: positionId * mint_lp Add LP via NPM. Fields: tickLower, tickUpper, amount0 (wei string), amount1 (wei string) @@ -179,6 +182,9 @@ contract AttackRunner is Script { /// recenter_is_up=null on the initial snapshot (before any recenter has occurred) /// rather than the ambiguous false default. bool internal _hasRecentered; + /// @dev Snapshot sequence counter. Shared between run() and batch ops like + /// buy_recenter_loop that emit their own snapshots internally. + uint256 internal _seq; // ─── Entry point ───────────────────────────────────────────────────────── @@ -207,18 +213,18 @@ contract AttackRunner is Script { // Execute attack operations, snapshotting after each recenter. string memory attackFile = vm.envString("ATTACK_FILE"); - uint256 seq = 1; + _seq = 1; string memory line = vm.readLine(attackFile); while (bytes(line).length > 0) { bool isRecenter = _execute(line); if (isRecenter) { - _logSnapshot(seq++); + _logSnapshot(_seq++); } line = vm.readLine(attackFile); } // Final state snapshot. - _logSnapshot(seq); + _logSnapshot(_seq); } // ─── Setup ──────────────────────────────────────────────────────────────── @@ -272,6 +278,8 @@ contract AttackRunner is Script { _executeMintLp(line); } else if (_eq(op, "burn_lp")) { _executeBurnLp(line); + } else if (_eq(op, "buy_recenter_loop")) { + _executeBuyRecenterLoop(line); } else if (_eq(op, "mine")) { uint256 blocks = vm.parseJsonUint(line, ".blocks"); vm.roll(block.number + blocks); @@ -298,6 +306,47 @@ contract AttackRunner is Script { vm.stopBroadcast(); } + /** + * @dev Batch buy→recenter loop. Executes `count` cycles of (buy `amount` WETH → recenter), + * emitting a snapshot after each successful recenter. Using a single op instead of + * individual JSONL lines eliminates per-step forge-script dispatch overhead, allowing + * high-cycle attacks (e.g. 80 cycles) to complete within the fitness evaluation budget. + * + * Fields: count (uint), amount (wei string) + */ + function _executeBuyRecenterLoop(string memory line) internal { + uint256 count = vm.parseJsonUint(line, ".count"); + uint256 amount = vm.parseUint(vm.parseJsonString(line, ".amount")); + + for (uint256 i = 0; i < count; i++) { + // Buy WETH→KRK. + vm.startBroadcast(ADV_PK); + ISwapRouter02(SWAP_ROUTER).exactInputSingle( + ISwapRouter02.ExactInputSingleParams({ + tokenIn: WETH, + tokenOut: krkAddr, + fee: POOL_FEE, + recipient: advAddr, + amountIn: amount, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + ); + vm.stopBroadcast(); + + // Recenter. + vm.startBroadcast(RECENTER_PK); + try ILM(lmAddr).recenter() returns (bool isUp) { + _lastRecenterIsUp = isUp; + _hasRecentered = true; + _logSnapshot(_seq++); + } catch { + console.log("recenter: skipped (amplitude not reached)"); + } + vm.stopBroadcast(); + } + } + /// @dev Swap KRK→WETH via SwapRouter02. amount="all" uses full adversary KRK balance. function _executeSell(string memory line) internal { string memory amtStr = vm.parseJsonString(line, ".amount"); diff --git a/onchain/script/backtesting/attacks/il-crystallization-80.jsonl b/onchain/script/backtesting/attacks/il-crystallization-80.jsonl index baf15fa..53262e1 100644 --- a/onchain/script/backtesting/attacks/il-crystallization-80.jsonl +++ b/onchain/script/backtesting/attacks/il-crystallization-80.jsonl @@ -1,153 +1,2 @@ -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} -{"op":"buy","amount":"100000000000000000000","token":"WETH"} -{"op":"recenter"} +{"op":"buy_recenter_loop","count":80,"amount":"100000000000000000000"} {"op":"sell","amount":"all","token":"KRK"}