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}