Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ambitious Cedar Monkey - getTWAPPriceInEth () will return incorrect price for negative ticks cause it doesn't round up for negative ticks. #241

Open
sherlock-admin2 opened this issue Dec 31, 2024 · 0 comments
Labels
Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed

Comments

@sherlock-admin2
Copy link
Contributor

Ambitious Cedar Monkey

High

getTWAPPriceInEth () will return incorrect price for negative ticks cause it doesn't round up for negative ticks.

Summary

Negative ticks when fetching the price from the Uniswap pool are not rounded towards 0, which will give a higher price for pools using TWAP.

For reference here is the Uniswap implementation which uses the correct approach.

As result, in case if int24(tickCumulatives[1] - tickCumulatives[0]) is negative and (tickCumulatives[1] - tickCumulatives[0]) % secondsAgo != 0, then returned tick will be bigger then it should be, which opens possibility for some price manipulations and arbitrage opportunities.

Root Cause

The problem is that due to the implementation of getV3SqrtPriceAvg() function, wrong prices are prone to be returned leading to the use of wrong pricing protocol wide.

Moreso, the getTWAPPriceInEth() function is used extensively for TWAP pricing in the protocol

https://github.com/sherlock-audit/2024-12-numa-audit/blob/main/Numa/contracts/NumaProtocol/NumaOracle.sol#L226-L247

File: NumaOracle.sol
095:     function getTWAPPriceInEth(
096:         address _numaPool,
097:         address _converter,
098:         uint _numaAmount,
099:         uint32 _interval
100:     ) external view returns (uint256) {
101:  @>     uint160 sqrtPriceX96 = getV3SqrtPriceAvg(_numaPool, _interval);


226:     function getV3SqrtPriceAvg(
227:         address _uniswapV3Pool,
228:         uint32 _interval
229:     ) public view returns (uint160) {
230:         require(_interval > 0, "interval cannot be zero");
231:         //Returns TWAP prices for short and long intervals
232:         uint32[] memory secondsAgo = new uint32[](2);
233:         secondsAgo[0] = _interval; // from (before)
234:         secondsAgo[1] = 0; // to (now)
235: 
236:         (int56[] memory tickCumulatives, ) = IUniswapV3Pool(_uniswapV3Pool)
237:             .observe(secondsAgo);
238: 
239:         // tick(imprecise as it's an integer) to sqrtPriceX96
240:         return
241:             TickMath.getSqrtRatioAtTick(
242:                 int24(
243:      @>             (tickCumulatives[1] - tickCumulatives[0]) /
244:                         int56(int32(_interval))
245:                 )
246:             );
247:     }

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

If tickCumulatives[1] - tickCumulatives[0] is negative and (tickCumulatives[1] - tickCumulatives[0]) % int56(int32(_interval) != 0, then returned tick will be bigger than it should be this returning a wrong price.
This pricing is used for evaluations and state update extensively throughout the protocol

PoC

No response

Mitigation

Consider modifying the function as shown below

File: NumaOracle.sol
226:     function getV3SqrtPriceAvg(
227:         address _uniswapV3Pool,
228:         uint32 _interval
229:     ) public view returns (uint160) {
230:         require(_interval > 0, "interval cannot be zero");
231:         //Returns TWAP prices for short and long intervals
232:         uint32[] memory secondsAgo = new uint32[](2);
233:         secondsAgo[0] = _interval; // from (before)
234:         secondsAgo[1] = 0; // to (now)
235: 
236:         (int56[] memory tickCumulatives, ) = IUniswapV3Pool(_uniswapV3Pool)
237:             .observe(secondsAgo);
238: 
239:         // tick(imprecise as it's an integer) to sqrtPriceX96
+              int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint32(_interval)));
+              if (tickCumulatives[1] - tickCumulatives[0] < 0 && (tickCumulatives[1] - tickCumulatives[0]) % secondsAgo != 0) tick--;
240:         return
241:             TickMath.getSqrtRatioAtTick(
242:                 int24(
+                             tick
-243:                     (tickCumulatives[1] - tickCumulatives[0])
-244:                         int56(int32(_interval))
245:                 )
246:             );
247:     }
@sherlock-admin3 sherlock-admin3 added Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed labels Jan 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed
Projects
None yet
Development

No branches or pull requests

2 participants