Function initTickTracking initializes the tick tracking data structure, but does not validate that tick is within the min/max tick range for the pool. This could allow initializing invalid tick values.
Proof of Concept
Here is the line in initTickTracking that could initialize invalid tick values.
The tick parameter is used to index into the tickTracking_ mapping without validating it is within the min/max tick range for the pool.
The initTickTracking function is used to initialize the tick tracking data structure for a specific tick in a pool. This data structure tracks when ticks become activated/deactivated over time.
It is defined as:
function initTickTracking(bytes32 poolIdx, int24 tick) internal {
//...
}
The key parameters are:
poolIdx: The unique ID of the pool
tick: The specific tick that we want to start tracking
Within the function, it does the following:
Creates a TickTracking struct to represent the initial data for this tick:
The issue here is that tick is used to index directly into the tickTracking_ mapping without validating it is within the min/max range for the pool.
The min/max ticks are set for each pool based on the tick spacing (number of ticks in the entire range). This range is stored in the PoolRegistry.
By not validating tick is within range, it is possible to initialize tick tracking for arbitrary tick values, even those outside the min/max bounds for the pool.
This could then corrupt the tick tracking data structure by inserting invalid entries. Later accounting logic relies on this structure being valid.
Here is a simple proof of concept that demonstrates initializing the tick tracking for an invalid, out-of-range tick value in the initTickTracking function:
contract Exploit {
address constant pool = 0x...; // some pool address
function exploitInitTickTracking() external {
IUniswapV3Pool poolContract = IUniswapV3Pool(pool);
bytes32 poolKey = poolContract.poolId();
// Let's say this pool has a min tick of -887272 and max tick of -887270
// Calling initTickTracking with a tick below the min
LiquidityMining(pool).initTickTracking(poolKey, -887300);
// This initializes tick tracking for an invalid tick below the min
// Now the tickTracking mapping has an invalid entry for tick -887300
}
}
Key points:
I first get the poolId for the target pool contract
I assume a hypothetical min/max tick range that is valid for the pool
Then call the initTickTracking function with a tick value -887300 that is lower than the min tick
This will successfully initialize tick tracking data for that invalid tick
Resulting in a corrupted tickTracking_ mapping containing invalid data
This simple exploit shows how failing to validate the tick input can lead to improper state in the contract's data structures.
Tools Used
Vs
Recommended Mitigation Steps
The min/max tick values for a pool can be obtained from the PoolRegistry contract using PoolRegistry.getPoolData(poolIdx). So to validate, you could add something like:
function initTickTracking(bytes32 poolIdx, int24 tick) internal {
(,,int24 tickSpacing,, int24 minTick, int24 maxTick,) = PoolRegistry.getPoolData(poolIdx);
require(tick >= minTick && tick < maxTick, "Invalid tick");
//...rest of function
}
This would prevent initializing the tick tracking data structure for invalid tick values outside the min/max range.
Lines of code
https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L16-L20
Vulnerability details
Impact
Function
initTickTracking
initializes the tick tracking data structure, but does not validate thattick
is within the min/max tick range for the pool. This could allow initializing invalid tick values.Proof of Concept
Here is the line in initTickTracking that could initialize invalid tick values.
The
tick
parameter is used to index into thetickTracking_
mapping without validating it is within the min/max tick range for the pool.The
initTickTracking
function is used to initialize the tick tracking data structure for a specific tick in a pool. This data structure tracks when ticks become activated/deactivated over time.It is defined as:
The key parameters are:
poolIdx
: The unique ID of the pooltick
: The specific tick that we want to start trackingWithin the function, it does the following:
TickTracking
struct to represent the initial data for this tick:The issue here is that
tick
is used to index directly into thetickTracking_
mapping without validating it is within the min/max range for the pool.The min/max ticks are set for each pool based on the tick spacing (number of ticks in the entire range). This range is stored in the PoolRegistry.
By not validating
tick
is within range, it is possible to initialize tick tracking for arbitrary tick values, even those outside the min/max bounds for the pool.This could then corrupt the tick tracking data structure by inserting invalid entries. Later accounting logic relies on this structure being valid.
Key points:
initTickTracking
function with a tick value-887300
that is lower than the min ticktickTracking_
mapping containing invalid dataThis simple exploit shows how failing to validate the tick input can lead to improper state in the contract's data structures.
Tools Used
Vs
Recommended Mitigation Steps
The min/max tick values for a pool can be obtained from the PoolRegistry contract using
PoolRegistry.getPoolData(poolIdx)
. So to validate, you could add something like:This would prevent initializing the tick tracking data structure for invalid tick values outside the min/max range.
Assessed type
Invalid Validation