Lucene search

K
code423n4Code4renaCODE423N4:2023-10-CANTO-FINDINGS-ISSUES-257
HistoryOct 06, 2023 - 12:00 a.m.

Lack of validation allows invalid ticks, impacting data integrity.

2023-10-0600:00:00
Code4rena
github.com
2
validation
crossticks
data integrity
pool id
github
tick tracking
nonsensical state

7 High

AI Score

Confidence

Low

Lines of code
<https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L29-L31&gt;
<https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L32-L35&gt;

Vulnerability details

Impact

Function crossTicks updates the tick tracking data when ticks are crossed, but does not validate that exitTick and entryTick are valid and make sense. For example, exitTick could be lower than entryTick.

Proof of Concept

Here is the relevant code in the crossTicks function.

function crossTicks(
        bytes32 poolIdx,
        int24 exitTick,  
        int24 entryTick
    ) internal {

  uint256 numElementsExit = tickTracking_[poolIdx][exitTick].length;
  
  tickTracking_[poolIdx][exitTick][numElementsExit - 1] 
            .exitTimestamp = uint32(block.timestamp);
            
  StorageLayout.TickTracking memory tickTrackingData = StorageLayout
            .TickTracking(uint32(block.timestamp), 0);
            
  tickTracking_[poolIdx][entryTick].push(tickTrackingData);
  
}

The parameters are:

  • exitTick: The tick that is being exited
  • entryTick: The tick that is being entered

The issue is that there is no validation on the relationship between exitTick and entryTick.

Specifically, the exitTick data is updated without checking:

  • exitTick is lower than entryTick (nonsensical state)
  • exitTick and entryTick are valid ticks for the pool

This lack of validation could allow the tick tracking data structure to enter an inconsistent state if exitTick is lower than entryTick.

> You can see the crossTicks function is used to update the tick tracking data when ticks are crossed by a position. It is defined as:

function crossTicks(
  bytes32 poolIdx,
  int24 exitTick,
  int24 entryTick  
) internal {

  // ...

}

The key parameters are:

  • poolIdx: The pool ID
  • exitTick: The tick that is being exited
  • entryTick: The tick that is being entered

When a position transitions from one tick range to another, this function gets called to record the tick crossing in the tracking data structure.

Within the function, it does the following:

  1. Updates the exitTimestamp for the exitTick, marking when it was deactivated on Line29-Line31

    uint256 numElementsExit = tickTracking_[poolIdx][exitTick].length;

    tickTracking_[poolIdx][exitTick][numElementsExit - 1]
    .exitTimestamp = uint32(block.timestamp);

  2. Initializes tick tracking data for the new entryTick on line32-34

    StorageLayout.TickTracking memory tickTrackingData = StorageLayout
    .TickTracking(uint32(block.timestamp), 0);

    tickTracking_[poolIdx][entryTick].push(tickTrackingData);

You can see the issue here is there is no validation on the relationship between exitTick and entryTick.

Some examples of invalid states that could occur:

  • exitTick is lower than entryTick (nonsensical ordering)
  • exitTick or entryTick are outside the min/max bounds for the pool
  • exitTick and entryTick are the same value

This means the tick tracking structure could end up in an inconsistent state if invalid tick values are passed in.

Later accounting logic relies on the integrity of this data structure to calculate rewards properly. Invalid data could lead to incorrect reward calculations.

> A proof of concept that demonstrates passing invalid ticks into the crossTicks function:

contract Exploit {

  address constant pool = 0x...; // some pool address

  function exploitCrossTicks() external {

    IUniswapV3Pool poolContract = IUniswapV3Pool(pool);

    bytes32 poolKey = poolContract.poolId();
    
    // Let's say this pool has a min tick of -500 and max tick of 500
    
    // First initialize some valid tick tracking
    LiquidityMining(pool).initTickTracking(poolKey, -400); 
    LiquidityMining(pool).initTickTracking(poolKey, 100);

    // Now pass an invalid lower exit tick
    LiquidityMining(pool).crossTicks(poolKey, -600, 100);
    
    // This will incorrectly update the tick tracking for -600
    // Despite -600 being below the min tick for the pool (-500)

    // Result: Invalid data now exists in the tick tracking structure

  }

}

The points in this demostration:

  • Initialize some valid tick tracking data
  • Call crossTicks with exitTick = -600 and entryTick = 100
  • -600 is below the hypothetical min tick, so is invalid
  • Yet crossTicks still updates the tracking data for this invalid tick
  • Resulting in corrupt, inconsistent state due to lack of validation

This shows how failing to check tick parameters in crossTicks can lead to invalid data in the contract’s data structures.

Tools Used

Vs

Recommended Mitigation Steps

To fix this, we should add proper validation, e.g.:

require(exitTick &gt; entryTick, "Invalid ticks");
require(exitTick &gt;= minTick && exitTick &lt; maxTick, "Invalid exit tick");
require(entryTick &gt;= minTick && entryTick &lt; maxTick, "Invalid entry tick");

Adding checks like this ensures the tick tracking structure maintains data integrity when ticks are crossed.

Assessed type

Invalid Validation


The text was updated successfully, but these errors were encountered:

All reactions

7 High

AI Score

Confidence

Low