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

sqrt_price_math.gno

11.77 Kb · 341 lines
  1package gnsmath
  2
  3import (
  4	"gno.land/p/gnoswap/consts/v1"
  5	i256 "gno.land/p/gnoswap/int256/v1"
  6	u256 "gno.land/p/gnoswap/uint256/v1"
  7)
  8
  9// MIN_SQRT_RATIO returns the minimum valid Q64.96 square-root price ratio.
 10//
 11// Returns:
 12//   - minSqrtRatio: A fresh *u256.Uint containing 4,295,128,739, the lower
 13//     boundary accepted by the pool's square-root price math.
 14func MIN_SQRT_RATIO() *u256.Uint { return consts.MinSqrtRatio() }
 15
 16// MAX_SQRT_RATIO returns the upper boundary used by Q64.96 square-root price math.
 17//
 18// Returns:
 19//   - maxSqrtRatio: A fresh *u256.Uint containing
 20//     1461446703485210103287273052203988822378723970342. Inverse tick conversion
 21//     treats this boundary as exclusive.
 22func MAX_SQRT_RATIO() *u256.Uint { return consts.MaxSqrtRatio() }
 23
 24// getNextPriceAmount0Add calculates the next sqrt price when adding token0 liquidity,
 25// rounding up to ensure conservative pricing for the protocol.
 26// This internal function handles the case where token0 is being added to the pool.
 27func getNextPriceAmount0Add(
 28	currentSqrtPriceX96, liquidity, amountToAdd *u256.Uint,
 29) *u256.Uint {
 30	// liquidityShifted = liquidity << 96
 31	liquidityShifted := u256.Zero().Lsh(liquidity, Q96_RESOLUTION)
 32	// amountTimesSqrtPrice = amount * sqrtPrice
 33	amountTimesSqrtPrice := u256.Zero().Mul(amountToAdd, currentSqrtPriceX96)
 34
 35	// Overflow check: Ensure (amountTimesSqrtPrice / amountToAdd) == currentSqrtPriceX96
 36	quotientCheck := u256.Zero().Div(amountTimesSqrtPrice, amountToAdd)
 37	if quotientCheck.Eq(currentSqrtPriceX96) {
 38		// denominator = liquidityShifted + amountTimesSqrtPrice
 39		denominator := u256.Zero().Add(liquidityShifted, amountTimesSqrtPrice)
 40		// only take this path when denominator >= liquidityShifted
 41		if denominator.Gte(liquidityShifted) {
 42			return u256.MulDivRoundingUp(liquidityShifted, currentSqrtPriceX96, denominator)
 43		}
 44	}
 45
 46	// fallback: liquidityShifted / ((liquidityShifted / sqrtPrice) + amount)
 47	divValue := u256.Zero().Div(liquidityShifted, currentSqrtPriceX96)
 48	denominator, overflow := u256.Zero().AddOverflow(divValue, amountToAdd)
 49	if overflow {
 50		panic(errSafeMathOverflow)
 51	}
 52	return u256.DivRoundingUp(liquidityShifted, denominator)
 53}
 54
 55// getNextPriceAmount0Remove calculates the next sqrt price when removing token0 liquidity,
 56// rounding up to ensure conservative pricing for the protocol.
 57// This internal function handles the case where token0 is being removed from the pool.
 58// Panics if validation checks fail (invalid pool sqrt price calculation).
 59func getNextPriceAmount0Remove(
 60	currentSqrtPriceX96, liquidity, amountToRemove *u256.Uint,
 61) *u256.Uint {
 62	// liquidityShifted = liquidity << 96
 63	liquidityShifted := u256.Zero().Lsh(liquidity, Q96_RESOLUTION)
 64	// amountTimesSqrtPrice = amountToRemove * currentSqrtPriceX96
 65	amountTimesSqrtPrice := u256.Zero().Mul(amountToRemove, currentSqrtPriceX96)
 66
 67	// Validation checks
 68	quotientCheck := u256.Zero().Div(amountTimesSqrtPrice, amountToRemove)
 69	if !quotientCheck.Eq(currentSqrtPriceX96) || !liquidityShifted.Gt(amountTimesSqrtPrice) {
 70		panic(errInvalidPoolSqrtPrice)
 71	}
 72
 73	denominator := u256.Zero().Sub(liquidityShifted, amountTimesSqrtPrice)
 74	return u256.MulDivRoundingUp(liquidityShifted, currentSqrtPriceX96, denominator)
 75}
 76
 77// getNextSqrtPriceFromAmount0RoundingUp calculates the next sqrt price based on token0 amount,
 78// always rounding up to ensure conservative pricing in both exact output and exact input cases.
 79// The add parameter determines whether liquidity is being added (true) or removed (false).
 80func getNextSqrtPriceFromAmount0RoundingUp(
 81	sqrtPX96 *u256.Uint,
 82	liquidity *u256.Uint,
 83	amount *u256.Uint,
 84	add bool,
 85) *u256.Uint {
 86	// Shortcut: if no amount, return original price
 87	if amount.IsZero() {
 88		return sqrtPX96
 89	}
 90
 91	if add {
 92		return getNextPriceAmount0Add(sqrtPX96, liquidity, amount)
 93	}
 94	return getNextPriceAmount0Remove(sqrtPX96, liquidity, amount)
 95}
 96
 97// getNextPriceAmount1Add calculates the next sqrt price when adding token1,
 98// preserving rounding-down logic for the final result.
 99// This internal function handles the case where token1 is being added to the pool.
100func getNextPriceAmount1Add(
101	sqrtPX96, liquidity, amount *u256.Uint,
102) *u256.Uint {
103	var quotient *u256.Uint
104
105	if amount.Lte(consts.Max160()) {
106		// Use local variables to avoid allocation conflicts
107		shifted := u256.Zero().Lsh(amount, Q96_RESOLUTION)
108		quotient = u256.Zero().Div(shifted, liquidity)
109	} else {
110		quotient = u256.MulDiv(amount, consts.Q96(), liquidity)
111	}
112
113	result, overflow := u256.Zero().AddOverflow(sqrtPX96, quotient)
114	if overflow || result.Gt(consts.Max160()) {
115		panic(errSqrtPriceOverflow)
116	}
117
118	return result
119}
120
121// getNextPriceAmount1Remove calculates the next sqrt price when removing token1,
122// preserving rounding-down logic for the final result.
123// This internal function handles the case where token1 is being removed from the pool.
124// Panics if sqrt price would exceed quotient.
125func getNextPriceAmount1Remove(
126	sqrtPX96, liquidity, amount *u256.Uint,
127) *u256.Uint {
128	var quotient *u256.Uint
129
130	if amount.Lte(consts.Max160()) {
131		shifted := u256.Zero().Lsh(amount, Q96_RESOLUTION)
132		quotient = u256.DivRoundingUp(shifted, liquidity)
133	} else {
134		quotient = u256.MulDivRoundingUp(amount, consts.Q96(), liquidity)
135	}
136
137	if !sqrtPX96.Gt(quotient) {
138		panic(errSqrtPriceExceedsQuotient)
139	}
140
141	return u256.Zero().Sub(sqrtPX96, quotient)
142}
143
144// getNextSqrtPriceFromAmount1RoundingDown calculates the next sqrt price based on token1 amount,
145// always rounding down to ensure conservative pricing in both exact output and exact input cases.
146// The add parameter determines whether liquidity is being added (true) or removed (false).
147func getNextSqrtPriceFromAmount1RoundingDown(
148	sqrtPX96,
149	liquidity,
150	amount *u256.Uint,
151	add bool,
152) *u256.Uint {
153	// Shortcut: if no amount, return original price
154	if amount.IsZero() {
155		return sqrtPX96
156	}
157
158	if add {
159		return getNextPriceAmount1Add(sqrtPX96, liquidity, amount)
160	}
161	return getNextPriceAmount1Remove(sqrtPX96, liquidity, amount)
162}
163
164// getNextSqrtPriceFromInput calculates the next sqrt price after adding tokens to the pool,
165// rounding up for conservative pricing in both swap directions.
166// The zeroForOne parameter indicates swap direction (token0 for token1 when true).
167// Panics if sqrtPX96 or liquidity is zero.
168func getNextSqrtPriceFromInput(
169	sqrtPX96, liquidity, amountIn *u256.Uint,
170	zeroForOne bool,
171) *u256.Uint {
172	if sqrtPX96.IsZero() {
173		panic(errSqrtPriceZero)
174	}
175
176	if liquidity.IsZero() {
177		panic(errLiquidityZero)
178	}
179
180	if zeroForOne {
181		return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true)
182	}
183
184	return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true)
185}
186
187// getNextSqrtPriceFromOutput calculates the next sqrt price after removing tokens from the pool,
188// using different rounding directions based on swap direction.
189// The zeroForOne parameter indicates swap direction (token0 for token1 when true).
190// Panics if sqrtPX96 or liquidity is zero.
191func getNextSqrtPriceFromOutput(
192	sqrtPX96, liquidity, amountOut *u256.Uint,
193	zeroForOne bool,
194) *u256.Uint {
195	if sqrtPX96.IsZero() {
196		panic(errSqrtPriceZero)
197	}
198
199	if liquidity.IsZero() {
200		panic(errLiquidityZero)
201	}
202
203	if zeroForOne {
204		return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false)
205	}
206
207	return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false)
208}
209
210// getAmount0DeltaHelper calculates the absolute token0 amount difference between two price ranges,
211// automatically swapping inputs to ensure correct ordering. The roundUp parameter controls
212// rounding direction for the final result to ensure conservative AMM calculations.
213// Panics if sqrtRatioAX96 is zero.
214func getAmount0DeltaHelper(
215	sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint,
216	roundUp bool,
217) *u256.Uint {
218	if sqrtRatioAX96.Gt(sqrtRatioBX96) {
219		sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96
220	}
221
222	// Use local variables for thread safety
223	numerator := u256.Zero().Lsh(liquidity, Q96_RESOLUTION)
224	difference := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
225
226	if sqrtRatioAX96.IsZero() {
227		panic(errSqrtRatioAX96Zero)
228	}
229
230	if roundUp {
231		intermediate := u256.MulDivRoundingUp(numerator, difference, sqrtRatioBX96)
232		return u256.DivRoundingUp(intermediate, sqrtRatioAX96)
233	}
234
235	intermediate := u256.MulDiv(numerator, difference, sqrtRatioBX96)
236	return u256.Zero().Div(intermediate, sqrtRatioAX96)
237}
238
239// getAmount1DeltaHelper calculates the absolute token1 amount difference between two price ranges,
240// automatically swapping inputs to ensure correct ordering. The roundUp parameter controls
241// rounding direction for the final result to ensure conservative AMM calculations.
242func getAmount1DeltaHelper(
243	sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint,
244	roundUp bool,
245) *u256.Uint {
246	if sqrtRatioAX96.Gt(sqrtRatioBX96) {
247		sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96
248	}
249
250	// amount1 = liquidity * (sqrtB - sqrtA) / 2^96
251	// Use local variable for thread safety
252	difference := u256.Zero().Sub(sqrtRatioBX96, sqrtRatioAX96)
253
254	if roundUp {
255		return u256.MulDivRoundingUp(liquidity, difference, consts.Q96())
256	}
257
258	return u256.MulDiv(liquidity, difference, consts.Q96())
259}
260
261// GetAmount0Delta computes the signed token0 amount represented between two prices.
262// Positive liquidity rounds the amount up; negative liquidity returns a negative amount
263// rounded down after applying the magnitude.
264//
265// Parameters:
266//   - sqrtRatioAX96: First price endpoint in Q64.96 square-root format.
267//   - sqrtRatioBX96: Second price endpoint in Q64.96 square-root format.
268//   - liquidity: Signed liquidity value; its sign determines the result sign and rounding.
269//
270// Returns:
271//   - amount0Delta: Signed int256 token0 amount represented by the range.
272//
273// Panics if an input is nil or the computed magnitude cannot be represented by int256.
274func GetAmount0Delta(
275	sqrtRatioAX96, sqrtRatioBX96 *u256.Uint,
276	liquidity *i256.Int,
277) *i256.Int {
278	if sqrtRatioAX96 == nil || sqrtRatioBX96 == nil || liquidity == nil {
279		panic(errGetAmount0DeltaNilInput)
280	}
281
282	if liquidity.IsNeg() {
283		u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false)
284		if u.Gt(consts.MaxInt256()) {
285			// if u > (2**255 - 1), cannot cast to int256
286			panic(errAmount0DeltaOverflow)
287		}
288
289		// Convert to i256 and negate properly
290		return i256.Zero().Neg(i256.FromUint256(u))
291	}
292
293	u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true)
294	if u.Gt(consts.MaxInt256()) {
295		// if u > (2**255 - 1), cannot cast to int256
296		panic(errAmount0DeltaOverflow)
297	}
298
299	return i256.FromUint256(u)
300}
301
302// GetAmount1Delta computes the signed token1 amount represented between two prices.
303// Positive liquidity rounds the amount up; negative liquidity returns a negative amount
304// rounded down after applying the magnitude.
305//
306// Parameters:
307//   - sqrtRatioAX96: First price endpoint in Q64.96 square-root format.
308//   - sqrtRatioBX96: Second price endpoint in Q64.96 square-root format.
309//   - liquidity: Signed liquidity value; its sign determines the result sign and rounding.
310//
311// Returns:
312//   - amount1Delta: Signed int256 token1 amount represented by the range.
313//
314// Panics if an input is nil or the computed magnitude cannot be represented by int256.
315func GetAmount1Delta(
316	sqrtRatioAX96, sqrtRatioBX96 *u256.Uint,
317	liquidity *i256.Int,
318) *i256.Int {
319	if sqrtRatioAX96 == nil || sqrtRatioBX96 == nil || liquidity == nil {
320		panic(errGetAmount1DeltaNilInput)
321	}
322
323	if liquidity.IsNeg() {
324		u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false)
325		if u.Gt(consts.MaxInt256()) {
326			// if u > (2**255 - 1), cannot cast to int256
327			panic(errAmount1DeltaOverflow)
328		}
329
330		// Convert to i256 and negate properly
331		return i256.Zero().Neg(i256.FromUint256(u))
332	}
333
334	u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true)
335	if u.Gt(consts.MaxInt256()) {
336		// if u > (2**255 - 1), cannot cast to int256
337		panic(errAmount1DeltaOverflow)
338	}
339
340	return i256.FromUint256(u)
341}