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: 14/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
Tick arrays like tickTracking_
can grow unbounded. Could hit gas limit.
As ticks are crossed, new TickTracking
elements are pushed:
function crossTicks( bytes32 poolIdx, int24 exitTick, int24 entryTick ) internal { tickTracking_[poolIdx][exitTick].push( TickTracking(block.timestamp, 0) ); tickTracking_[poolIdx][entryTick].push( TickTracking(block.timestamp, 0) ); }
There is no limit on the size of the tickTracking_
arrays. They can grow indefinitely as ticks are crossed.
This could eventually cause the contract to hit the Ethereum block gas limit by having extremely large storage arrays.
function crossTicks: https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L24-L35
The TickTracking
arrays are appended to whenever a tick is crossed in the crossTicks()
function:
function crossTicks(bytes32 poolId, int24 exitTick, int24 entryTick) public { tickTracking_[poolId][exitTick].push( TickTracking(block.timestamp, 0) ); tickTracking_[poolId][entryTick].push( TickTracking(block.timestamp, 0) ); }
The core issue is that there is no limit on the size of these tickTracking_
arrays.
Each time crossTicks()
is called, the array grows larger. There is no max length check.
Over time as ticks are crossed, the arrays could grow to enormous sizes, consuming large amounts of storage.
For example, if a pool has 2000 total ticks, and each tick is crossed 10 times per day, the tickTracking_
arrays would increase by 40,000 elements per day.
In a year that would be over 14 million elements, which could easily exceed the Ethereum block gas limit.
This is a scenario of how unbounded tickTracking_
growth could cause issues is very useful.
contract TickDoS { LiquidityMining liquidityMining; constructor(address _liquidityMining) { liquidityMining = LiquidityMining(_liquidityMining); } function exploitUnboundedTickArrays() public { bytes32 poolId = 0x1234; for(uint i = 0; i < 50000; i++) { liquidityMining.crossTicks( poolId, int24(i), // Exit tick int24(i + 1) // Entry tick ); } } }
This contract repeatedly calls the crossTicks()
function, passing incremental ticks as the price "walks" up.
Since tickTracking_
arrays grow unbounded, this will quickly increase the array size to extreme lengths.
For example, with only 50,000 ticks crossed, the tickTracking_
arrays would contain over 100,000 elements.
At 8000 array entries per 1M gas, this would already exceed the 15M block gas limit.
And arrays would continue growing indefinitely as crossTicks()
is called, eventually making the contract unusable due to gas costs.
Manual
Adding a limit like:
tickTracking_[poolIdx][tick].length <= MAX_TICK_TRACKING_LENGTH
Could help prevent arrays growing too large.
The unbounded growth of the tickTracking_ arrays is a potential risk.
Or
Introducing a max length check like:
require( tickTracking_[poolId][tick].length < MAX_LENGTH, "Max length exceeded" );
Would prevent this by capping the tick arrays at a reasonable size.
DoS
#0 - c4-pre-sort
2023-10-08T12:32:55Z
141345 marked the issue as duplicate of #114
#1 - c4-pre-sort
2023-10-09T16:42:15Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-18T19:29:48Z
dmvt marked the issue as satisfactory