Strategy main ticks are not symmetric when the tick spacing is one due to incorrect isLowerSided inequality
Summary
Strategy main position ticks are set according to:
function _setMainTicks(int24 tick) internal {
int24 halfWidth = int24(positionWidth / 2);
int24 modulo = tick % tickSpacing;
if (modulo < 0) modulo += tickSpacing; // if tick is negative, modulo is also negative
bool isLowerSided = modulo < (tickSpacing / 2);
int24 tickBorder = tick - modulo;
if (!isLowerSided) tickBorder += tickSpacing;
mainPosition.tickLower = tickBorder - halfWidth;
mainPosition.tickUpper = tickBorder + halfWidth;
emit NewMainTicks(tickBorder - halfWidth, tickBorder + halfWidth);
}As can be seen, when the tick spacing is 1, the tick will be deemed lower sided, which adds a tick spacing (1) to the tick border. So, if the current tick is -1769, the ticks will be -1770 to -1766, which is not symmetric and will miss out on fees.
Root Cause
In Strategy:236, is lower sided check is incorrect.
Internal Pre-conditions
None.
External Pre-conditions
None.
Attack Path
- Protocol rebalances but main ticks are not symmetric and will miss out on fees when the price moves to the side that has less liquidity allocated.
Impact
Loss of fees.
PoC
// in setup
IUniswapV3Pool pool = IUniswapV3Pool(0x20E068D76f9E90b90604500B84c7e19dCB923e7e);
IERC20 wbtc = IERC20(0x4200000000000000000000000000000000000006); // token0
IERC20 usdc = IERC20(0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452); // token1
address uniRouter = address(0x2626664c2603336E57B271c5C0b26F421741e481);
vm.createSelectFork(vm.envString("RPC_URL_BASE"), 26874136);
function test_POC_WrongTicks_DueToIsLowerSide() public {
skip(10 minutes);
vm.startPrank(rebalancer);
IStrategy(strategy).rebalance();
IStrategy.Position memory mainPos = IStrategy(strategy).getMainPosition();
(, int24 tick,,,,,) = pool.slot0();
//@audit position is not symmetric, harming long term fees
assertEq(tick, -1769);
assertEq(mainPos.tickLower, -1770);
assertEq(mainPos.tickUpper, -1766);
}Mitigation
bool isLowerSided = modulo <= (tickSpacing / 2);