sherlock-audit/2024-04-teller-finance-judging

pkqs90 - `LenderCommitmentGroup_Smart#getSqrtTwapX96` may revert due to not enough cardinality in UniswapV3 pool

Closed this issue · 1 comments

pkqs90

medium

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.

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 creating LenderCommitmentGroup_Smart pool doesn't set a twapInterval too large.

Invalid, market creators input error when creating new markets and setting twap interval.