// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.26; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; /// @notice Uniswap v3's TickMath.getSqrtRatioAtTick and LiquidityAmounts, ported to 0.8 (unchecked where the /// original relied on 0.7 wrapping). Same constants, same rounding. The test suite checks the tick /// maths against the LIVE pool's own slot0 at the current tick, and against the ends of the range. library UniMath { int24 internal constant MIN_TICK = -887272; int24 internal constant MAX_TICK = 887272; uint160 internal constant MIN_SQRT_RATIO = 4295128739; uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; uint256 internal constant Q96 = 0x1000000000000000000000000; error TickOutOfRange(int24 tick); function sqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { unchecked { uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); if (absTick > uint256(int256(MAX_TICK))) revert TickOutOfRange(tick); uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; if (tick > 0) ratio = type(uint256).max / ratio; sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); } } function _order(uint160 a, uint160 b) private pure returns (uint160, uint160) { return a > b ? (b, a) : (a, b); } function liquidityForAmount0(uint160 sa, uint160 sb, uint256 amount0) internal pure returns (uint256) { (sa, sb) = _order(sa, sb); uint256 intermediate = Math.mulDiv(sa, sb, Q96); return Math.mulDiv(amount0, intermediate, sb - sa); } function liquidityForAmount1(uint160 sa, uint160 sb, uint256 amount1) internal pure returns (uint256) { (sa, sb) = _order(sa, sb); return Math.mulDiv(amount1, Q96, sb - sa); } /// @notice The most liquidity `amount0` and `amount1` can buy in [sa, sb] at price `sp`, capped at uint128. function liquidityForAmounts(uint160 sp, uint160 sa, uint160 sb, uint256 amount0, uint256 amount1) internal pure returns (uint128) { (sa, sb) = _order(sa, sb); uint256 l; if (sp <= sa) { l = liquidityForAmount0(sa, sb, amount0); } else if (sp < sb) { l = Math.min(liquidityForAmount0(sp, sb, amount0), liquidityForAmount1(sa, sp, amount1)); } else { l = liquidityForAmount1(sa, sb, amount1); } return l > type(uint128).max ? type(uint128).max : uint128(l); } function amount0ForLiquidity(uint160 sa, uint160 sb, uint128 liquidity) internal pure returns (uint256) { (sa, sb) = _order(sa, sb); return Math.mulDiv(uint256(liquidity) << 96, sb - sa, sb) / sa; } function amount1ForLiquidity(uint160 sa, uint160 sb, uint128 liquidity) internal pure returns (uint256) { (sa, sb) = _order(sa, sb); return Math.mulDiv(liquidity, sb - sa, Q96); } /// @notice What a position of `liquidity` in [sa, sb] holds at price `sp`, rounded down. function amountsForLiquidity(uint160 sp, uint160 sa, uint160 sb, uint128 liquidity) internal pure returns (uint256 amount0, uint256 amount1) { (sa, sb) = _order(sa, sb); if (sp <= sa) { amount0 = amount0ForLiquidity(sa, sb, liquidity); } else if (sp < sb) { amount0 = amount0ForLiquidity(sp, sb, liquidity); amount1 = amount1ForLiquidity(sa, sp, liquidity); } else { amount1 = amount1ForLiquidity(sa, sb, liquidity); } } }