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(); }