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

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}