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