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