Search Apps Documentation Source Content File Folder Download Copy Actions Download

liquidity_amounts.gno

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