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}