Canto Liquidity Mining Protocol - radev_sw'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: 18/62

Findings: 2

Award: $113.58

QA:
grade-a
Analysis:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

78.3912 USDC - $78.39

Labels

bug
grade-a
QA (Quality Assurance)
sufficient quality report
edited-by-warden
Q-02

External Links

NumberIssueInstances
NC-1Overwriting set values for poolIdx and weekFrom1
NC-2Potential gas issues with setConcRewards1
NC-3Improvement in LiquidityMiningPath#claimConcentratedRewards()1
NC-4Function interruption due to "Already claimed" check2
NC-5DoS in accrueAmbientPositionTimeWeightedLiquidity()1
NC-6Skipped weeks issue in accrueAmbientPositionTimeWeightedLiquidity()1
NC-7Checks absence on lookupPosition1
NC-8Event emitting in claimAmbientRewards()1
NC-9Floating point arithmetic concern1

NC-1 Overwriting set values for poolIdx and weekFrom

There's no check to prevent overwriting already set values for a particular poolIdx and weekFrom combination in the concRewardPerWeek_ mapping. This might lead to unforeseen results and data integrity issues.

Example:

  • If concRewardPerWeek_[1][5] = 20; has already been set,
  • And the user (or governance, when the commented out permission check is enabled) calls setConcRewards with poolIdx = 1, weekFrom = 5, and weeklyReward = 10,
  • Then after the function execution, the value would indeed be overwritten to concRewardPerWeek_[1][5] = 10;. If there's a desire to prevent overwriting already set values, an additional check would need to be added, such as:
require(concRewardPerWeek_[poolIdx][weekFrom] == 0, "Reward already set for this week");

This would throw an exception and halt the function if there's already a reward set for the given pool and week.

NC-2 Potential gas issues with setConcRewards

The usage of the while loop without bounds can result in high gas consumption, making transactions expensive or even impossible to process in extreme cases. It also opens up possibilities for malicious actors to grief the contract.

Gas Griefing: A malicious actor (or even an unintentional user) could call this function with a very large range between weekFrom and weekTo, causing the function to execute the while loop an excessive number of times. This could result in a transaction that consumes a vast amount of gas, potentially causing the transaction to run out of gas or become prohibitively expensive to execute.

If there are no restrictions on who can call this function (the governance check is commented out), it could be used as an attack vector by malicious users to "grief" the contract, by repeatedly calling the function with large ranges to waste the gas of any monitoring or interacting services.

Gas Limit Issues: If the range between weekFrom and weekTo is large enough, it could exceed the block gas limit. This would make it impossible for the transaction to be processed, effectively blocking the operation.

Gas Griefing Explained:

Gas griefing is when someone purposely makes certain actions on the Ethereum network (like using a function in a smart contract) use up more gas, or computing power, than they should. This can cause problems and inconveniences.

Impacts:

  1. Costly Actions: Making an action on the network can become more expensive for users.
  2. Blocked Functions: Some actions might become impossible to do if they use up too much gas.
  3. Slower Network: Too many of these "expensive" actions can make the whole network slow down.
  4. Loss of Trust: Users might start doubting the reliability of a project if it's often targeted by gas griefing.
  5. Good for Miners, Bad for Users: Miners, who validate transactions, might earn more because of the higher fees. But this is bad for regular users who have to pay these fees.
  6. Network Traffic Jams: A lot of these "expensive" actions can cause a traffic jam on the network, making everything slow.
  7. Opens Doors for Other Attacks: Gas griefing can create opportunities for other harmful actions on the network.
  8. Extra Costs for Projects: Projects have to spend more to monitor for gas griefing and might need to fix or update their systems if attacked.

In simple words, gas griefing is like someone intentionally causing traffic jams on a highway, making journeys longer and more costly for everyone. It's essential to design roads (or in this case, smart contracts) to prevent these jams and keep everything running smoothly.

Recommendation:

  1. Add an Upper Bound: Limit the difference between weekFrom and weekTo to a maximum value, e.g.,

    require(weekTo - weekFrom <= MAX_WEEKS_RANGE, "Range too large");

    where MAX_WEEKS_RANGE is a predefined constant that sets a reasonable maximum number of weeks for which rewards can be set in a single transaction.

  2. Re-enable Governance Check: If it's intended for only certain addresses (like a governance mechanism) to call this function, ensure that you uncomment and properly configure the require check for permissions. This way, you can limit potential abuse by restricting who can set rewards.

NC-3 Improvement in LiquidityMiningPath#claimConcentratedRewards()

The direct usage of msg.sender in this function can be optimized. It's generally good practice to provide flexibility and make functions more versatile by allowing parameters instead.

NC-4 Function interruption due to "Already claimed" check

These checks, though essential for security, might end up stopping the entire function process. An alternative design should be considered, perhaps skipping the loop iteration instead of stopping the whole function.

        require(
            !concLiquidityRewardsClaimed_[poolIdx][posKey][week],
            "Already claimed"
        );
        require(
            !ambLiquidityRewardsClaimed_[poolIdx][posKey][week],
            "Already claimed"
        );

NC-5 DoS in accrueAmbientPositionTimeWeightedLiquidity()

A potential Denial of Service could occur if the difference between lastAccrued and block.timestamp is significantly large, making the function very gas-heavy.

NC-6 Skipped weeks issue in accrueAmbientPositionTimeWeightedLiquidity()

In case this function isn't called for a prolonged period, weeks in between might not be correctly accounted for, potentially resulting in inaccurate reward calculations.

NC-7 Checks absence on lookupPosition

Assuming always correct data without validation might result in unexpected behaviors. Validation checks should be added.

NC-8 Event emitting in claimAmbientRewards()

Emitting events is crucial for dApps to monitor and track. Their omission might make interaction and monitoring difficult.

NC-9 Floating point arithmetic concern

Solidity doesn't handle floating-point numbers natively. It's essential to be cautious when performing divisions to avoid precision loss.

#0 - c4-pre-sort

2023-10-09T17:21:13Z

141345 marked the issue as sufficient quality report

#1 - c4-judge

2023-10-18T23:07:44Z

dmvt marked the issue as grade-a

Findings Information

Labels

analysis-advanced
grade-b
sufficient quality report
edited-by-warden
A-03

Awards

35.1935 USDC - $35.19

External Links

Canto Analysis Report


Canto Liquidity Mining Protocol Audit

  • Purpose: Canto introduces a new liquidity mining feature specifically for Ambient Finance.

  • Implementation: This feature is a sidecar contract integrated into Ambient using their proxy contract pattern.

  • New Contracts:

    1. LiquidityMiningPath.sol: Allows user interactions.
    2. LiquidityMining.sol: Contains core logic.
  • About LiquidityMining Sidecar:

    • This sidecar is designed to implement a liquidity mining protocol for Ambient. Its goal is to incentivize liquidity for Ambient pools on Canto.
  • Incentive Mechanism:

    • Incentives focus on a width of liquidity based on the current tick, specifically ranging from currentTick-10 to currentTick+10. Users must provide liquidity within this range to qualify for rewards.
  • Rewards:

    • The protocol tracks time-weighted liquidity (both global and per-user) for various positions, helping calculate user rewards based on their contribution. Rewards are then disbursed proportionately among liquidity providers.
  • Implementation Details:

    • Reward rates are set weekly, based on a predetermined total disbursement amount. Governance determines the duration of these reward rates.
    • LiquidityMining.sol is central for rewards and needs special attention during audits.
  • Ambient Overview:

    • Previously known as CrocSwap, Ambient is a dex allowing liquidity providers to deposit either "ambient" liquidity or concentrated liquidity into token pairs.
    • CrocSwapDex is Ambient's main contract that users interact with.
    • Ambient uses modular "sidecar" contracts to manage functionalities not accommodated in CrocSwapDex due to EVM contract limit.
  • Noteworthy Sidecar Contracts:

    • BootPath: Installs other sidecars.
    • ColdPath: Creates new pools.
    • WarmPath: Manages liquidity.
    • LiquidityMiningPath: Canto's sidecar for liquidity mining.
    • KnockoutPath, LongPath, MicroPaths, SafeModePath: Handle various specific functionalities.


Examples & Scenarios

Loop Execution in LiquidityMining.sol#accrueAmbientPositionTimeWeightedLiquidity():

Let's assume:

  1. WEEK = 7 days = 604800 seconds.
  2. time (the starting point from which we are updating) = some arbitrary timestamp representing a Wednesday at noon.
  3. block.timestamp (current Ethereum block's timestamp) = a timestamp representing 9 days later, which is a Friday evening.

This means the loop will have to account for a full week from the starting Wednesday to the next Wednesday, and then an additional 2 days up to Friday evening.

Initial Data:

  • time = 1,678,688,000 (Wednesday, Noon)
  • block.timestamp = 1,693,568,000 (Friday, 9 days later)
  • liquidity = 1000 (just an arbitrary value for this example)

First Iteration:

  • currWeek = time rounded down to the last Sunday = 1,676,160,000 (Last Sunday)
  • nextWeek = currWeek + WEEK = 1,682,960,000 (Next Sunday)
  • dt = Since nextWeek (Next Sunday) is before block.timestamp (Friday, 9 days later), we'll take a full week, i.e., nextWeek - time = 1,682,960,000 - 1,678,688,000 = 4,272,000 seconds (5 days from Wednesday Noon to Sunday).
  • The liquidity for the current week (from the Last Sunday to the Next Sunday) is incremented by dt * liquidity = 4,272,000 * 1000.
  • Now, increment time by dt: time = 1,678,688,000 + 4,272,000 = 1,682,960,000 (Next Sunday).

Second Iteration:

  • currWeek = time (which is now the Next Sunday) = 1,682,960,000.
  • nextWeek = currWeek + WEEK = 1,689,760,000 (Sunday after the Next Sunday).
  • dt = Since block.timestamp (Friday, 9 days later) is before nextWeek (Sunday after the Next Sunday), we'll take the difference from the current time (Next Sunday) up to block.timestamp (Friday, 9 days later) = 1,693,568,000 - 1,682,960,000 = 10,608,000 seconds (2 days from Sunday to Tuesday).
  • The liquidity for the current week (from the Next Sunday to the following Sunday) is incremented by dt * liquidity = 10,608,000 * 1000.
  • Now, increment time by dt: time = 1,682,960,000 + 10,608,000 = 1,693,568,000 (Friday, 9 days later).

At this point, time is equal to block.timestamp, so the loop ends.

Results:

  • For the week starting at the Last Sunday, the time-weighted liquidity was incremented by the equivalent of 5 days of liquidity.
  • For the week starting at the Next Sunday, the time-weighted liquidity was incremented by the equivalent of 2 days of liquidity.

claimAmbientRewards() Function Walkthrough:

Scenario:

  1. Assume owner is 0x1234567890abcdef1234567890abcdef12345678.
  2. The poolIdx is a hypothetical index (hash) 0xabcdef1234567890.
  3. There are 2 weeks to claim in weeksToClaim array: [100, 101] (These are hypothetical week numbers).
  4. Let's assume that the block.timestamp is 102.

Step-by-Step Execution:

  1. Initialization:

    • curve is fetched from curves_ based on poolIdx.
    • The function accrueAmbientPositionTimeWeightedLiquidity() and accrueAmbientGlobalTimeWeightedLiquidity() are called.
    • The posKey (a unique identifier for the user's position) is generated using encodePosKey().
  2. Rewards Calculation:

    • Start loop for each week in weeksToClaim:

      • For week = 100:

        • Check if week + WEEK < block.timestamp. Since 100 + WEEK < 102, this is true.
        • Check if the rewards for this week have already been claimed. Assume they haven't.
        • Fetch the overall time-weighted liquidity for this week from timeWeightedWeeklyGlobalAmbLiquidity_. Let's assume this is 1000.
        • If the overall liquidity is greater than 0, calculate the rewards for the week:
          • Get the time-weighted position ambient liquidity for this week for the specific user, assume it's 10.
          • Fetch the total rewards available for the week (ambRewardPerWeek_). Let's assume it's 500.
          • Calculate rewards: (10 * 500) / 1000 = 5. Add this to rewardsToSend.
        • Mark the rewards as claimed for this week.
      • For week = 101:

        • Same steps repeated. Let's assume the user gets 4 as the reward for this week.
        • Add this to rewardsToSend. Now, rewardsToSend = 9.
  3. Sending Rewards:

    • Check if rewardsToSend is greater than 0.
    • Send the rewards to the owner. Here, 9 will be sent to the owner.

Outcome:

The owner would receive 9 as rewards for the weeks [100, 101] in our hypothetical scenario.



Questions & Notes:

During Documentation Review:

  1. Who is the intended audience for interaction with the LiquidityMining and LiquidityMiningPath contracts?
  2. What is the correct procedure to engage with Ambient Contracts?
  3. Clarification on the term "sidecar contract".

During Audit:

  1. Canto is developing a liquidity mining protocol tailored for the Ambient decentralized exchange.
  2. Differentiation between Ambient rewards and Concentrated rewards.
  3. Insight into the CurveState Struct.

Potential Attack Vectors:

  1. Possibility of liquidity providers withdrawing more rewards than they have accumulated.
  2. Scenario where liquidity providers might receive lesser rewards than they are entitled to.

Time spent:

20 hours

#0 - c4-pre-sort

2023-10-09T17:24:21Z

141345 marked the issue as sufficient quality report

#1 - c4-judge

2023-10-19T16:33:02Z

dmvt marked the issue as grade-b

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