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

14.13 Kb · 334 lines
  1package gnsmath
  2
  3import (
  4	ufmt "gno.land/p/nt/ufmt/v0"
  5
  6	"gno.land/p/gnoswap/consts"
  7	i256 "gno.land/p/gnoswap/int256"
  8	u256 "gno.land/p/gnoswap/uint256"
  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 given the current price (sqrtRatioX96),
 77// upper and lower price bounds (sqrtRatioAX96 and sqrtRatioBX96), and token amounts (amount0, amount1).
 78//
 79// This function evaluates how much liquidity can be obtained for specified amounts of token0 and token1
 80// within the provided price range. It returns the lesser liquidity based on available token0 or token1
 81// to ensure the pool remains balanced.
 82//
 83// Parameters:
 84// - sqrtRatioX96: The current price as a square root ratio in Q64.96 format (*u256.Uint).
 85// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
 86// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
 87// - amount0: The amount of token0 available to provide liquidity (*u256.Uint).
 88// - amount1: The amount of token1 available to provide liquidity (*u256.Uint).
 89//
 90// Returns:
 91// - *u256.Uint: The maximum possible liquidity that can be minted.
 92//
 93// Notes:
 94//   - The `Clone` method is used to prevent modification of the original values during computation.
 95//   - The function ensures that liquidity calculations handle edge cases when the current price
 96//     is outside the specified range by returning liquidity based on the dominant token.
 97func GetLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1 *u256.Uint) (liquidity *u256.Uint) {
 98	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
 99
100	if sqrtRatioX96.Lte(sqrtRatioAX96) {
101		liquidity = computeLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0)
102	} else if sqrtRatioX96.Lt(sqrtRatioBX96) {
103		liquidity0 := computeLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0)
104		liquidity1 := computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1)
105
106		if liquidity0.Lt(liquidity1) {
107			liquidity = liquidity0
108		} else {
109			liquidity = liquidity1
110		}
111	} else {
112		liquidity = computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1)
113	}
114	return liquidity
115}
116
117// computeAmount0ForLiquidity calculates the required amount of token0 for a given liquidity level
118// within a specified price range (represented by sqrt ratios).
119//
120// This function determines the amount of token0 needed to provide a specified amount of liquidity
121// within a price range defined by sqrtRatioAX96 (lower bound) and sqrtRatioBX96 (upper bound).
122//
123// Parameters:
124// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
125// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
126// - liquidity: The liquidity to be provided (*u256.Uint).
127//
128// Returns:
129// - *u256.Uint: The amount of token0 required to achieve the specified liquidity level.
130//
131// Notes:
132// - This function assumes the price bounds are expressed in Q64.96 fixed-point format.
133// - The function returns 0 if the liquidity is 0 or the price bounds are invalid.
134// - Handles edge cases where sqrtRatioAX96 equals sqrtRatioBX96 by returning 0 (to prevent division by zero).
135func computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint {
136	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
137	if sqrtRatioAX96.IsZero() || sqrtRatioBX96.IsZero() || liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) {
138		return u256.Zero()
139	}
140
141	val1 := u256.Zero().Lsh(liquidity, Q96_RESOLUTION)
142	val2 := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
143
144	res := u256.MulDiv(val1, val2, sqrtRatioBX96)
145	res = res.Div(res, sqrtRatioAX96)
146
147	return res
148}
149
150// computeAmount1ForLiquidity calculates the required amount of token1 for a given liquidity level
151// within a specified price range (represented by sqrt ratios).
152//
153// This function determines the amount of token1 needed to provide liquidity between the
154// lower (sqrtRatioAX96) and upper (sqrtRatioBX96) price bounds. The calculation is performed
155// in Q64.96 fixed-point format, which is standard for many liquidity calculations.
156//
157// Parameters:
158// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
159// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
160// - liquidity: The liquidity amount to be used in the calculation (*u256.Uint).
161//
162// Returns:
163// - *u256.Uint: The amount of token1 required to achieve the specified liquidity level.
164//
165// Notes:
166//   - This function handles edge cases where the liquidity is zero or when sqrtRatioAX96 equals sqrtRatioBX96
167//     to prevent division by zero.
168//   - The calculation assumes sqrtRatioAX96 is always less than or equal to sqrtRatioBX96 after the initial
169//     ascending order sorting.
170func computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint {
171	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
172	if liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) {
173		return u256.Zero()
174	}
175
176	diff := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
177	res := u256.MulDiv(liquidity, diff, consts.Q96())
178
179	return res
180}
181
182// GetAmountsForLiquidity calculates the amounts of token0 and token1 required
183// to provide a specified liquidity within a price range.
184//
185// This function determines the quantities of token0 and token1 necessary to achieve
186// a given liquidity level, depending on the current price (sqrtRatioX96) and the
187// bounds of the price range (sqrtRatioAX96 and sqrtRatioBX96). The function returns
188// the calculated amounts of token0 and token1 as strings.
189//
190// If the current price is below the lower bound of the price range, only token0 is required.
191// If the current price is above the upper bound, only token1 is required. When the
192// price is within the range, both token0 and token1 are calculated.
193//
194// Parameters:
195// - sqrtRatioX96: The current price represented as a square root ratio in Q64.96 format (*u256.Uint).
196// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
197// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint).
198// - liquidity: The amount of liquidity to be provided (*u256.Uint).
199//
200// Returns:
201// - string: The calculated amount of token0 required to achieve the specified liquidity.
202// - string: The calculated amount of token1 required to achieve the specified liquidity.
203//
204// Notes:
205//   - If liquidity is zero, the function returns "0" for both token0 and token1.
206//   - The function guarantees that sqrtRatioAX96 is always the lower bound and
207//     sqrtRatioBX96 is the upper bound by calling toAscendingOrder().
208//   - Edge cases where the current price is exactly on the bounds are handled without division by zero.
209//
210// Example:
211// ```
212// amount0, amount1 := GetAmountsForLiquidity(
213//
214//	u256.MustFromDecimal("79228162514264337593543950336"),  // sqrtRatioX96 (1.0 in Q64.96)
215//	u256.MustFromDecimal("39614081257132168796771975168"),  // sqrtRatioAX96 (0.5 in Q64.96)
216//	u256.MustFromDecimal("158456325028528675187087900672"), // sqrtRatioBX96 (2.0 in Q64.96)
217//	u256.MustFromDecimal("1000000"),                        // Liquidity
218//
219// )
220//
221// println("Token0:", amount0, "Token1:", amount1)
222//
223// // Output:
224// Token0: 500000, Token1: 250000
225// ```
226func GetAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) (*u256.Uint, *u256.Uint) {
227	if liquidity.IsZero() {
228		return u256.Zero(), u256.Zero()
229	}
230
231	sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96)
232
233	amount0 := u256.Zero()
234	amount1 := u256.Zero()
235
236	if sqrtRatioX96.Lte(sqrtRatioAX96) {
237		amount0 = computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity)
238	} else if sqrtRatioX96.Lt(sqrtRatioBX96) {
239		amount0 = computeAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity)
240		amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity)
241	} else {
242		amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity)
243	}
244
245	return amount0, amount1
246}
247
248// LiquidityMathAddDelta calculates the new liquidity by applying the delta liquidity to the current liquidity.
249// If delta liquidity is negative, it subtracts the absolute value of delta liquidity from the current liquidity.
250// If delta liquidity is positive, it adds the absolute value of delta liquidity to the current liquidity.
251//
252// Parameters:
253//   - x: current liquidity as unsigned 256-bit integer
254//   - y: delta liquidity as signed 256-bit integer (positive to add, negative to subtract)
255//
256// Returns the new liquidity as a uint256 value.
257//
258// Panics if x or y is nil, or if the operation would result in underflow or overflow.
259func LiquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint {
260	if x == nil || y == nil {
261		panic("liquidity_math: x or y is nil")
262	}
263
264	yAbs := y.Abs()
265
266	// Subtract or add based on the sign of y
267	if y.Lt(i256.Zero()) {
268		z := u256.Zero().Sub(x, yAbs)
269		if z.Gte(x) {
270			panic(ufmt.Sprintf(
271				"liquidity_math: underflow (x: %s, y: %s, z:%s)",
272				x.ToString(), y.ToString(), z.ToString()))
273		}
274		if z.Gt(consts.MaxUint128()) {
275			panic(ufmt.Sprintf(
276				"liquidity_math: result exceeds uint128 range (z: %s)",
277				z.ToString()))
278		}
279		return z
280	}
281
282	z := u256.Zero().Add(x, yAbs)
283	if z.Lt(x) {
284		panic(ufmt.Sprintf(
285			"liquidity_math: overflow (x: %s, y: %s, z:%s)",
286			x.ToString(), y.ToString(), z.ToString()))
287	}
288	if z.Gt(consts.MaxUint128()) {
289		panic(ufmt.Sprintf(
290			"liquidity_math: result exceeds uint128 range (z: %s)",
291			z.ToString()))
292	}
293	return z
294}
295
296// toAscendingOrder returns the two values in ascending order.
297func toAscendingOrder(a, b *u256.Uint) (*u256.Uint, *u256.Uint) {
298	if a.Gt(b) {
299		return b, a
300	}
301
302	return a, b
303}
304
305// SafeConvertToUint128 safely ensures a *u256.Uint value fits within the uint128 range.
306//
307// This function verifies that the provided unsigned 256-bit integer does not exceed the maximum value for uint128 (`2^128 - 1`).
308// If the value is within the uint128 range, it is returned as is; otherwise, the function triggers a panic.
309//
310// Parameters:
311// - value (*u256.Uint): The unsigned 256-bit integer to be checked.
312//
313// Returns:
314// - *u256.Uint: The same value if it is within the uint128 range.
315//
316// Panics:
317//   - If the value exceeds the maximum uint128 value (`2^128 - 1`), the function will panic with a descriptive error
318//     indicating the overflow and the original value.
319//
320// Notes:
321// - The constant `MAX_UINT128` is defined as `340282366920938463463374607431768211455` (the largest uint128 value).
322// - No actual conversion occurs since the function works directly with *u256.Uint types.
323//
324// Example:
325// validUint128 := SafeConvertToUint128(u256.MustFromDecimal("340282366920938463463374607431768211455")) // Valid
326// SafeConvertToUint128(u256.MustFromDecimal("340282366920938463463374607431768211456")) // Panics due to overflow
327func SafeConvertToUint128(value *u256.Uint) *u256.Uint {
328	if value.Gt(consts.MaxUint128()) {
329		panic(ufmt.Sprintf(
330			"%v: amount(%s) overflows uint128 range",
331			errLiquidityOverflow, value.ToString()))
332	}
333	return value
334}