fix: LiquidityManager silently clamps anchorWidth to 100, undocumented upper bound (#689)
- Extract magic number into named constant MAX_ANCHOR_WIDTH = 100 in LiquidityManager.sol - Document effective ceiling in IOptimizer.sol natspec for anchorWidth return value - Add testAnchorWidthAbove100IsClamped in LiquidityManager.t.sol asserting that optimizer-returned anchorWidth=150 is silently clamped to 100 (not rejected) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e484d4b03a
commit
39c25fa330
3 changed files with 54 additions and 2 deletions
|
|
@ -27,6 +27,7 @@ import { ExceededAvailableStake, Stake } from "../src/Stake.sol";
|
|||
import { ThreePositionStrategy } from "../src/abstracts/ThreePositionStrategy.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import "../test/mocks/MockOptimizer.sol";
|
||||
import "../test/mocks/ConfigurableOptimizer.sol";
|
||||
import { TestEnvironment } from "./helpers/TestBase.sol";
|
||||
import { UniSwapHelper } from "./helpers/UniswapTestBase.sol";
|
||||
|
||||
|
|
@ -1155,4 +1156,51 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
assertEq(harness.exposed_getKraikenToken(), address(harberg), "_getKraikenToken should return kraiken");
|
||||
assertEq(harness.exposed_getWethToken(), address(weth), "_getWethToken should return weth");
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Optimizer returning anchorWidth > 100 is clamped to MAX_ANCHOR_WIDTH (100), not rejected.
|
||||
* @dev Verifies that LiquidityManager silently truncates oversized anchorWidth values from
|
||||
* the optimizer rather than reverting. The resulting anchor position tick range must
|
||||
* equal exactly 100 * TICK_SPACING ticks wide.
|
||||
*/
|
||||
function testAnchorWidthAbove100IsClamped() public {
|
||||
// Deploy a ConfigurableOptimizer that returns anchorWidth = 150 (above the 100-tick ceiling)
|
||||
ConfigurableOptimizer highWidthOptimizer = new ConfigurableOptimizer(
|
||||
0, // capitalInefficiency = 0 (safest)
|
||||
3e17, // anchorShare = 30%
|
||||
150, // anchorWidth = 150 — intentionally above MAX_ANCHOR_WIDTH
|
||||
3e17 // discoveryDepth = 30%
|
||||
);
|
||||
|
||||
TestEnvironment clampTestEnv = new TestEnvironment(feeDestination);
|
||||
(
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
LiquidityManager customLm,
|
||||
,
|
||||
) = clampTestEnv.setupEnvironmentWithOptimizer(
|
||||
DEFAULT_TOKEN0_IS_WETH,
|
||||
RECENTER_CALLER,
|
||||
address(highWidthOptimizer)
|
||||
);
|
||||
|
||||
// recenter() must succeed (value clamped, not rejected)
|
||||
vm.prank(RECENTER_CALLER);
|
||||
customLm.recenter();
|
||||
|
||||
// Anchor position must exist and its tick range must match anchorWidth=100 (clamped), not 150.
|
||||
// The anchor spacing formula is: anchorSpacing = TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100)
|
||||
// For anchorWidth=100: anchorSpacing = 200 + 34*100*200/100 = 7000; tickWidth = 2*7000 = 14000
|
||||
// For anchorWidth=150: anchorSpacing = 200 + 34*150*200/100 = 10400; tickWidth = 2*10400 = 20800
|
||||
(uint128 liquidity, int24 tickLower, int24 tickUpper) = customLm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
assertTrue(liquidity > 0, "anchor position should have been placed");
|
||||
int24 tickWidth = tickUpper - tickLower;
|
||||
int24 expectedClamped = 2 * (TICK_SPACING + (34 * int24(100) * TICK_SPACING / 100)); // 14000
|
||||
int24 unclamped150 = 2 * (TICK_SPACING + (34 * int24(150) * TICK_SPACING / 100)); // 20800
|
||||
assertEq(tickWidth, expectedClamped, "anchorWidth above 100 must be clamped to MAX_ANCHOR_WIDTH=100");
|
||||
assertTrue(tickWidth < unclamped150, "clamped width must be narrower than unclamped anchorWidth=150 would produce");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue