diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol new file mode 100644 index 0000000..61add38 --- /dev/null +++ b/src/libraries/SqrtPriceMath.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +import {FullMath} from "./FullMath.sol"; +import {UnsafeMath} from "./UnsafeMath.sol"; +import {FixedPoint96} from "./FixedPoint96.sol"; +import {SafeCast} from "./SafeCast.sol"; + +/// @title Sqrt-price and token amount deltas along the constant-product curve. +library SqrtPriceMath { + using SafeCast for uint256; + + function getNextSqrtPriceFromAmount0RoundingUp(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + internal + pure + returns (uint160) + { + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.resolution; + + unchecked { + if (add) { + uint256 product = amount * sqrtPX96; + if (product / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) { + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + return uint160(UnsafeMath.divRoundingUp(numerator1, numerator1 / sqrtPX96 + amount)); + } else { + uint256 product = amount * sqrtPX96; + require(product / amount == sqrtPX96 && numerator1 > product, "SqrtPriceMath: A0"); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + internal + pure + returns (uint160) + { + unchecked { + if (add) { + uint256 quotient = + (amount <= type(uint160).max + ? (amount << FixedPoint96.resolution) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.q96, liquidity)); + return (uint256(sqrtPX96) + quotient).toUint160(); + } else { + uint256 quotient = + (amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.resolution, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.q96, liquidity)); + require(sqrtPX96 > quotient, "SqrtPriceMath: A1"); + return uint160(sqrtPX96 - quotient); + } + } + } + + function getNextSqrtPriceFromInput(uint160 sqrtPX96, uint128 liquidity, uint256 amountIn, bool zeroForOne) + internal + pure + returns (uint160 sqrtQX96) + { + require(sqrtPX96 > 0, "SqrtPriceMath: PRICE"); + require(liquidity > 0, "SqrtPriceMath: LIQ"); + + return zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + function getNextSqrtPriceFromOutput(uint160 sqrtPX96, uint128 liquidity, uint256 amountOut, bool zeroForOne) + internal + pure + returns (uint160 sqrtQX96) + { + require(sqrtPX96 > 0, "SqrtPriceMath: PRICE"); + require(liquidity > 0, "SqrtPriceMath: LIQ"); + + return zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + internal + pure + returns (uint256 amount0) + { + unchecked { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.resolution; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0, "SqrtPriceMath: A0Z"); + + return roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + } + + function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + internal + pure + returns (uint256 amount1) + { + unchecked { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.q96); + } + } + + function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + internal + pure + returns (int256 amount0) + { + unchecked { + return liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + } + + function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + internal + pure + returns (int256 amount1) + { + unchecked { + return liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + } +}