twapFilter()
would return the wrong prices for negative tick deltas since it doesn't round up for them
#195
Labels
bug
Something isn't working
downgraded by judge
Judge downgraded the risk level of this issue
edited-by-warden
grade-a
primary issue
Highest quality submission among a set of duplicates
QA (Quality Assurance)
Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
🤖_195_group
AI based duplicate group recommendation
sponsor disputed
Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue
Lines of code
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1450-L1452
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1026
(https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1200
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1231-L1238
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1250-L1262
Vulnerability details
Proof of Concept
First take a look at https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1450-L1452
This function is queried to get he TWAP price, using 10 minutes as the TWAP duration.
Now consider the implementation of
PanopticMath.twapFilter()
at https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/libraries/PanopticMath.sol#L241-L268This function is used to compute the twap of a Uniswap V3 pool using data from its oracle, and then it returns the final calculated twap tick as the median price, but multiple data in the array could be flawed because current implementation deviates from Uniswap's.
Considering the native implementation in Uniswap), we can see that the differences between the tickCummulatives is considered the
tickCummulativeDelta
, and in a case where this delta is less than0
, a check is implemented to see if the delta is directly divisible by the twap duration, if not the tick calculated is rounded down, i.e:if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;
Now note that unlike the original uniswap implementation, here the delta of the tick cummulatives is being calculated in a different manner, i.e Panoptic implements
(tickCumulatives[i] - tickCumulatives[i + 1])
instead oftickCumulatives[1] - (tickCumulatives[0]
which is cause here oursecondsAgos[]
array is ascending i.esecondsAgos[0] < secondsAgos[1] < secondsAgos[5] < secondsAgos[10]< secondsAgos[20]
, unlike in the Uniswap OracleLibrary where it's descending, albeit only two element are present in the arraysecondsAgos[0] = secondsAgo; & secondsAgos[1] = 0;
i.esecondsAgos[0] > secondsAgos[1]
so everything checks out and the tick deltas are calculated accurately, i.e in our casetickCumulativesDelta = (tickCumulatives[i] - tickCumulatives[i + 1])
is valid.As a result, in Panoptic's current implementation case, if
int24(tickCumulatives[i] - tickCumulatives[i + 1])
is negative andint24(tickCumulatives[i] - tickCumulatives[i + 1]) % int56(uint56(twapWindow / 20) != 0
then the returned tick will be bigger then it should be, which opens possibility for some price manipulations and arbitrage opportunities.Remember that this function is directly queried via
getUniV3TWAP()
, which is used in both liquidations and whenever force exercising a single position.Impact
If
int24(tickCumulatives[i] - tickCumulatives[i + 1])
is negative andint24(tickCumulatives[i] - tickCumulatives[i + 1]) % int56(uint56(twapWindow / 20) != 0
, then the returned tick will be bigger than it should be which places protocol wanting prices to be right not be able to achieve this goal, note that Panoptic relies on the price attached to this to be valid and uses them directly while liquidating and whenever force exercising a single position, having the wrong price being returned easily causes reverts on valid attempts to liquidate since this check inliquidate()
could revert could could fail, or the calculation of the fee for force exercising the single position would be off than it should be and this could even cause protocol to assume a wrong amount to be redistributed which is used while settling the differences between delegated amounts and the refund amountsTool used
Recommended Mitigation Steps
Add this line to
PanopticMath.twapFilter()
afterif (int24(tickCumulatives[i] - tickCumulatives[i + 1]) < 0) && ( int24(tickCumulatives[i] - tickCumulatives[i + 1]) % int56(uint56(twapWindow / 20) != 0) twapMeasurement[i] --;
.Assessed type
Oracle
The text was updated successfully, but these errors were encountered: