LenderCommitmentGroup_Smart#getSqrtTwapX96 may revert due to not enough cardinality in UniswapV3 pool
Summary
When fetching the price in UniswapV3, it uses Pool.observe(secondsAgo). However, if secondsAgo is too large compared to the cardinality of the pool oracle, this would always end up in a revert.
Vulnerability Detail
Each slot in UniV3 oracle stores the last updated price of a block. If the cardinality is N, the least amount of time it supports querying is N * 12 seconds (blocktime on mainnet). Since the contract also deploys on Arbitrum, the blocktime is even shorter (0.26s).
This means that the larger twapInterval of the LenderCommitmentGroup_Smart pool is set, the larger cardinality it would require. However, currently there is no check of cardinality of the UniV3 pool, which means it may be likely for getSqrtTwapX96 to fail.
A recommended way is to call univ3Pool.increaseObservationCardinalityNext to increase cardinality during initialization, and add some documentation so that users creating LenderCommitmentGroup_Smart pool doesn't set a twapInterval too large.
function getSqrtTwapX96(uint32 twapInterval)
public
view
returns (uint160 sqrtPriceX96)
{
if (twapInterval == 0) {
// return the current price if twapInterval == 0
(sqrtPriceX96, , , , , , ) = IUniswapV3Pool(UNISWAP_V3_POOL)
.slot0();
} else {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapInterval; // from (before)
secondsAgos[1] = 0; // to (now)
> (int56[] memory tickCumulatives, ) = IUniswapV3Pool(UNISWAP_V3_POOL)
> .observe(secondsAgos);
// tick(imprecise as it's an integer) to price
sqrtPriceX96 = TickMath.getSqrtRatioAtTick(
int24(
(tickCumulatives[1] - tickCumulatives[0]) /
int32(twapInterval)
)
);
}
}
/// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos`
> /// @dev Reverts if `secondsAgos` > oldest observation
/// @param self The stored oracle array
/// @param time The current block.timestamp
/// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation
/// @param tick The current tick
/// @param index The index of the observation that was most recently written to the observations array
/// @param liquidity The current in-range pool liquidity
/// @param cardinality The number of populated elements in the oracle array
/// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo`
/// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo`
function observe(
Observation[65535] storage self,
uint32 time,
uint32[] memory secondsAgos,
int24 tick,
uint16 index,
uint128 liquidity,
uint16 cardinality
) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) {
require(cardinality > 0, 'I');
tickCumulatives = new int56[](secondsAgos.length);
secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length);
for (uint256 i = 0; i < secondsAgos.length; i++) {
(tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle(
self,
time,
secondsAgos[i],
tick,
index,
liquidity,
cardinality
);
}
}
Impact
getSqrtTwapX96 may revert due to not enough cardinality, blocking loan creations.
A recommended way is to call univ3Pool.increaseObservationCardinalityNext to increase cardinality during initialization, and add some documentation so that users creating LenderCommitmentGroup_Smart pool doesn't set a twapInterval too large.
pkqs90
medium
LenderCommitmentGroup_Smart#getSqrtTwapX96
may revert due to not enough cardinality in UniswapV3 poolSummary
When fetching the price in UniswapV3, it uses
Pool.observe(secondsAgo)
. However, ifsecondsAgo
is too large compared to the cardinality of the pool oracle, this would always end up in a revert.Vulnerability Detail
Each slot in UniV3 oracle stores the last updated price of a block. If the cardinality is N, the least amount of time it supports querying is
N * 12 seconds (blocktime on mainnet)
. Since the contract also deploys on Arbitrum, the blocktime is even shorter (0.26s).This means that the larger
twapInterval
of theLenderCommitmentGroup_Smart
pool is set, the larger cardinality it would require. However, currently there is no check of cardinality of the UniV3 pool, which means it may be likely forgetSqrtTwapX96
to fail.A recommended way is to call
univ3Pool.increaseObservationCardinalityNext
to increase cardinality during initialization, and add some documentation so that users creatingLenderCommitmentGroup_Smart
pool doesn't set atwapInterval
too large.Impact
getSqrtTwapX96
may revert due to not enough cardinality, blocking loan creations.Code Snippet
Tool used
Manual review.
Recommendation
A recommended way is to call
univ3Pool.increaseObservationCardinalityNext
to increase cardinality during initialization, and add some documentation so that users creatingLenderCommitmentGroup_Smart
pool doesn't set atwapInterval
too large.