MEDIUM severity: `OracleUniGeoDistribution` oracle tick validation is flawed. **Description:** `OracleUniGeoDistribution::floorPriceToRick` computes the rounded tick to which the given bond token floor price corresponds:

This function is called within `OracleUniGeoDistribution::isValidParams`:

And validation of the resulting oracle rick, which informs the...

Vulnerable code:
function floorPriceToRick(uint256 floorPriceWad, int24 tickSpacing) public view returns (int24 rick) {
    // convert floor price to sqrt price
    // assume bond is currency0, floor price's unit is (currency1 / currency0)
    // unscale by WAD then rescale by 2**(96*2), then take the sqrt to get sqrt(floorPrice) * 2**96
    uint160 sqrtPriceX96 = ((floorPriceWad << 192) / WAD).sqrt().toUint160();

    // convert sqrt price to rick
    rick = sqrtPriceX96.getTickAtSqrtPrice();
    rick = bondLtStablecoin ? rick : -rick; // need to invert the sqrt price if bond is currency1
    rick = roundTickSingle(rick, tickSpacing);
}

function isValidParams(PoolKey calldata key, uint24, /* twapSecondsAgo */ bytes32 ldfParams)
        public
        view
        override
        returns (bool)
    {
        // only allow the bond-stablecoin pairing
        (Currency currency0, Currency currency1) = bond < stablecoin ? (bond, stablecoin) : (stablecoin, bond);

        return LibOracleUniGeoDistribution.isValidParams(
@>          key.tickSpacing, ldfParams, floorPriceToRick(oracle.getFloorPrice(), key.tickSpacing)
        ) && key.currency0 == currency0 && key.currency1 == currency1;
    }

function isValidParams(int24 tickSpacing, bytes32 ldfParams, int24 oracleTick) internal pure returns (bool) {
        // decode params
        // | shiftMode - 1 byte | distributionType - 1 byte | oracleIsTickLower - 1 byte | oracleTickOffset - 2 bytes | nonOracleTick - 3 bytes | alpha - 4 bytes |
        uint8 shiftMode = uint8(bytes1(ldfParams));
        uint8 distributionType = uint8(bytes1(ldfParams << 8));
        bool oracleIsTickLower = uint8(bytes1(ldfParams << 16)) != 0;
        int24 oracleTickOffset = int24(int16(uint16(bytes2(ldfParams << 24))));
        int24 nonOracleTick = int24(uint24(bytes3(ldfParams << 40)));
        uint32 alpha = uint32(bytes4(ldfParams << 64));

@>      oracleTick += oracleTickOffset; // apply offset to oracle tick
@>      (int24 tickLower, int24 tickUpper) =
            oracleIsTickLower ? (oracleTick, nonOracleTick) : (nonOracleTick, oracleTick);
        if (tickLower >= tickUpper) {
            // ensure tickLower < tickUpper
            // use the non oracle tick as the bound
            // LDF needs to be at least one tickSpacing wide
            (tickLower, tickUpper) =
                oracleIsTickLower ? (tickUpper - tickSpacing, tickUpper) : (tickLower, tickLower + tickSpacing);
        }

        bytes32 geometricLdfParams =
        bytes32(abi.encodePacked(shiftMode, tickLower, int16((tickUpper - tickLower) / tickSpacing), alpha));

        (int24 minUsableTick, int24 maxUsableTick) =
            (TickMath.minUsableTick(tickSpacing), TickMath.maxUsableTick(tickSpacing));

        // validity conditions:
        // - geometric LDF params are valid
        // - uniform LDF params are valid
        // - shiftMode is static
        // - distributionType is valid
        // - oracleTickOffset is aligned to tickSpacing
        // - nonOracleTick is aligned to tickSpacing
        return LibGeometricDistribution.isValidParams(tickSpacing, 0, geometricLdfParams)
            && tickLower % tickSpacing == 0 && tickUpper % tickSpacing == 0 && tickLower >= minUsableTick
            && tickUpper <= maxUsableTick && shiftMode == uint8(ShiftMode.STATIC)
            && distributionType <= uint8(type(DistributionType).max) && oracleTickOffset % tickSpacing == 0
            && nonOracleTick % tickSpacing == 0;
    }