MEDIUM severity: DoS of Bunni pools configured with dynamic LDFs due to insufficient validation of post-shift tick bounds. **Description:** Unlike in `LibUniformDistribution::decodeParams`, the min/max usable ticks are not validated by `UniformDistribution::query`. The maximum length of the liquidity position corresponds to $\text{tick} \in [minUsableTick, maxUsableTick]$, but if the invocation of `enforceShiftMode()`.....

Vulnerable code:
function query(
    PoolKey calldata key,
    int24 roundedTick,
    int24 twapTick,
    int24, /* spotPriceTick */
    bytes32 ldfParams,
    bytes32 ldfState
)
    external
    view
    override
    guarded
    returns (
        uint256 liquidityDensityX96_,
        uint256 cumulativeAmount0DensityX96,
        uint256 cumulativeAmount1DensityX96,
        bytes32 newLdfState,
        bool shouldSurge
    )
{
    (int24 tickLower, int24 tickUpper, ShiftMode shiftMode) =
        LibUniformDistribution.decodeParams(twapTick, key.tickSpacing, ldfParams);
    (bool initialized, int24 lastTickLower) = _decodeState(ldfState);
    if (initialized) {
        int24 tickLength = tickUpper - tickLower;
        tickLower = enforceShiftMode(tickLower, lastTickLower, shiftMode);
@>      tickUpper = tickLower + tickLength;
        shouldSurge = tickLower != lastTickLower;
    }
     (liquidityDensityX96_, cumulativeAmount0DensityX96, cumulativeAmount1DensityX96) =
        LibUniformDistribution.query(roundedTick, key.tickSpacing, tickLower, tickUpper);
    newLdfState = _encodeState(tickLower);
}

/// @return tickLower The lower tick of the distribution
/// @return tickUpper The upper tick of the distribution
function decodeParams(int24 twapTick, int24 tickSpacing, bytes32 ldfParams)
    internal
    pure
    returns (int24 tickLower, int24 tickUpper, ShiftMode shiftMode)
{
    shiftMode = ShiftMode(uint8(bytes1(ldfParams)));

    if (shiftMode != ShiftMode.STATIC) {
        // | shiftMode - 1 byte | offset - 3 bytes | length - 3 bytes |
        int24 offset = int24(uint24(bytes3(ldfParams << 8))); // offset of tickLower from the twap tick
        int24 length = int24(uint24(bytes3(ldfParams << 32))); // length of the position in rounded ticks
        tickLower = roundTickSingle(twapTick + offset, tickSpacing);
        tickUpper = tickLower + length * tickSpacing;

        // bound distribution to be within the range of usable ticks
        (int24 minUsableTick, int24 maxUsableTick) =
            (TickMath.minUsableTick(tickSpacing), TickMath.maxUsableTick(tickSpacing));
        if (tickLower < minUsableTick) {
            int24 tickLength = tickUpper - tickLower;
            tickLower = minUsableTick;
            tickUpper = int24(FixedPointMathLib.min(tickLower + tickLength, maxUsableTick));
        } else if (tickUpper > maxUsableTick) {
            int24 tickLength = tickUpper - tickLower;
            tickUpper = maxUsableTick;
            tickLower = int24(FixedPointMathLib.max(tickUpper - tickLength, minUsableTick));
        }
    } else {
        ...
    }
}

when validating params:
 minUsableTick          maxUsableTick
    |                          |
      |                           |
       ____________________________
              encoded length


when decoding params:
 minUsableTick      maxUsableTick
    |                      |
    |                      |
    ________________________
   tickLength = smallerLength


when enforcing shift mode:
      newTickLower  maxUsableTick
    |      |               |       |
            ________________________
                tickLength