Lines of code
<https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L29-L31>
<https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L32-L35>
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.
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:
The issue is that there is no validation on the relationship between exitTick and entryTick.
Specifically, the exitTick data is updated without checking:
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:
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:
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);
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:
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:
This shows how failing to check tick parameters in crossTicks can lead to invalid data in the contractβs data structures.
Vs
To fix this, we should add proper validation, e.g.:
require(exitTick > entryTick, "Invalid ticks");
require(exitTick >= minTick && exitTick < maxTick, "Invalid exit tick");
require(entryTick >= minTick && entryTick < maxTick, "Invalid entry tick");
Adding checks like this ensures the tick tracking structure maintains data integrity when ticks are crossed.
Invalid Validation
The text was updated successfully, but these errors were encountered:
All reactions