Platform: Code4rena
Start Date: 03/10/2023
Pot Size: $24,500 USDC
Total HM: 6
Participants: 62
Period: 3 days
Judge: LSDan
Total Solo HM: 3
Id: 288
League: ETH
Rank: 10/62
Findings: 2
Award: $364.87
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Banditx0x
Also found by: 0xDING99YA, 0xWaitress, 0xpiken, 3docSec, Banditx0x, adriro, emerald7017, maanas, twicek
359.9307 USDC - $359.93
accrueConcentratedPositionTimeWeightedLiquidity() will iterate every single tick of a user's position. Since that total tick number can be large, this function can encounter a out of gas issue and users may not be able to claim the rewards properly.
We can take a look at the for loop in accrueConcentratedPositionTimeWeightedLiquidity():
for (int24 i = lowerTick + 10; i <= upperTick - 10; ++i) { uint32 tickTrackingIndex = tickTrackingIndexAccruedUpTo_[poolIdx][posKey][i]; uint32 origIndex = tickTrackingIndex; uint32 numTickTracking = uint32(tickTracking_[poolIdx][i].length); uint32 time = lastAccrued;
We can see that for each of the single tick it will performs at least 2 storage load operations (reading tickTrackingAccruedUpTo_ mapping and tickTracking_ mapping), and both SLOAD are cold storage read and will cost lots of gas. Now we consider the total tick number of a user's position, assuming a user provides range between price [2.718 (tick = 10000), 54.587 (tick = 40000)], this is a reasonable price range of user liquidity position. Combined those this function can easily cost multi millions of gas.
What's worse, if tickTracking_[poolIdx[[I].length is not zero, and a user hasn't called this function for a while and during that time the tick i is crossed lots of time, another large cost of gas will be added.
So in conclusion, the inefficiency may result in the out of gas and the function cannot work properly. The scenario mentioned above seems normal and not in extreme condition, and accrueConcentratedPositionTimeWeightedLiquidity() function will be called when claiming rewards, so I think it can be a high issue.
Manual Review
Instead of iterate over every single tick of a user position, allowing the user to provide a custom lower tick and upper tick range to update their time-weighted liquidity and checks if that range is a subset of the users liquidity position. In that case user can avoid out of gas error.
DoS
#0 - c4-pre-sort
2023-10-08T12:04:21Z
141345 marked the issue as duplicate of #114
#1 - c4-pre-sort
2023-10-09T16:41:20Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-18T19:29:32Z
dmvt marked the issue as satisfactory
🌟 Selected for report: adriro
Also found by: 0x3b, 0xAadi, 0xDING99YA, 0xTheC0der, 0xWaitress, 0xdice91, 100su, 3docSec, BRONZEDISC, BoRonGod, Eurovickk, GKBG, HChang26, IceBear, JP_Courses, MatricksDeCoder, Mike_Bello90, SovaSlava, Topmark, albahaca, cookedcookee, gzeon, hunter_w3b, kutugu, lukejohn, marqymarq10, matrix_0wl, orion, pep7siup, radev_sw, sces60107, taner2344, tpiliposian, wahedtalash77, xAriextz, zpan
4.9369 USDC - $4.94
Time updates in accrueConcentratedPositionTimeWeightedLiquidity() is inaccurate in certain conditions, this may cause some unexpected result like one more run of a loop
if (tickTracking.exitTimestamp < nextWeek) { // Exit was in this week, continue with next tick tickActiveEnd = tickTracking.exitTimestamp; tickTrackingIndex++; dt = tickActiveEnd - tickActiveStart; }
In this if block, the dt is updated as tickActiveEnd - tickActiveStart, and dt will be later added to "time".
time += dt;
This is meant to update time to the tickActiveEnd, however, since tickActiveStart is not always equal to time, time may be updated to a value less than tickActiveEnd. For example, tickTracking.enterTimestamp = 90 and time = 10, tickTracking.exitTimestamp = 100, nextWeek = 604800, in this case since 100 < 604800, dt will be 100 - 90 = 10, and time will be 10 + 10 = 20, while it should be updated to 100, which is the exitTimestamp.
In the next run within the loop the time will be updated, however, that will cost one more run.
Manual Review
change
if (tickTracking.exitTimestamp < nextWeek) { // Exit was in this week, continue with next tick tickActiveEnd = tickTracking.exitTimestamp; tickTrackingIndex++; dt = tickActiveEnd - tickActiveStart; }
to
if (tickTracking.exitTimestamp < nextWeek) { // Exit was in this week, continue with next tick tickActiveEnd = tickTracking.exitTimestamp; tickTrackingIndex++; dt = tickActiveEnd - time; }
#0 - c4-pre-sort
2023-10-09T17:21:13Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-18T23:09:26Z
dmvt marked the issue as grade-b