From 78246ed3991bd1aac36af1840fab7d68aa346b3c Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 12 Mar 2026 07:10:13 +0000 Subject: [PATCH] fix: fix: Debug failing staking-safe attack in evolution fitness (#596) Stake.nextPositionId starts at 654_321, so attack files cannot use literal on-chain IDs (e.g. positionId=1 always reverts with PositionNotFound). Fix AttackRunner to treat the JSONL positionId field as a 1-based index into the list of positions created by stake ops during the current run: - Add IStake.snatch returns (uint256) to the interface so the returned ID is captured. - Track returned IDs in _stakedPositionIds[] (inserted in creation order). - _executeUnstake resolves positionId to _stakedPositionIds[positionId-1] before calling exitPosition, matching the natural "unstake position 1" semantics in the attack DSL. KRK approval for Stake was already present in _setup(); no other changes needed. Co-Authored-By: Claude Sonnet 4.6 --- onchain/script/backtesting/AttackRunner.s.sol | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/onchain/script/backtesting/AttackRunner.s.sol b/onchain/script/backtesting/AttackRunner.s.sol index e0a33b6..351ee35 100644 --- a/onchain/script/backtesting/AttackRunner.s.sol +++ b/onchain/script/backtesting/AttackRunner.s.sol @@ -50,7 +50,7 @@ interface IOptimizer { interface IStake { // taxRate matches the actual Stake.sol parameter name (a raw rate value, not a lookup index) - function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) external; + function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) external returns (uint256 positionId); function exitPosition(uint256 positionId) external; } @@ -169,6 +169,9 @@ contract AttackRunner is Script { address internal optAddr; IUniswapV3Pool internal pool; bool internal token0isWeth; + /// @dev On-chain position IDs returned by Stake.snatch(), in insertion order. + /// Attack files reference positions by 1-based index (positionId=1 → _stakedPositionIds[0]). + uint256[] internal _stakedPositionIds; /// @dev Direction of the most recent recenter: true = price moved up, false = price moved down. /// Read by _logSnapshot to include in post-recenter snapshots. bool internal _lastRecenterIsUp; @@ -313,19 +316,29 @@ contract AttackRunner is Script { /// @dev Stake KRK via Stake.snatch() with no snatching. /// Attack files use the field key ".taxRateIndex" for backward compatibility; /// the value is passed directly as a raw taxRate to Stake.snatch(). + /// The returned positionId is appended to _stakedPositionIds so that + /// subsequent unstake ops can reference positions by 1-based index. function _executeStake(string memory line) internal { uint256 amount = vm.parseUint(vm.parseJsonString(line, ".amount")); uint32 taxRate = uint32(vm.parseJsonUint(line, ".taxRateIndex")); // JSONL key kept for compat vm.startBroadcast(ADV_PK); - IStake(stakeAddr).snatch(amount, advAddr, taxRate, new uint256[](0)); + uint256 newPositionId = IStake(stakeAddr).snatch(amount, advAddr, taxRate, new uint256[](0)); vm.stopBroadcast(); + _stakedPositionIds.push(newPositionId); } /// @dev Exit a staking position. + /// positionId in the attack file is a 1-based index into _stakedPositionIds + /// (the positions created by stake ops in this run), not the raw on-chain ID. + /// Stake.nextPositionId starts at 654_321, so literal IDs like "1" would always + /// revert with PositionNotFound — the index-based lookup resolves the real ID. function _executeUnstake(string memory line) internal { - uint256 positionId = vm.parseJsonUint(line, ".positionId"); + uint256 positionIndex = vm.parseJsonUint(line, ".positionId"); + require(positionIndex >= 1 && positionIndex <= _stakedPositionIds.length, + "AttackRunner: unstake positionId out of range (must be 1-based index of a prior stake op)"); + uint256 realPositionId = _stakedPositionIds[positionIndex - 1]; vm.startBroadcast(ADV_PK); - IStake(stakeAddr).exitPosition(positionId); + IStake(stakeAddr).exitPosition(realPositionId); vm.stopBroadcast(); }