Merge pull request 'fix: getLiquidityParams: int256 overflow on large VWAP values (#622)' (#1030) from fix/issue-622 into master

This commit is contained in:
johba 2026-03-20 04:15:39 +01:00
commit df3389bdeb
3 changed files with 80 additions and 5 deletions

View file

@ -259,13 +259,19 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
{
uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
if (vwapX96 > 0) {
int24 rawVwapTick = _tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
// vwapX96 >> 32 converts Q96 Q64 (ABDKMath64x64) for _tickAtPriceRatio.
// Skip mirror tick if the shifted value exceeds int128 range to prevent
// overflow in the int128 cast (#622). mirrorTick stays at currentTick.
uint256 priceRatioX64 = vwapX96 >> 32;
if (priceRatioX64 <= uint256(uint128(type(int128).max))) {
int24 rawVwapTick = _tickAtPriceRatio(int128(int256(priceRatioX64)));
rawVwapTick = token0isWeth ? -rawVwapTick : rawVwapTick;
int24 vwapDistance = currentTick - rawVwapTick;
if (vwapDistance < 0) vwapDistance = -vwapDistance;
mirrorTick = token0isWeth ? currentTick + vwapDistance : currentTick - vwapDistance;
}
}
}
// 3. Clamp tick: minimum distance (anti-overlap with anchor)
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);

View file

@ -704,3 +704,4 @@ contract OptimizerNormalizedInputsTest is Test {
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";

View file

@ -502,4 +502,72 @@ contract ThreePositionStrategyTest is TestConstants {
vm.expectRevert();
strategy.setAnchorPosition(CURRENT_TICK, 20 ether, params);
}
// ========================================
// VWAP INT128 OVERFLOW GUARD (#622)
// ========================================
/// @notice VWAP values that would overflow int128 in _tickAtPriceRatio are handled
/// gracefully floor is placed using scarcity/clamp signals instead of reverting (#622).
function testFloorPositionLargeVWAPNoOverflow() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
params.capitalInefficiency = 0; // adjustedVWAP = 7*rawVwap/10
// Set VWAP so that getAdjustedVWAP(0) >> 32 exceeds int128.max (2^127-1).
// rawVwap = 2^160 adjustedVwap 7*2^160/10 2^159.1 shifted 2^127.1 > int128.max
uint256 hugeVwap = uint256(1) << 160;
strategy.setVWAP(hugeVwap, 1);
uint256 floorEthBalance = 80 ether;
uint256 pulledHarb = 1000 ether;
uint256 discoveryAmount = 500 ether;
// Must not revert mirror tick is skipped, floor uses scarcity/clamp fallback
strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Should be floor position");
assertTrue(pos.liquidity > 0, "Floor should have liquidity");
}
/// @notice VWAP at uint256.max >> 32 boundary must not revert.
function testFloorPositionMaxVWAPNoOverflow() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
params.capitalInefficiency = 0;
// Use a very large VWAP near uint256 limits
// getAdjustedVWAP(0) = 7 * rawVwap / 10 stays within uint256
uint256 maxSafeVwap = type(uint256).max / 7; // avoids overflow in 7*rawVwap
strategy.setVWAP(maxSafeVwap, 1);
uint256 floorEthBalance = 80 ether;
uint256 pulledHarb = 1000 ether;
uint256 discoveryAmount = 500 ether;
// Must not revert int128 guard skips mirror tick
strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertTrue(pos.liquidity > 0, "Floor should have liquidity even with extreme VWAP");
}
/// @notice Normal VWAP values (below int128 threshold) still compute mirror tick correctly.
function testFloorPositionNormalVWAPStillUsesMirror() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
params.capitalInefficiency = 0;
// Set a normal VWAP (1.0 in X96 format) well below int128.max after >> 32
uint256 normalVwap = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96
strategy.setVWAP(normalVwap, 1000 ether);
uint256 floorEthBalance = 80 ether;
uint256 pulledHarb = 1000 ether;
uint256 discoveryAmount = 500 ether;
strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Should be floor position");
assertTrue(pos.liquidity > 0, "Floor should have liquidity");
}
}