Search Apps Documentation Source Content File Folder Download Copy Actions Download

liquidity_math.gno

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