Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

tick.gno

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