Skip to content

Latest commit

 

History

History
105 lines (73 loc) · 4.47 KB

File metadata and controls

105 lines (73 loc) · 4.47 KB

Harsh Yellow Poodle

Medium

Incorrect Price Calculation for Negative Ticks Due to Missing Rounding Down

Summary

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.

Root Cause

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--;

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

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.

PoC

No response

Mitigation

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)
-              )
-        );

        }

   }