Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

liquidity_math.gno

12.43 Kb · 315 lines
  1package gnsmath
  2
  3import (
  4	ufmt "gno.land/p/nt/ufmt/v0"
  5
  6	"gno.land/p/gnoswap/consts/v1"
  7	i256 "gno.land/p/gnoswap/int256/v1"
  8	u256 "gno.land/p/gnoswap/uint256/v1"
  9)
 10
 11// computeLiquidityForAmount0 calculates the liquidity for a given amount of token0.
 12//
 13// This function computes the maximum possible liquidity that can be provided for `token0`
 14// based on the provided price boundaries (sqrtRatioAX96 and sqrtRatioBX96) in Q64.96 format.
 15//
 16// Parameters:
 17//   - sqrtRatioAX96: *u256.Uint - The square root price at the lower tick boundary (Q64.96).
 18//   - sqrtRatioBX96: *u256.Uint - The square root price at the upper tick boundary (Q64.96).
 19//   - amount0: *u256.Uint - The amount of token0 to be converted to liquidity.
 20//
 21// Returns:
 22//   - *u256.Uint: The calculated liquidity, represented as an unsigned 128-bit integer (uint128).
 23//
 24// Panics:
 25//   - If the resulting liquidity exceeds the uint128 range, `SafeConvertToUint128` will trigger a panic.
 26func computeLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0 *u256.Uint) *u256.Uint {
 27	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
 28	intermediate := u256.MulDiv(sqrtRatioAX96, sqrtRatioBX96, consts.Q96())
 29
 30	diff := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
 31	if diff.IsZero() {
 32		panic(newErrorWithDetail(
 33			errLiquidityIdenticalTicks,
 34			ufmt.Sprintf("sqrtRatioAX96 (%s) and sqrtRatioBX96 (%s) are identical", sqrtRatioAX96.ToString(), sqrtRatioBX96.ToString()),
 35		))
 36	}
 37	res := u256.MulDiv(amount0, intermediate, diff)
 38	return SafeConvertToUint128(res)
 39}
 40
 41// computeLiquidityForAmount1 calculates liquidity based on the provided token1 amount and price range.
 42//
 43// This function computes the liquidity for a given amount of token1 by using the difference
 44// between the upper and lower square root price ratios. The calculation uses Q96 fixed-point
 45// arithmetic to maintain precision.
 46//
 47// Parameters:
 48//   - sqrtRatioAX96: *u256.Uint - The square root ratio of price at the lower tick, represented in Q96 format.
 49//   - sqrtRatioBX96: *u256.Uint - The square root ratio of price at the upper tick, represented in Q96 format.
 50//   - amount1: *u256.Uint - The amount of token1 to calculate liquidity for.
 51//
 52// Returns:
 53//   - *u256.Uint: The calculated liquidity based on the provided amount of token1 and price range.
 54//
 55// Notes:
 56//   - The result is not directly limited to uint128, as liquidity values can exceed uint128 bounds.
 57//   - If `sqrtRatioAX96 == sqrtRatioBX96`, the function will panic due to division by zero.
 58//   - Q96 is a constant representing `2^96`, ensuring that precision is maintained during division.
 59//
 60// Panics:
 61//   - If the resulting liquidity exceeds the uint128 range, `SafeConvertToUint128` will trigger a panic.
 62func computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1 *u256.Uint) *u256.Uint {
 63	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
 64
 65	diff := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
 66	if diff.IsZero() {
 67		panic(newErrorWithDetail(
 68			errLiquidityIdenticalTicks,
 69			ufmt.Sprintf("sqrtRatioAX96 (%s) and sqrtRatioBX96 (%s) are identical", sqrtRatioAX96.ToString(), sqrtRatioBX96.ToString()),
 70		))
 71	}
 72	res := u256.MulDiv(amount1, consts.Q96(), diff)
 73	return SafeConvertToUint128(res)
 74}
 75
 76// GetLiquidityForAmounts calculates the maximum liquidity supported by two token amounts.
 77//
 78// The current square-root price determines which token amounts are active. Below
 79// the range only token0 is used, above it only token1 is used, and inside it the
 80// smaller of the token0- and token1-derived liquidities is returned.
 81//
 82// Parameters:
 83//   - sqrtRatioX96: Current square-root price, encoded as a Q64.96 ratio.
 84//   - sqrtRatioAX96: First price-range endpoint, encoded as a Q64.96 ratio.
 85//   - sqrtRatioBX96: Second price-range endpoint, encoded as a Q64.96 ratio; the endpoints are sorted.
 86//   - amount0: Available token0 amount.
 87//   - amount1: Available token1 amount.
 88//
 89// Returns:
 90//   - liquidity: Maximum liquidity supported by the amounts, constrained to uint128.
 91//
 92// Panics if a selected amount/range calculation has identical bounds or exceeds uint128.
 93func GetLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1 *u256.Uint) (liquidity *u256.Uint) {
 94	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
 95
 96	if sqrtRatioX96.Lte(sqrtRatioAX96) {
 97		liquidity = computeLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0)
 98	} else if sqrtRatioX96.Lt(sqrtRatioBX96) {
 99		liquidity0 := computeLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0)
100		liquidity1 := computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1)
101
102		if liquidity0.Lt(liquidity1) {
103			liquidity = liquidity0
104		} else {
105			liquidity = liquidity1
106		}
107	} else {
108		liquidity = computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1)
109	}
110	return liquidity
111}
112
113// computeAmount0ForLiquidity calculates the required amount of token0 for a given liquidity level
114// within a specified price range (represented by sqrt ratios).
115//
116// This function determines the amount of token0 needed to provide a specified amount of liquidity
117// within a price range defined by sqrtRatioAX96 (lower bound) and sqrtRatioBX96 (upper bound).
118//
119// Parameters:
120// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
121// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
122// - liquidity: The liquidity to be provided (*u256.Uint).
123//
124// Returns:
125// - *u256.Uint: The amount of token0 required to achieve the specified liquidity level.
126//
127// Notes:
128// - This function assumes the price bounds are expressed in Q64.96 fixed-point format.
129// - The function returns 0 if the liquidity is 0 or the price bounds are invalid.
130// - Handles edge cases where sqrtRatioAX96 equals sqrtRatioBX96 by returning 0 (to prevent division by zero).
131func computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint {
132	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
133	if sqrtRatioAX96.IsZero() || sqrtRatioBX96.IsZero() || liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) {
134		return u256.Zero()
135	}
136
137	val1 := u256.Zero().Lsh(liquidity, Q96_RESOLUTION)
138	val2 := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
139
140	res := u256.MulDiv(val1, val2, sqrtRatioBX96)
141	res = res.Div(res, sqrtRatioAX96)
142
143	return res
144}
145
146// computeAmount1ForLiquidity calculates the required amount of token1 for a given liquidity level
147// within a specified price range (represented by sqrt ratios).
148//
149// This function determines the amount of token1 needed to provide liquidity between the
150// lower (sqrtRatioAX96) and upper (sqrtRatioBX96) price bounds. The calculation is performed
151// in Q64.96 fixed-point format, which is standard for many liquidity calculations.
152//
153// Parameters:
154// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
155// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
156// - liquidity: The liquidity amount to be used in the calculation (*u256.Uint).
157//
158// Returns:
159// - *u256.Uint: The amount of token1 required to achieve the specified liquidity level.
160//
161// Notes:
162//   - This function handles edge cases where the liquidity is zero or when sqrtRatioAX96 equals sqrtRatioBX96
163//     to prevent division by zero.
164//   - The calculation assumes sqrtRatioAX96 is always less than or equal to sqrtRatioBX96 after the initial
165//     ascending order sorting.
166func computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint {
167	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
168	if liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) {
169		return u256.Zero()
170	}
171
172	diff := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
173	res := u256.MulDiv(liquidity, diff, consts.Q96())
174
175	return res
176}
177
178// GetAmountsForLiquidity calculates the amounts of token0 and token1 represented
179// by a specified liquidity within a price range.
180//
181// If the current price is below the lower bound, only token0 is required. If the
182// current price is above the upper bound, only token1 is required. When the
183// price is within the range, both token0 and token1 are calculated.
184//
185// Parameters:
186//   - sqrtRatioX96: Current square-root price in Q64.96 format.
187//   - sqrtRatioAX96: First price-range endpoint in Q64.96 format.
188//   - sqrtRatioBX96: Second price-range endpoint in Q64.96 format; the endpoints are ordered internally.
189//   - liquidity: Non-negative liquidity amount to value.
190//
191// Returns:
192//   - amount0: Token0 amount represented by liquidity; zero when the current price is at or above the upper endpoint.
193//   - amount1: Token1 amount represented by liquidity; zero when the current price is at or below the lower endpoint.
194//     Call ToString() on either value for decimal display.
195//
196// Notes:
197//   - If liquidity is zero, the function returns zero values for both tokens.
198//   - At a boundary, the corresponding out-of-range token amount is zero.
199//
200// Example:
201// ```
202// amount0, amount1 := GetAmountsForLiquidity(
203//
204//	u256.MustFromDecimal("79228162514264337593543950336"),  // sqrtRatioX96 (1.0 in Q64.96)
205//	u256.MustFromDecimal("39614081257132168796771975168"),  // sqrtRatioAX96 (0.5 in Q64.96)
206//	u256.MustFromDecimal("158456325028528675187087900672"), // sqrtRatioBX96 (2.0 in Q64.96)
207//	u256.MustFromDecimal("1000000"),                        // Liquidity
208//
209// )
210//
211// println("Token0:", amount0.ToString(), "Token1:", amount1.ToString())
212//
213// // Output:
214// Token0: 500000, Token1: 500000
215// ```
216func GetAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) (*u256.Uint, *u256.Uint) {
217	if liquidity.IsZero() {
218		return u256.Zero(), u256.Zero()
219	}
220
221	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
222
223	amount0 := u256.Zero()
224	amount1 := u256.Zero()
225
226	if sqrtRatioX96.Lte(sqrtRatioAX96) {
227		amount0 = computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity)
228	} else if sqrtRatioX96.Lt(sqrtRatioBX96) {
229		amount0 = computeAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity)
230		amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity)
231	} else {
232		amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity)
233	}
234
235	return amount0, amount1
236}
237
238// LiquidityMathAddDelta calculates the new liquidity after applying a signed delta.
239// A negative delta subtracts its magnitude; a non-negative delta adds its magnitude.
240//
241// Parameters:
242//   - x: Current non-negative liquidity value.
243//   - y: Signed liquidity delta; positive values add and negative values subtract.
244//
245// Returns:
246//   - liquidity: Updated liquidity, constrained to the uint128 range.
247//
248// Panics if x or y is nil, subtraction underflows, addition overflows uint256,
249// or the resulting liquidity exceeds MaxUint128.
250func LiquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint {
251	if x == nil || y == nil {
252		panic("liquidity_math: x or y is nil")
253	}
254
255	yAbs := y.Abs()
256
257	// Subtract or add based on the sign of y
258	if y.Lt(i256.Zero()) {
259		z := u256.Zero().Sub(x, yAbs)
260		if z.Gte(x) {
261			panic(ufmt.Sprintf(
262				"liquidity_math: underflow (x: %s, y: %s, z:%s)",
263				x.ToString(), y.ToString(), z.ToString()))
264		}
265		if z.Gt(consts.MaxUint128()) {
266			panic(ufmt.Sprintf(
267				"liquidity_math: result exceeds uint128 range (z: %s)",
268				z.ToString()))
269		}
270		return z
271	}
272
273	z := u256.Zero().Add(x, yAbs)
274	if z.Lt(x) {
275		panic(ufmt.Sprintf(
276			"liquidity_math: overflow (x: %s, y: %s, z:%s)",
277			x.ToString(), y.ToString(), z.ToString()))
278	}
279	if z.Gt(consts.MaxUint128()) {
280		panic(ufmt.Sprintf(
281			"liquidity_math: result exceeds uint128 range (z: %s)",
282			z.ToString()))
283	}
284	return z
285}
286
287// toAscendingOrder returns the two values in ascending order.
288func toAscendingOrder(a, b *u256.Uint) (*u256.Uint, *u256.Uint) {
289	if a.Gt(b) {
290		return b, a
291	}
292
293	return a, b
294}
295
296// SafeConvertToUint128 verifies that value fits in the uint128 range.
297//
298// No representation conversion is performed: the original pointer is returned
299// when its value is at most 2^128 - 1.
300//
301// Parameters:
302//   - value: Non-nil unsigned 256-bit value to validate.
303//
304// Returns:
305//   - converted: The same *u256.Uint pointer when value fits in uint128.
306//
307// Panics if value is nil or exceeds the maximum uint128 value.
308func SafeConvertToUint128(value *u256.Uint) *u256.Uint {
309	if value.Gt(consts.MaxUint128()) {
310		panic(ufmt.Sprintf(
311			"%v: amount(%s) overflows uint128 range",
312			errLiquidityOverflow, value.ToString()))
313	}
314	return value
315}