fix: Unclamped anchorWidth can overflow tick range — no upper-bound guard after MAX_ANCHOR_WIDTH removal (#783) (#817)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-15 21:34:33 +00:00
parent cd4926b540
commit a21cf398bf
4 changed files with 34 additions and 1 deletions

View file

@ -34,6 +34,7 @@
- [2026-03-14] llm_contrarian.push3 AW=150/250 clamped to 100 — three rounds unaddressed (#756)
- [2026-03-14] bootstrap.sh hardcodes BASE_SEPOLIA_LOCAL_FORK even on mainnet forks (#746)
- [2026-03-14] remove MAX_ANCHOR_WIDTH clamp in ThreePositionStrategy (#783)
- [2026-03-15] re-add MAX_ANCHOR_WIDTH=1233 guard at LiquidityManager call site; anchorWidth clamped before _setPositions, independent of Optimizer (#817)
- [2026-03-14] increase CALCULATE_PARAMS_GAS_LIMIT from 200k to 500k (#782)
- [2026-03-15] add evolution run 8 champion to seed pool (#781)
- [2026-03-15] fix FitnessEvaluator.t.sol broken on Base mainnet fork (#780)

View file

@ -204,7 +204,7 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
PositionParams memory params = PositionParams({
capitalInefficiency: (capitalInefficiency > MAX_PARAM_SCALE) ? MAX_PARAM_SCALE : capitalInefficiency,
anchorShare: (anchorShare > MAX_PARAM_SCALE) ? MAX_PARAM_SCALE : anchorShare,
anchorWidth: anchorWidth,
anchorWidth: (anchorWidth > MAX_ANCHOR_WIDTH) ? MAX_ANCHOR_WIDTH : anchorWidth,
discoveryDepth: (discoveryDepth > MAX_PARAM_SCALE) ? MAX_PARAM_SCALE : discoveryDepth
});

View file

@ -30,6 +30,10 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
int24 internal constant DISCOVERY_SPACING = 11_000;
/// @notice Minimum discovery depth multiplier
uint128 internal constant MIN_DISCOVERY_DEPTH = 200;
/// @notice Maximum safe anchorWidth: ensures 34 * MAX_ANCHOR_WIDTH * TICK_SPACING / 100 fits in int24
/// @dev With TICK_SPACING=200: 34 * 1233 * 200 = 8,384,400 int24 max (8,388,607).
/// anchorWidth=1234 produces 8,391,200 which overflows int24 and reverts in Solidity 0.8.
uint24 internal constant MAX_ANCHOR_WIDTH = 1233;
/// @notice The three liquidity position types
enum Stage {

View file

@ -473,4 +473,32 @@ contract ThreePositionStrategyTest is TestConstants {
strategy.setPositions(CURRENT_TICK, extremeParams);
assertEq(strategy.getMintedPositionsCount(), 3, "Should handle extreme parameters gracefully");
}
// ========================================
// ANCHOR WIDTH BOUNDS TESTS (#817)
// ========================================
function testAnchorWidthAtMaxBoundarySucceeds() public {
// MAX_ANCHOR_WIDTH = 1233: 34 * 1233 * 200 = 8,384,400 fits within int24 max (8,388,607)
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
params.anchorWidth = 1233;
strategy.setAnchorPosition(CURRENT_TICK, 20 ether, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertTrue(pos.tickLower < pos.tickUpper, "tickLower must be less than tickUpper");
assertTrue(pos.tickLower >= -887272, "tickLower must be >= MIN_TICK");
assertTrue(pos.tickUpper <= 887272, "tickUpper must be <= MAX_TICK");
assertGt(pos.liquidity, 0, "Anchor should have positive liquidity");
}
function testAnchorWidthAboveMaxReverts() public {
// anchorWidth = 1234: 34 * 1234 * 200 = 8,391,200 overflows int24 must not silently produce
// an invalid tick range; Solidity 0.8 arithmetic check causes a panic revert instead.
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
params.anchorWidth = 1234;
vm.expectRevert();
strategy.setAnchorPosition(CURRENT_TICK, 20 ether, params);
}
}