From 37905a29b290eee2eff89a0fe4a1cdef9d0f4e7d Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 00:23:58 +0000 Subject: [PATCH] fix: mint_lp and burn_lp not soft-failing in attack execution (#630) Wrap mint_lp and burn_lp ops in _executeOp with try/catch to match the soft-fail pattern used by buy, sell, stake, and unstake. Replace burn_lp's require() with a soft return for out-of-range index validation. Co-Authored-By: Claude Opus 4.6 (1M context) --- onchain/test/FitnessEvaluator.t.sol | 33 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/onchain/test/FitnessEvaluator.t.sol b/onchain/test/FitnessEvaluator.t.sol index 755c44d..2fe0650 100644 --- a/onchain/test/FitnessEvaluator.t.sol +++ b/onchain/test/FitnessEvaluator.t.sol @@ -516,7 +516,7 @@ contract FitnessEvaluator is Test { vm.prank(advAddr); // Track the returned tokenId so burn_lp can reference it by 1-based index, // making attack files fork-block-independent (NPM tokenIds depend on fork tip). - (uint256 mintedTokenId,,,) = INonfungiblePositionManager(NPM_ADDR).mint( + try INonfungiblePositionManager(NPM_ADDR).mint( INonfungiblePositionManager.MintParams({ token0: t0, token1: t1, @@ -530,22 +530,20 @@ contract FitnessEvaluator is Test { recipient: advAddr, deadline: block.timestamp + 3600 }) - ); - _mintedNpmTokenIds.push(mintedTokenId); + ) returns (uint256 mintedTokenId, uint128, uint256, uint256) { + _mintedNpmTokenIds.push(mintedTokenId); + } catch { } } else if (_eq(op, "burn_lp")) { // .tokenId in the attack file is a 1-based index into _mintedNpmTokenIds // (positions created by mint_lp ops in this run), not a raw NPM tokenId. // This mirrors the stake/unstake index pattern and avoids fork-block sensitivity. uint256 tokenIndex = vm.parseJsonUint(line, ".tokenId"); - require( - tokenIndex >= 1 && tokenIndex <= _mintedNpmTokenIds.length, - "FitnessEvaluator: burn_lp tokenId out of range (must be 1-based index of a prior mint_lp op)" - ); + if (tokenIndex < 1 || tokenIndex > _mintedNpmTokenIds.length) return; uint256 tokenId = _mintedNpmTokenIds[tokenIndex - 1]; (,,,,,, , uint128 liquidity,,,,) = INonfungiblePositionManager(NPM_ADDR).positions(tokenId); if (liquidity == 0) return; vm.startPrank(advAddr); - INonfungiblePositionManager(NPM_ADDR).decreaseLiquidity( + try INonfungiblePositionManager(NPM_ADDR).decreaseLiquidity( INonfungiblePositionManager.DecreaseLiquidityParams({ tokenId: tokenId, liquidity: liquidity, @@ -553,15 +551,16 @@ contract FitnessEvaluator is Test { amount1Min: 0, deadline: block.timestamp + 3600 }) - ); - INonfungiblePositionManager(NPM_ADDR).collect( - INonfungiblePositionManager.CollectParams({ - tokenId: tokenId, - recipient: advAddr, - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); + ) { + try INonfungiblePositionManager(NPM_ADDR).collect( + INonfungiblePositionManager.CollectParams({ + tokenId: tokenId, + recipient: advAddr, + amount0Max: type(uint128).max, + amount1Max: type(uint128).max + }) + ) { } catch { } + } catch { } vm.stopPrank(); } // Unknown ops are silently ignored (mirrors AttackRunner behaviour).