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