Harsh Yellow Poodle
Medium
The function getSqrtTwapX96
is used to calculate the Time-Weighted Average Price (TWAP) for a given Uniswap V3 pool. It fetches the cumulative tick values over a specified interval and calculates the square root price in X96 format. However, the function does not handle negative tick differences correctly, leading to potential inaccuracies in the TWAP calculation.
In cases where (tickCumulatives[1] - tickCumulatives[0])
is negative and (tickCumulatives[1] - tickCumulatives[0]) % twapInterval != 0
, the tick should be rounded down to account for fractional remainders. The current implementation directly performs integer division, which implicitly rounds towards zero, leading to an incorrectly higher tick value and therefore inaccurate price calculations.
The implementation of getSqrtTwapX96 does not apply rounding towards negative infinity for the tick calculation when (tickCumulatives[1] - tickCumulatives[0])
is negative.
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapInterval + 1; // from (before)
secondsAgos[1] = 1; // one block prior
(int56[] memory tickCumulatives, ) = IUniswapV3Pool(uniswapV3Pool)
.observe(secondsAgos);
// tick(imprecise as it's an integer) to price
sqrtPriceX96 = TickMath.getSqrtRatioAtTick(
int24(
(tickCumulatives[1] - tickCumulatives[0]) /
int32(twapInterval)
)
);
As result, in case if (tickCumulatives[1] - tickCumulatives[0])
is negative and (tickCumulatives[1] - tickCumulatives[0]) % twapInterval != 0
, then returned tick will be bigger then it should be, hence incorrect prices would be used.
In contrast, the Uniswap OracleLibrary includes a safeguard for such cases by explicitly decrementing the tick when necessary.
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
uint160 secondsPerLiquidityCumulativesDelta =
secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];
arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgo);
// Always round to negative infinity
@> if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;
No response
No response
No response
The TWAP derived from the function will be inaccurate when the cumulative tick difference is negative and not evenly divisible by the interval. In such cases, the tick is effectively rounded up instead of down, causing the returned price to be higher than the actual value.
No response
Similar to Uniswap, add the following check before using tick to calculate sqrtPriceX96.
function getSqrtTwapX96(address uniswapV3Pool, uint32 twapInterval)
internal
view
returns (uint160 sqrtPriceX96)
{
if (twapInterval == 0) {
// return the current price if twapInterval == 0
(sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniswapV3Pool).slot0();
} else {
uint32[] memory secondsAgo = new uint32[](2);
secondsAgo[0] = _interval; // from (before)
secondsAgo[1] = 0; // to (now)
(int56[] memory tickCumulatives, ) = IUniswapV3Pool(_uniswapV3Pool)
.observe(secondsAgo);
+ int56 tickCumulativeDelta = tickCumulatives[1] - tickCumulatives[0];
+ int24 tick = int24(tickCumulativeDelta / int56(int32(twapInterval)));
// Apply rounding towards negative infinity when necessary
+ if (tickCumulativeDelta < 0 && (tickCumulativeDelta % int32(twapInterval) != 0)) tick--;
+ sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);
- sqrtPriceX96 = TickMath.getSqrtRatioAtTick(
- int24(
- (tickCumulatives[1] - tickCumulatives[0]) /
- int32(twapInterval)
- )
- );
}
}