Canto Liquidity Mining Protocol - twicek's results

Execution layer for original work.

General Information

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

Canto

Findings Distribution

Researcher Performance

Rank: 13/62

Findings: 1

Award: $359.93

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Banditx0x

Also found by: 0xDING99YA, 0xWaitress, 0xpiken, 3docSec, Banditx0x, adriro, emerald7017, maanas, twicek

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
upgraded by judge
edited-by-warden
duplicate-114

Awards

359.9307 USDC - $359.93

External Links

Lines of code

https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L94-L133

Vulnerability details

Proof of Concept

If a user neither modifies his position nor claims rewards for a very long time, it might become impossible for him to do any action involving the internal functions accrueAmbientPositionTimeWeightedLiquidity or accrueConcentratedPositionTimeWeightedLiquidity. accrueConcentratedPositionTimeWeightedLiquidity is the more likely to generate this issue:

LiquidityMining.sol#L94-L133

                while (time < block.timestamp && tickTrackingIndex < numTickTracking) {
                    TickTracking memory tickTracking = tickTracking_[poolIdx][i][tickTrackingIndex];
                    uint32 currWeek = uint32((time / WEEK) * WEEK);
                    uint32 nextWeek = uint32(((time + WEEK) / WEEK) * WEEK);
                    uint32 dt = uint32(
                        nextWeek < block.timestamp
                            ? nextWeek - time
                            : block.timestamp - time
                    );
                    uint32 tickActiveStart; // Timestamp to use for the liquidity addition
                    uint32 tickActiveEnd;
                    if (tickTracking.enterTimestamp < nextWeek) {
                        // Tick was active before next week, need to add the liquidity
                        if (tickTracking.enterTimestamp < time) {
                            // Tick was already active when last claim happened, only accrue from last claim timestamp
                            tickActiveStart = time;
                        } else {
                            // Tick has become active this week
                            tickActiveStart = tickTracking.enterTimestamp;
                        }
                        if (tickTracking.exitTimestamp == 0) {
                            // Tick still active, do not increase index because we need to continue from here
                            tickActiveEnd = uint32(nextWeek < block.timestamp ? nextWeek : block.timestamp);
                        } else {
                            // Tick is no longer active
                            if (tickTracking.exitTimestamp < nextWeek) {
                                // Exit was in this week, continue with next tick
                                tickActiveEnd = tickTracking.exitTimestamp;
                                tickTrackingIndex++;
                                dt = tickActiveEnd - tickActiveStart;
                            } else {
                                // Exit was in next week, we need to consider the current tick there (i.e. not increase the index)
                                tickActiveEnd = nextWeek;
                            }
                        }
                        timeWeightedWeeklyPositionInRangeConcLiquidity_[poolIdx][posKey][currWeek][i] +=
                            (tickActiveEnd - tickActiveStart) * liquidity;
                    }
                    time += dt;
                }

In this function, a for loop iterates over each tick, and for each tick a while loop is done for each tick tracking index to calculate its contribution. A tick could have a very large amount of tick tracking indexes to loop over in its while loop if it has not been updated for a very long time for the user position.

Example:

Each time a tick is crossed it will have a new item in its tickTracking_ array. If a user does not change his position for let's say 1 year, hundreds, if not thousands, of entries could be added to tickTracking_. A year later, the user function call might need to do more than 21 (ticks) * 1000 (number of tick tracking items not accrued) = 21000 total iterations for the for + while loop in accrueConcentratedPositionTimeWeightedLiquidity. The cost for such a function call could surpass the 30M gas limit.

Impact

The user funds will be locked in the contract as he will not be able to call the mint or burn function as well as not being able to claim rewards.

Tool used

Manual Review

Consider splitting the accrual in several calls with a dedicated function or create an admin function that updates inactive accounts before they surpass the 30M gas limit.

Assessed type

DoS

#0 - c4-pre-sort

2023-10-08T12:07:05Z

141345 marked the issue as duplicate of #114

#1 - c4-pre-sort

2023-10-09T16:41:49Z

141345 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-18T19:27:43Z

dmvt changed the severity to 3 (High Risk)

#3 - c4-judge

2023-10-18T19:29:38Z

dmvt marked the issue as satisfactory

#4 - radeveth

2023-10-21T10:58:27Z

NC-02 and NC-05 issues from QA Report #297, also address potential gas issues and the risk of running out of gas for the accrueAmbientPositionTimeWeightedLiquidity() function. Therefore, I strongly believe that the NC-02 and NC-05 issues from QA Report #297 should be marked as duplicates of these issues, particularly #114.

Could you please review this?


Note: L-01 from #61 is also marked as duplicate of issue #114.


@dmvt @141345

#5 - dmvt

2023-10-21T22:39:13Z

NC-02 and NC-05 issues from QA Report #297, also address potential gas issues and the risk of running out of gas for the accrueAmbientPositionTimeWeightedLiquidity() function. Therefore, I strongly believe that the NC-02 and NC-05 issues from QA Report #297 should be marked as duplicates of these issues, particularly #114.

Could you please review this?

Note: L-01 from #61 is also marked as duplicate of issue #114.

@dmvt @141345

NC-2 is a different issue and is actually incorrect. The function in question is protected by governance. N-5 is not of sufficient quality to be upgraded to a high risk issue.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter