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

tick.gno

19.53 Kb · 511 lines
  1package pool
  2
  3import (
  4	"chain"
  5
  6	"gno.land/p/gnoswap/consts"
  7	"gno.land/p/gnoswap/gnsmath"
  8	ufmt "gno.land/p/nt/ufmt/v0"
  9
 10	i256 "gno.land/p/gnoswap/int256"
 11	u256 "gno.land/p/gnoswap/uint256"
 12	pl "gno.land/r/gnoswap/pool"
 13)
 14
 15const (
 16	MAX_LIQUIDITY_PER_TICK_SPACING_1         = "191757530477355301479181766273477"
 17	MAX_LIQUIDITY_PER_TICK_SPACING_10        = "1917569901783203986719870431555990"
 18	MAX_LIQUIDITY_PER_TICK_SPACING_60        = "11505743598341114571880798222544994"
 19	MAX_LIQUIDITY_PER_TICK_SPACING_200       = "38350317471085141830651933667504588"
 20	MIN_TICK                           int32 = -887272
 21	MAX_TICK                           int32 = 887272
 22)
 23
 24// maxLiquidityPerTickSpacing* return the precomputed max-liquidity-per-tick for
 25// each supported tick spacing. They are constructors (not package-level vars) so
 26// each caller receives a fresh instance — calculateMaxLiquidityPerTick returns
 27// the value directly to callers, and a shared singleton could otherwise be
 28// mutated in place and corrupt every caller. Values are built from little-endian
 29// [4]uint64 literals to avoid runtime decimal parsing.
 30func maxLiquidityPerTickSpacing1FromDec() *u256.Uint {
 31	return &u256.Uint{3639524637645646277, 10395196556700, 0, 0} // 191757530477355301479181766273477
 32}
 33
 34func maxLiquidityPerTickSpacing10FromDec() *u256.Uint {
 35	return &u256.Uint{4727306266354938262, 103951672670308, 0, 0} // 1917569901783203986719870431555990
 36}
 37
 38func maxLiquidityPerTickSpacing60FromDec() *u256.Uint {
 39	return &u256.Uint{1428959955126579298, 623727610269131, 0, 0} // 11505743598341114571880798222544994
 40}
 41
 42func maxLiquidityPerTickSpacing200FromDec() *u256.Uint {
 43	return &u256.Uint{6592429331424883148, 2078974875882965, 0, 0} // 38350317471085141830651933667504588
 44}
 45
 46// GetTickLiquidityGross returns the gross liquidity for the specified tick.
 47func GetTickLiquidityGross(p *pl.Pool, tick int32) string {
 48	return mustGetTick(p, tick).LiquidityGross()
 49}
 50
 51// GetTickLiquidityNet returns the net liquidity for the specified tick.
 52func GetTickLiquidityNet(p *pl.Pool, tick int32) string {
 53	return mustGetTick(p, tick).LiquidityNet()
 54}
 55
 56// GetTickFeeGrowthOutside0X128 returns the fee growth outside the tick for token 0.
 57func GetTickFeeGrowthOutside0X128(p *pl.Pool, tick int32) string {
 58	return mustGetTick(p, tick).FeeGrowthOutside0X128()
 59}
 60
 61// GetTickFeeGrowthOutside1X128 returns the fee growth outside the tick for token 1.
 62func GetTickFeeGrowthOutside1X128(p *pl.Pool, tick int32) string {
 63	return mustGetTick(p, tick).FeeGrowthOutside1X128()
 64}
 65
 66// GetTickCumulativeOutside returns the cumulative liquidity outside the tick.
 67func GetTickCumulativeOutside(p *pl.Pool, tick int32) int64 {
 68	return mustGetTick(p, tick).TickCumulativeOutside()
 69}
 70
 71// GetTickSecondsPerLiquidityOutsideX128 returns the seconds per liquidity outside the tick.
 72func GetTickSecondsPerLiquidityOutsideX128(p *pl.Pool, tick int32) string {
 73	return mustGetTick(p, tick).SecondsPerLiquidityOutsideX128()
 74}
 75
 76// GetTickSecondsOutside returns the seconds outside the tick.
 77func GetTickSecondsOutside(p *pl.Pool, tick int32) uint32 {
 78	return mustGetTick(p, tick).SecondsOutside()
 79}
 80
 81// GetTickInitialized returns whether the tick is initialized.
 82func GetTickInitialized(p *pl.Pool, tick int32) bool {
 83	return mustGetTick(p, tick).Initialized()
 84}
 85
 86// getFeeGrowthInside calculates the fee growth within a specified tick range.
 87//
 88// This function computes the accumulated fee growth for token 0 and token 1 inside a given tick range
 89// (`tickLower` to `tickUpper`) relative to the current tick position (`tickCurrent`). It isolates the fee
 90// growth within the range by subtracting the fee growth below the lower tick and above the upper tick
 91// from the global fee growth.
 92//
 93// Parameters:
 94//   - tickLower: int32, the lower tick boundary of the range.
 95//   - tickUpper: int32, the upper tick boundary of the range.
 96//   - tickCurrent: int32, the current tick index.
 97//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
 98//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
 99//
100// Returns:
101//   - *u256.Uint: Fee growth inside the tick range for token 0.
102//   - *u256.Uint: Fee growth inside the tick range for token 1.
103//
104// Workflow:
105//  1. Retrieve the tick information (`lower` and `upper`) for the lower and upper tick boundaries
106//     using `p.getTick`.
107//  2. Calculate the fee growth below the lower tick using `getFeeGrowthBelowX128`.
108//  3. Calculate the fee growth above the upper tick using `getFeeGrowthAboveX128`.
109//  4. Subtract the fee growth below and above the range from the global fee growth values:
110//     feeGrowthInside = feeGrowthGlobal - feeGrowthBelow - feeGrowthAbove
111//  5. Return the computed fee growth values for token 0 and token 1 within the range.
112//
113// Behavior:
114//   - The fee growth is isolated within the range `[tickLower, tickUpper]`.
115//   - The function ensures the calculations accurately consider the tick boundaries and the current tick position.
116//
117// Example:
118//
119// ```gno
120//
121//	feeGrowth0, feeGrowth1 := pool.getFeeGrowthInside(
122//	    100, 200, 150, globalFeeGrowth0, globalFeeGrowth1,
123//	)
124//	println("Fee Growth Inside (Token 0):", feeGrowth0)
125//	println("Fee Growth Inside (Token 1):", feeGrowth1)
126//
127// ```
128func getFeeGrowthInside(
129	p *pl.Pool,
130	tickLower int32,
131	tickUpper int32,
132	tickCurrent int32,
133	feeGrowthGlobal0X128 *u256.Uint,
134	feeGrowthGlobal1X128 *u256.Uint,
135) (*u256.Uint, *u256.Uint) {
136	lower := getTick(p, tickLower)
137	upper := getTick(p, tickUpper)
138
139	feeGrowthBelow0X128, feeGrowthBelow1X128 := getFeeGrowthBelowX128(tickLower, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128, lower)
140	feeGrowthAbove0X128, feeGrowthAbove1X128 := getFeeGrowthAboveX128(tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128, upper)
141
142	feeGrowthInside0X128 := u256.Zero().Sub(u256.Zero().Sub(feeGrowthGlobal0X128, feeGrowthBelow0X128), feeGrowthAbove0X128)
143	feeGrowthInside1X128 := u256.Zero().Sub(u256.Zero().Sub(feeGrowthGlobal1X128, feeGrowthBelow1X128), feeGrowthAbove1X128)
144
145	return feeGrowthInside0X128, feeGrowthInside1X128
146}
147
148// tickUpdate updates the state of a specific tick.
149//
150// This function applies a given liquidity change (liquidityDelta) to the specified tick, updates
151// the fee growth values if necessary, and adjusts the net liquidity based on whether the tick
152// is an upper or lower boundary. It also verifies that the total liquidity does not exceed the
153// maximum allowed value and ensures the net liquidity stays within the valid int128 range.
154//
155// Parameters:
156//   - tick:          int32, the index of the tick to update.
157//   - tickCurrent:   int32, the current active tick index.
158//   - liquidityDelta: *i256.Int, the amount of liquidity to add or remove.
159//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth value for token 0.
160//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth value for token 1.
161//   - upper:         bool, indicates if this is the upper boundary (true for upper, false for lower).
162//   - maxLiquidity:  *u256.Uint, the maximum allowed liquidity.
163//
164// Returns:
165//   - flipped: bool, indicates if the tick's initialization state has changed.
166//     (e.g., liquidity transitioning from zero to non-zero, or vice versa)
167//
168// Workflow:
169// 1. Nil input values are replaced with zero.
170// 2. The function retrieves the tick information for the specified tick index.
171// 3. Applies the liquidityDelta to compute the new total liquidity (liquidityGross).
172//   - If the total liquidity exceeds the maximum allowed value, the function panics.
173//     4. Checks whether the tick's initialized state has changed and sets the `flipped` flag.
174//     5. If the tick was previously uninitialized and its index is less than or equal to the current tick,
175//     the fee growth values are initialized to the current global values.
176//     6. Updates the tick's net liquidity:
177//   - For an upper boundary, it subtracts liquidityDelta.
178//   - For a lower boundary, it adds liquidityDelta.
179//   - Ensures the net liquidity remains within the int128 range using `checkOverFlowInt128`.
180//     7. Updates the tick's state with the new values.
181//     8. Returns whether the tick's initialized state has flipped.
182//
183// Panic Conditions:
184// - The total liquidity (liquidityGross) exceeds the maximum allowed liquidity (maxLiquidity).
185// - The net liquidity (liquidityNet) exceeds the int128 range.
186//
187// Example:
188//
189// ```gno
190//
191//	flipped := pool.tickUpdate(10, 5, liquidityDelta, feeGrowth0, feeGrowth1, true, maxLiquidity)
192//	println("Tick flipped:", flipped)
193//
194// ```
195func tickUpdate(
196	p *pl.Pool,
197	tick int32,
198	tickCurrent int32,
199	liquidityDelta *i256.Int,
200	feeGrowthGlobal0X128 *u256.Uint,
201	feeGrowthGlobal1X128 *u256.Uint,
202	upper bool,
203	maxLiquidity *u256.Uint,
204) (flipped bool) {
205	tickInfo := getTick(p, tick)
206
207	liquidityGrossBefore := u256.MustFromDecimal(tickInfo.LiquidityGross())
208	liquidityGrossAfter := gnsmath.LiquidityMathAddDelta(liquidityGrossBefore, liquidityDelta)
209
210	if !liquidityGrossAfter.Lte(maxLiquidity) {
211		panic(newErrorWithDetail(
212			errLiquidityCalculation,
213			ufmt.Sprintf("liquidityGrossAfter(%s) overflows maxLiquidity(%s)", liquidityGrossAfter.ToString(), maxLiquidity.ToString()),
214		))
215	}
216
217	flipped = liquidityGrossAfter.IsZero() != liquidityGrossBefore.IsZero()
218
219	if liquidityGrossBefore.IsZero() {
220		if tick <= tickCurrent {
221			tickInfo.SetFeeGrowthOutside0X128(feeGrowthGlobal0X128.ToString())
222			tickInfo.SetFeeGrowthOutside1X128(feeGrowthGlobal1X128.ToString())
223		}
224		tickInfo.SetInitialized(true)
225	}
226
227	tickInfo.SetLiquidityGross(liquidityGrossAfter.ToString())
228
229	liquidityNet := i256.MustFromDecimal(tickInfo.LiquidityNet())
230	if upper {
231		newLiquidityNet := i256.Zero().Sub(liquidityNet, liquidityDelta)
232		checkOverFlowInt128(newLiquidityNet)
233		tickInfo.SetLiquidityNet(newLiquidityNet.ToString())
234	} else {
235		newLiquidityNet := i256.Zero().Add(liquidityNet, liquidityDelta)
236		checkOverFlowInt128(newLiquidityNet)
237		tickInfo.SetLiquidityNet(newLiquidityNet.ToString())
238	}
239
240	setTick(p, tick, tickInfo)
241
242	return flipped
243}
244
245// tickCross updates a tick's state when it is crossed and returns the liquidity net.
246// Updates fee growth and oracle accumulator values for the tick.
247func tickCross(
248	p *pl.Pool,
249	tick int32,
250	feeGrowthGlobal0X128 *u256.Uint,
251	feeGrowthGlobal1X128 *u256.Uint,
252	secondsPerLiquidityCumulativeX128 *u256.Uint,
253	tickCumulative int64,
254	blockTimestamp int64,
255) *i256.Int {
256	thisTick := getTick(p, tick)
257
258	feeOutside0 := u256.MustFromDecimal(thisTick.FeeGrowthOutside0X128())
259	feeOutside1 := u256.MustFromDecimal(thisTick.FeeGrowthOutside1X128())
260	thisTick.SetFeeGrowthOutside0X128(u256.Zero().Sub(feeGrowthGlobal0X128, feeOutside0).ToString())
261	thisTick.SetFeeGrowthOutside1X128(u256.Zero().Sub(feeGrowthGlobal1X128, feeOutside1).ToString())
262
263	tickSecondsPerLiquidity := u256.MustFromDecimal(thisTick.SecondsPerLiquidityOutsideX128())
264	thisTick.SetSecondsPerLiquidityOutsideX128(u256.Zero().Sub(secondsPerLiquidityCumulativeX128, tickSecondsPerLiquidity).ToString())
265	thisTick.SetTickCumulativeOutside(tickCumulative - thisTick.TickCumulativeOutside())
266	thisTick.SetSecondsOutside(uint32(blockTimestamp) - thisTick.SecondsOutside())
267
268	setTick(p, tick, thisTick)
269
270	chain.Emit(
271		"PoolTickCross",
272		"poolPath", p.PoolPath(),
273		"tick", NewTickEventInfo(tick, thisTick).ToString(),
274	)
275
276	return i256.MustFromDecimal(thisTick.LiquidityNet())
277}
278
279// setTick updates the tick data for the specified tick index in the pool.
280func setTick(p *pl.Pool, tick int32, newTickInfo pl.TickInfo) {
281	p.SetTick(tick, newTickInfo)
282}
283
284// deleteTick deletes the tick data for the specified tick index in the pool.
285func deleteTick(p *pl.Pool, tick int32) {
286	p.DeleteTick(tick)
287}
288
289// getTick retrieves the TickInfo associated with the specified tick index from the pool.
290// If the TickInfo contains any nil fields, they are replaced with zero values using valueOrZero.
291//
292// Parameters:
293// - tick: The tick index (int32) for which the TickInfo is to be retrieved.
294//
295// Behavior:
296// - Retrieves the TickInfo for the given tick from the pool's tick map.
297// - Ensures that all fields of TickInfo are non-nil by calling valueOrZero, which replaces nil values with zero.
298// - Returns the updated TickInfo.
299//
300// Returns:
301// - TickInfo: The tick data with all fields guaranteed to have valid values (nil fields are set to zero).
302//
303// Use Case:
304// This function ensures the retrieved tick data is always valid and safe for further operations,
305// such as calculations or updates, by sanitizing nil fields in the TickInfo structure.
306func getTick(p *pl.Pool, tick int32) pl.TickInfo {
307	tickInfo, err := p.GetTick(tick)
308	if err != nil {
309		return pl.NewTickInfo()
310	}
311
312	return tickInfo
313}
314
315// mustGetTick retrieves the TickInfo for a specific tick, panicking if the tick does not exist.
316//
317// This function ensures that the requested tick data exists in the pool's tick mapping.
318// If the tick does not exist, it panics with an appropriate error message.
319//
320// Parameters:
321//   - tick: int32, the index of the tick to retrieve.
322//
323// Returns:
324//   - TickInfo: The information associated with the specified tick.
325//
326// Behavior:
327//   - Checks if the tick exists in the pool's tick mapping (`p.ticks`).
328//   - If the tick exists, it returns the corresponding `TickInfo`.
329//   - If the tick does not exist, the function panics with a descriptive error.
330//
331// Panic Conditions:
332//   - The specified tick does not exist in the pool's mapping.
333//
334// Example:
335//
336// ```gno
337//
338//	tickInfo := pool.mustGetTick(10)
339//	ufmt.Println("Tick Info:", tickInfo)
340//
341// ```
342func mustGetTick(p *pl.Pool, tick int32) *pl.TickInfo {
343	tickInfo, err := p.GetTick(tick)
344	if err != nil {
345		panic(err)
346	}
347
348	return &tickInfo
349}
350
351// calculateMaxLiquidityPerTick calculates the maximum liquidity
352// per tick for a given tick spacing.
353func calculateMaxLiquidityPerTick(tickSpacing int32) *u256.Uint {
354	switch tickSpacing {
355	case 1:
356		return maxLiquidityPerTickSpacing1FromDec()
357	case 10:
358		return maxLiquidityPerTickSpacing10FromDec()
359	case 60:
360		return maxLiquidityPerTickSpacing60FromDec()
361	case 200:
362		return maxLiquidityPerTickSpacing200FromDec()
363	default:
364		minTick := (MIN_TICK / tickSpacing) * tickSpacing
365		maxTick := (MAX_TICK / tickSpacing) * tickSpacing
366		numTicks := uint64((maxTick-minTick)/tickSpacing) + 1
367
368		return u256.Zero().Div(consts.MaxUint128(), u256.NewUint(numTicks))
369	}
370}
371
372// getFeeGrowthBelowX128 calculates the fee growth below a specified tick.
373//
374// This function computes the fee growth for token 0 and token 1 below a given tick (`tickLower`)
375// relative to the current tick (`tickCurrent`). The fee growth values are adjusted based on whether
376// the `tickCurrent` is above or below the `tickLower`.
377//
378// Parameters:
379//   - tickLower: int32, the lower tick boundary for fee calculation.
380//   - tickCurrent: int32, the current tick index.
381//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
382//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
383//   - lowerTick: TickInfo, the fee growth and liquidity details for the lower tick.
384//
385// Returns:
386//   - *u256.Uint: Fee growth below `tickLower` for token 0.
387//   - *u256.Uint: Fee growth below `tickLower` for token 1.
388//
389// Workflow:
390//  1. If `tickCurrent` is greater than or equal to `tickLower`:
391//     - Return the `feeGrowthOutside0X128` and `feeGrowthOutside1X128` values of the `lowerTick`.
392//  2. If `tickCurrent` is below `tickLower`:
393//     - Compute the fee growth below the lower tick by subtracting `feeGrowthOutside` values
394//     from the global fee growth values (`feeGrowthGlobal0X128` and `feeGrowthGlobal1X128`).
395//  3. Return the calculated fee growth values for both tokens.
396//
397// Behavior:
398//   - If `tickCurrent >= tickLower`, the fee growth outside the lower tick is returned as-is.
399//   - If `tickCurrent < tickLower`, the fee growth is calculated as:
400//     feeGrowthBelow = feeGrowthGlobal - feeGrowthOutside
401//
402// Example:
403//
404// ```gno
405//
406//	feeGrowth0, feeGrowth1 := getFeeGrowthBelowX128(
407//	    100, 150, globalFeeGrowth0, globalFeeGrowth1, lowerTickInfo,
408//	)
409//	println("Fee Growth Below:", feeGrowth0, feeGrowth1)
410func getFeeGrowthBelowX128(
411	tickLower, tickCurrent int32,
412	feeGrowthGlobal0X128, feeGrowthGlobal1X128 *u256.Uint,
413	lowerTick pl.TickInfo,
414) (*u256.Uint, *u256.Uint) {
415	feeOutside0 := u256.MustFromDecimal(lowerTick.FeeGrowthOutside0X128())
416	feeOutside1 := u256.MustFromDecimal(lowerTick.FeeGrowthOutside1X128())
417
418	if tickCurrent >= tickLower {
419		return feeOutside0, feeOutside1
420	}
421
422	feeGrowthBelow0X128 := u256.Zero().Sub(feeGrowthGlobal0X128, feeOutside0)
423	feeGrowthBelow1X128 := u256.Zero().Sub(feeGrowthGlobal1X128, feeOutside1)
424
425	return feeGrowthBelow0X128, feeGrowthBelow1X128
426}
427
428// getFeeGrowthAboveX128 calculates the fee growth above a specified tick.
429//
430// This function computes the fee growth for token 0 and token 1 above a given tick (`tickUpper`)
431// relative to the current tick (`tickCurrent`). The fee growth values are adjusted based on whether
432// the `tickCurrent` is above or below the `tickUpper`.
433//
434// Parameters:
435//   - tickUpper: int32, the upper tick boundary for fee calculation.
436//   - tickCurrent: int32, the current tick index.
437//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
438//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
439//   - upperTick: TickInfo, the fee growth and liquidity details for the upper tick.
440//
441// Returns:
442//   - *u256.Uint: Fee growth above `tickUpper` for token 0.
443//   - *u256.Uint: Fee growth above `tickUpper` for token 1.
444//
445// Workflow:
446//  1. If `tickCurrent` is less than `tickUpper`:
447//     - Return the `feeGrowthOutside0X128` and `feeGrowthOutside1X128` values of the `upperTick`.
448//  2. If `tickCurrent` is greater than or equal to `tickUpper`:
449//     - Compute the fee growth above the upper tick by subtracting `feeGrowthOutside` values
450//     from the global fee growth values (`feeGrowthGlobal0X128` and `feeGrowthGlobal1X128`).
451//  3. Return the calculated fee growth values for both tokens.
452//
453// Behavior:
454//   - If `tickCurrent < tickUpper`, the fee growth outside the upper tick is returned as-is.
455//   - If `tickCurrent >= tickUpper`, the fee growth is calculated as:
456//     feeGrowthAbove = feeGrowthGlobal - feeGrowthOutside
457//
458// Example:
459//
460//	feeGrowth0, feeGrowth1 := getFeeGrowthAboveX128(
461//	    200, 150, globalFeeGrowth0, globalFeeGrowth1, upperTickInfo,
462//	)
463//	println("Fee Growth Above:", feeGrowth0, feeGrowth1)
464//
465// ```
466func getFeeGrowthAboveX128(
467	tickUpper, tickCurrent int32,
468	feeGrowthGlobal0X128, feeGrowthGlobal1X128 *u256.Uint,
469	upperTick pl.TickInfo,
470) (*u256.Uint, *u256.Uint) {
471	feeOutside0 := u256.MustFromDecimal(upperTick.FeeGrowthOutside0X128())
472	feeOutside1 := u256.MustFromDecimal(upperTick.FeeGrowthOutside1X128())
473
474	if tickCurrent < tickUpper {
475		return feeOutside0, feeOutside1
476	}
477
478	feeGrowthAbove0X128 := u256.Zero().Sub(feeGrowthGlobal0X128, feeOutside0)
479	feeGrowthAbove1X128 := u256.Zero().Sub(feeGrowthGlobal1X128, feeOutside1)
480
481	return feeGrowthAbove0X128, feeGrowthAbove1X128
482}
483
484// validateTicks validates the tick range for a liquidity position.
485//
486// This function performs three essential checks to ensure the provided
487// tick values are valid before creating or modifying a liquidity position.
488func validateTicks(tickLower, tickUpper int32) error {
489	if tickLower >= tickUpper {
490		return makeErrorWithDetails(
491			errInvalidTickRange,
492			ufmt.Sprintf("tickLower(%d), tickUpper(%d)", tickLower, tickUpper),
493		)
494	}
495
496	if tickLower < MIN_TICK {
497		return makeErrorWithDetails(
498			errTickLowerInvalid,
499			ufmt.Sprintf("tickLower(%d) < MIN_TICK(%d)", tickLower, MIN_TICK),
500		)
501	}
502
503	if tickUpper > MAX_TICK {
504		return makeErrorWithDetails(
505			errTickUpperInvalid,
506			ufmt.Sprintf("tickUpper(%d) > MAX_TICK(%d)", tickUpper, MAX_TICK),
507		)
508	}
509
510	return nil
511}