Deep Sepia Gazelle
Medium
Uniswap V3 pools utilize an observation mechanism to store historical price data, which is crucial for calculating Time-Weighted Average Prices (TWAP). The observation cardinality determines the number of historical data points the pool can store. The problem is that Uniswap V3 pools are initialized with an observation cardinality of 1.
The NumaOracle
contract uses the TWAP oracle to calculate the price to convert ETH amounts to Numa tokens, convert Numa tokens back to ETH and to determine the lowest and highest prices. And the observation interval is set by the user as an input argument.
The problem is that Uniswap V3 pools are initialized with an observation cardinality of 1. The pool can only store the most recent observation. And historical price trends and volatility cannot be accurately assessed.
No response
No response
No response
TWAP requires multiple data points over a specified interval. With only one observation, TWAP calculations are highly unreliable. Moreover, lack of historical data makes the pool's price more susceptible to short-term fluctuations. In that way the TWAP can be easily manipulated due to reliance on a single data point, the price is returned direct for the spot price.
The NumaOracle::getV3SqrtPriceAvg
calls the function IUniswapV3Pool.observe
to retrieve and calculate the TWAP price:
function getV3SqrtPriceAvg(
address _uniswapV3Pool,
uint32 _interval
) public view returns (uint160) {
require(_interval > 0, "interval cannot be zero");
//Returns TWAP prices for short and long intervals
uint32[] memory secondsAgo = new uint32[](2);
secondsAgo[0] = _interval; // from (before)
secondsAgo[1] = 0; // to (now)
(int56[] memory tickCumulatives, ) = IUniswapV3Pool(_uniswapV3Pool)
@> .observe(secondsAgo);
// tick(imprecise as it's an integer) to sqrtPriceX96
return
TickMath.getSqrtRatioAtTick(
int24(
(tickCumulatives[1] - tickCumulatives[0]) /
int56(int32(_interval))
)
);
}
But Uniswap V3 pools are initialized with an observationCardinality
equals to 1 and therefore the pool will return only the most recent observation.
The protocol should call the IUniswapV3Pool.increaseObservationCardinalityNext(uint16 observationCardinalityNext)
function with a value high enough to cover the observation interval.