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: 15/62
Findings: 1
Award: $359.93
🌟 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
https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L88-L137 https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L139-L149 https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L184-L186
If a liquidity provider want to provide a wide range liquidity, it could be reverted because of gas limit. Even worse, they may not able to withdraw their assets after providing liquidity successfully, and they also can not claim concentrated rewards.
As a liquidity provider, they can control over what price ranges their capital is allocated to. The price range is defined by lowerTick
and upperTick
.
TradeMatcher#mintRange()
is called to mints concernated liquidity within a range.LiquidityMining#accrueConcentratedPositionTimeWeightedLiquidity()
is called in mintRange()
to accrue the in-range time-weighted concentrated liquidity.But in function accrueConcentratedPositionTimeWeightedLiquidity()
, several loops are used, which may consume excessive gas and the function could be reverted:
https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L88-L137
https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L139-L149
It also happens in the function claimConcentratedRewards()
:
https://github.com/code-423n4/2023-10-canto/blob/main/canto_ambient/contracts/mixins/LiquidityMining.sol#L184-L186
The above codes could consume gas a lot if there is a huge gap between lowerTick
and upperTick
. We can use the codes in the function claimConcentratedRewards()
to calculate the preliminary gas consuming:
184: for (int24 j = lowerTick + 10; j <= upperTick - 10; ++j) { 185: inRangeLiquidityOfPosition += timeWeightedWeeklyPositionInRangeConcLiquidity_[poolIdx][posKey][week][j]; 186: }
Suppose that lowerTick
is 275990, upperTick
is 278010, then the loop will be executed 2000 times.
Line 185 is reading a uint256
value from storage, which costs 2100 gas. Just storage reading in the loop requires at least 4,200,000.
If the liquidity provider chooses a wider price range, the transaction is very likely to be reverted because the gas consumption could be easy to reach block gas limit.
It's easy to verify this vulnerability using TestLiquidityMining.js:
210: currentTick - 1500, // tickLower @audit-info change tickLower 211: currentTick + 1500, // tickUpper @audit-info change tickUpper
Minting concentrated liquidity will be reverted.
6,000,000
to 30,000,000
and test again:219: tx = await dex.userCmd(2, mintConcentratedLiqCmd, { 220: gasLimit: 30000000, 221: value: ethers.utils.parseUnits("10", "ether"), 222: });
The transaction will be executed successfully because the gas limit is raised.
Manual review
Consider refactoring the code to make sure the gas consumption will not fluctuate greatly due to changes in lowerTick
and upperTick
, in particular, avoid large amounts of storage reading or writing operations.
DoS
#0 - c4-pre-sort
2023-10-08T11:34:45Z
141345 marked the issue as duplicate of #114
#1 - c4-pre-sort
2023-10-09T16:40:36Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-18T19:29:19Z
dmvt marked the issue as satisfactory