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

DenTonylifer - Function getSqrtTwapX96 doesn't round to negative infinity for negative ticks

Closed this issue · 4 comments

DenTonylifer

medium

Function getSqrtTwapX96 doesn't round to negative infinity for negative ticks

Summary

Function getSqrtTwapX96() doesn't round to negative infinity for negative ticks and may return wrong sqrt price.

Vulnerability Detail

Function getSqrtTwapX96() is used for calculating sqrt price based on tickCumulatives[] array from IUniswapV3Pool.observe(). The problem arises in these lines:

sqrtPriceX96 = TickMath.getSqrtRatioAtTick(  
                int24(
                    (tickCumulatives[1] - tickCumulatives[0]) / 
                        int32(twapInterval)
                )
            );

They differ from Uniswap V3 implementation. When the UniV3Oracle calls OracleLibrary.consult(), it returns the arithmeticMeanTick. This value is rounded DOWN to the nearest tick. This is because, in most use cases, the price being returned by an oracle is used to determine the value of an asset to be used for valuing collateral, where the caller is the one whose collateral is on the line, and it is crucial to ensure that user assets are not overvalued so as to give them an edge.
The above-mentioned lines of code are exactly necessary for the calculation of required collateral amount in acceptFundsForAcceptBid() function.
The problem is that in case if int24(tickCumulatives[1] - tickCumulatives[0]) is negative, then the tick should be rounded down as it's done in the uniswap library. In this case returned tick will be bigger then it should be.

Impact

Incorrect calculation of requiredCollateral may force acceptFundsForAcceptBid() function revert.

Code Snippet

https://github.com/sherlock-audit/2024-04-teller-finance/blob/defe55469a2576735af67483acf31d623e13592d/teller-protocol-v2-audit-2024/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol#L363
https://github.com/sherlock-audit/2024-04-teller-finance/blob/defe55469a2576735af67483acf31d623e13592d/teller-protocol-v2-audit-2024/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol#L580-L594

Tool used

Manual Review

Recommendation

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

+          int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
+          int24 arithmeticMeanTick = int24(tickCumulativesDelta / twapInterval);
             //// Always round to negative infinity
+          if (tickCumulativesDelta < 0 && (tickCumulativesDelta % twapInterval != 0)) arithmeticMeanTick--;
            // tick(imprecise as it's an integer) to price
-            sqrtPriceX96 = TickMath.getSqrtRatioAtTick(  
-                int24(
-                    (tickCumulatives[1] - tickCumulatives[0]) / 
-                        int32(twapInterval)
-                )
+          sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);

The protocol team fixed this issue in the following PRs/commits:
teller-protocol/teller-protocol-v2-audit-2024#28

I believe this is low severity per similar issue seen here. There is extremely minor difference in value (at most 0.1%)

@nevillehuang
The price difference depends on the pool's liquidity in each tick. Thus, the severity of the issue should be reconsidered:

  • For less liquid pairs it can be much more than 0.1%.
  • Teller finance supports any ERC20 token making it more likely to be affected by this mispricing.

For these reasons I believe this is a valid Medium.

The Lead Senior Watson signed off on the fix.