-
Notifications
You must be signed in to change notification settings - Fork 4
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
Wrong Median Cumulative TWAP Tick Price in situation of Series of High Price Volatilities in Pool #239
Comments
Picodes marked the issue as primary issue |
Not sure what the specific issue/impact is with not including the latest 30 seconds in the TWAP? That price is over the past 10 minutes and it's supposed to lag price changes a little, so 30 seconds doesn't make much of a difference. |
As we're taking the median it indeed doesn't change much, so I'll downgrade to Low for "function incorrect as to spec, issues with comments" |
Picodes changed the severity to QA (Quality Assurance) |
Thanks to Judge for how this issue is handled and also to Sponsor opinion on this issue which has made it seems that this report is not indeed qualified as high severity since it does not have a high likelihood since we are taking the median as noted by Judge and sponsor, however would like to argue that this vulnerability qualifies as medium severity for two reasons :
|
@Topmark1 I respectfully disagree. Fundamentally, nothing is broken here if you correct the documentation accordingly. Indeed there are some cases where the median between the prices starting at 0 and the price starting at 30 sec is not the same, but the main functionality of the twap oracle is to have a robust, slightly lagging oracle returning the asset price. Then the rest is a matter of documentation and oracle definition. |
Lines of code
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1450
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/libraries/PanopticMath.sol#L249-L253
Vulnerability details
Impact
Based on Protocol comment description at L249-L253 of PanopticMath Library :
We instead observe the average price over a series of time intervals, and define the TWAP as the median of those averages.
The problem is that Wrong Cumulative median TWAP tick Price will be returned in situation of series of high Price Volatility which would cause value loss to protocol and poor price integrity in contract
Denial of Service when twapTick price is within the allowed ranged to perform Liquidation due to it usage in validation at L1035 of the Panoptic Pool contract. More details of this second impact would be elaborated during the Scenario Proof of this report
Proof of Concept
The function provided above from the PanopticPool pool contract shows how TWAP price is derived by calling the twapFilter(...) function in the PanopticMath library, it computes price average based on different points from the last 600s, then a median Price is selected in other to prevent price manipulation as much as possible.
A look at the implementation of twapFilter(...) function provided below from the PanopticMath library and as noted from the first pointer, it can be seen that secondsAgos array is filled up by dividing twapWindow i.e 600, into sections of time intervals. Based on the implementation secondsAgos array would be equivalent to [30, 60, 90, ..., 570, 600], The period in between these different time slots i.e 30 to 60, 60 to 90, 90 to 120 etc is then used to derive tickCumulatives based on the second pointer by calling uniswap observe function i.e univ3pool.observe(...).
The problem is that zero was not included in the secondsAgos array which means deriving the price tick since the last 600s would not actually include the most current price between 0 and 30 but only from 30 seconds to 600, the implication of this is that when there is high volatility in the different prices that would have had great impact on the cumulative tick prices a wrong median price would be selected as the TWAP price price to be used by Protocol.
A quote from Uniswap doc on the correct call and usage of the observe(...) function at https://docs.uniswap.org/contracts/v3/reference/core/UniswapV3Pool#observe
To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, you must call it with secondsAgos = [3600, 0].
This shows the importance of including a zero time slot value as the check of previous twapTick is not complete without starting the most current price.
The comment at description at Uniswap code also noted that "0 may be passed as `secondsAgo' to return the current cumulative values"
https://github.com/Uniswap/v3-core/blob/6562c52e8f75f0c10f9deaf44861847585fc8129/contracts/libraries/Oracle.sol#L248
To understand this report a dummy value would be used just to have an idea of how the issue would look like
Scenario Proof
Based on the description at L249-L253 of PanopticMath and the code implementation leading to L266.
Lets assume the sorted cumulative price ticks are
[ 23, 32, 45, ... , 56 , ... , 74, 78, 125]
from the sample array it can be noted that the final price median is 56.
However if the TWAP tick price from the 0 to 30 time slot that was not included happen to be 50 which would probably have been the right value to hold the median position, the correct array would have looked like this
[ 23, 32, 45, ... , 50 , ... 69, 74, 78]
this shows that the correct median value of 50 is holding it rightful place but that would not be the case as 0 - 30 time slot was left out.
To link this explanation to the second impact at L1035 of the PanopticPool contract,
the code ensures price different between TWAP and currentTick is within the allowed range, of MAX_TWAP_DELTA_LIQUIDATION i.e 513,
assuming current tick is 4900 and the TWAP tick derived is 5600, the difference is 700 which is above 513, therefore the code would would revert, assuming the correct implementation of 0-30 time slot is present and the TWAP tick derived is 5000 instead of 5600, the price difference would be just 100 which is below 513 without denial of service.
Tools Used
Manual Review, Uniswap Documentation
Recommended Mitigation Steps
If protocol must include secondsAgo 600 to the array, the array length to be checked can be increased to 21 to also include zero time stot by increasing the loop implementation. However Protocol can simply adjust the code as provided below to use time stots from 0 to 570 for secondsAgos array
Assessed type
Oracle
The text was updated successfully, but these errors were encountered: