forked from Uniswap/v4-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSqrtPriceMath.sol
219 lines (199 loc) Β· 10.7 KB
/
SqrtPriceMath.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.19;
import {SafeCast} from "./SafeCast.sol";
import {FullMath} from "./FullMath.sol";
import {UnsafeMath} from "./UnsafeMath.sol";
import {FixedPoint96} from "./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 SafeCast for uint256;
/// @notice Gets the next sqrt price given a delta of currency0
/// @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 currency0 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of currency0 to add or remove from virtual reserves
/// @param add Whether to add or remove the amount of currency0
/// @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) {
unchecked {
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));
}
}
}
// denominator is checked for overflow
return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount));
} else {
unchecked {
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 currency1
/// @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 currency1 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of currency1 to add, or remove, from virtual reserves
/// @param add Whether to add, or remove, the amount of currency1
/// @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) + 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 currency0 or currency1
/// @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 currency0, or currency1, is being swapped in
/// @param zeroForOne Whether the amount in is currency0 or currency1
/// @return sqrtQX96 The price after adding the input amount to currency0 or currency1
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 currency0 or currency1
/// @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 currency0, or currency1, is being swapped out
/// @param zeroForOne Whether the amount out is currency0 or currency1
/// @return sqrtQX96 The price after removing the output amount of currency0 or currency1
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 currency0 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)
{
unchecked {
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 currency1 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 currency0 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 currency0 corresponding to the passed liquidityDelta between the two prices
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();
}
}
/// @notice Helper that gets signed currency1 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 currency1 corresponding to the passed liquidityDelta between the two prices
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();
}
}
}