infrastructure-upgrade/lib/v3-core/contracts/libraries/SqrtPriceMath.sol

228 lines
10 KiB
Solidity
Raw Permalink Normal View History

2023-04-08 21:46:18 +03:00
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import './LowGasSafeMath.sol';
import './SafeCast.sol';
import './FullMath.sol';
import './UnsafeMath.sol';
import './FixedPoint96.sol';
/// @title Functions based on Q64.96 sqrt price and liquidity
/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas
library SqrtPriceMath {
using LowGasSafeMath for uint256;
using SafeCast for uint256;
/// @notice Gets the next sqrt price given a delta of token0
/// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the
/// price less in order to not send too much output.
/// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96),
/// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount).
/// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token0 to add or remove from virtual reserves
/// @param add Whether to add or remove the amount of token0
/// @return The price after adding or removing amount, depending on add
function getNextSqrtPriceFromAmount0RoundingUp(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price
if (amount == 0) return sqrtPX96;
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
if (add) {
uint256 product;
if ((product = amount * sqrtPX96) / amount == sqrtPX96) {
uint256 denominator = numerator1 + product;
if (denominator >= numerator1)
// always fits in 160 bits
return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator));
}
return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount)));
} else {
uint256 product;
// if the product overflows, we know the denominator underflows
// in addition, we must check that the denominator does not underflow
require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
uint256 denominator = numerator1 - product;
return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160();
}
}
/// @notice Gets the next sqrt price given a delta of token1
/// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the
/// price less in order to not send too much output.
/// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity
/// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token1 to add, or remove, from virtual reserves
/// @param add Whether to add, or remove, the amount of token1
/// @return The price after adding or removing `amount`
function getNextSqrtPriceFromAmount1RoundingDown(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// if we're adding (subtracting), rounding down requires rounding the quotient down (up)
// in both cases, avoid a mulDiv for most inputs
if (add) {
uint256 quotient =
(
amount <= type(uint160).max
? (amount << FixedPoint96.RESOLUTION) / liquidity
: FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity)
);
return uint256(sqrtPX96).add(quotient).toUint160();
} else {
uint256 quotient =
(
amount <= type(uint160).max
? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
: FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
);
require(sqrtPX96 > quotient);
// always fits 160 bits
return uint160(sqrtPX96 - quotient);
}
}
/// @notice Gets the next sqrt price given an input amount of token0 or token1
/// @dev Throws if price or liquidity are 0, or if the next price is out of bounds
/// @param sqrtPX96 The starting price, i.e., before accounting for the input amount
/// @param liquidity The amount of usable liquidity
/// @param amountIn How much of token0, or token1, is being swapped in
/// @param zeroForOne Whether the amount in is token0 or token1
/// @return sqrtQX96 The price after adding the input amount to token0 or token1
function getNextSqrtPriceFromInput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountIn,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we don't pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true)
: getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true);
}
/// @notice Gets the next sqrt price given an output amount of token0 or token1
/// @dev Throws if price or liquidity are 0 or the next price is out of bounds
/// @param sqrtPX96 The starting price before accounting for the output amount
/// @param liquidity The amount of usable liquidity
/// @param amountOut How much of token0, or token1, is being swapped out
/// @param zeroForOne Whether the amount out is token0 or token1
/// @return sqrtQX96 The price after removing the output amount of token0 or token1
function getNextSqrtPriceFromOutput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountOut,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false)
: getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false);
}
/// @notice Gets the amount0 delta between two prices
/// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper),
/// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up or down
/// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount0) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
require(sqrtRatioAX96 > 0);
return
roundUp
? UnsafeMath.divRoundingUp(
FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96),
sqrtRatioAX96
)
: FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96;
}
/// @notice Gets the amount1 delta between two prices
/// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up, or down
/// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return
roundUp
? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96)
: FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
}
/// @notice Helper that gets signed token0 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount0 delta
/// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount0) {
return
liquidity < 0
? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
/// @notice Helper that gets signed token1 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount1 delta
/// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount1) {
return
liquidity < 0
? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
}