Search Apps Documentation Source Content File Folder Download Copy Actions Download

tick.gno

17.66 Kb ยท 463 lines
  1package v1
  2
  3import (
  4	"gno.land/p/nt/ufmt"
  5
  6	i256 "gno.land/p/gnoswap/int256"
  7	u256 "gno.land/p/gnoswap/uint256"
  8
  9	"gno.land/r/gnoswap/common"
 10	pl "gno.land/r/gnoswap/pool"
 11)
 12
 13const (
 14	MIN_TICK int32 = -887272
 15	MAX_TICK int32 = 887272
 16)
 17
 18// GetTickLiquidityGross returns the gross liquidity for the specified tick.
 19func GetTickLiquidityGross(p *pl.Pool, tick int32) *u256.Uint {
 20	return mustGetTick(p, tick).LiquidityGross()
 21}
 22
 23// GetTickLiquidityNet returns the net liquidity for the specified tick.
 24func GetTickLiquidityNet(p *pl.Pool, tick int32) *i256.Int {
 25	return mustGetTick(p, tick).LiquidityNet()
 26}
 27
 28// GetTickFeeGrowthOutside0X128 returns the fee growth outside the tick for token 0.
 29func GetTickFeeGrowthOutside0X128(p *pl.Pool, tick int32) *u256.Uint {
 30	return mustGetTick(p, tick).FeeGrowthOutside0X128()
 31}
 32
 33// GetTickFeeGrowthOutside1X128 returns the fee growth outside the tick for token 1.
 34func GetTickFeeGrowthOutside1X128(p *pl.Pool, tick int32) *u256.Uint {
 35	return mustGetTick(p, tick).FeeGrowthOutside1X128()
 36}
 37
 38// GetTickCumulativeOutside returns the cumulative liquidity outside the tick.
 39func GetTickCumulativeOutside(p *pl.Pool, tick int32) int64 {
 40	return mustGetTick(p, tick).TickCumulativeOutside()
 41}
 42
 43// GetTickSecondsPerLiquidityOutsideX128 returns the seconds per liquidity outside the tick.
 44func GetTickSecondsPerLiquidityOutsideX128(p *pl.Pool, tick int32) *u256.Uint {
 45	return mustGetTick(p, tick).SecondsPerLiquidityOutsideX128()
 46}
 47
 48// GetTickSecondsOutside returns the seconds outside the tick.
 49func GetTickSecondsOutside(p *pl.Pool, tick int32) uint32 {
 50	return mustGetTick(p, tick).SecondsOutside()
 51}
 52
 53// GetTickInitialized returns whether the tick is initialized.
 54func GetTickInitialized(p *pl.Pool, tick int32) bool {
 55	return mustGetTick(p, tick).Initialized()
 56}
 57
 58// getFeeGrowthInside calculates the fee growth within a specified tick range.
 59//
 60// This function computes the accumulated fee growth for token 0 and token 1 inside a given tick range
 61// (`tickLower` to `tickUpper`) relative to the current tick position (`tickCurrent`). It isolates the fee
 62// growth within the range by subtracting the fee growth below the lower tick and above the upper tick
 63// from the global fee growth.
 64//
 65// Parameters:
 66//   - tickLower: int32, the lower tick boundary of the range.
 67//   - tickUpper: int32, the upper tick boundary of the range.
 68//   - tickCurrent: int32, the current tick index.
 69//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
 70//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
 71//
 72// Returns:
 73//   - *u256.Uint: Fee growth inside the tick range for token 0.
 74//   - *u256.Uint: Fee growth inside the tick range for token 1.
 75//
 76// Workflow:
 77//  1. Retrieve the tick information (`lower` and `upper`) for the lower and upper tick boundaries
 78//     using `p.getTick`.
 79//  2. Calculate the fee growth below the lower tick using `getFeeGrowthBelowX128`.
 80//  3. Calculate the fee growth above the upper tick using `getFeeGrowthAboveX128`.
 81//  4. Subtract the fee growth below and above the range from the global fee growth values:
 82//     feeGrowthInside = feeGrowthGlobal - feeGrowthBelow - feeGrowthAbove
 83//  5. Return the computed fee growth values for token 0 and token 1 within the range.
 84//
 85// Behavior:
 86//   - The fee growth is isolated within the range `[tickLower, tickUpper]`.
 87//   - The function ensures the calculations accurately consider the tick boundaries and the current tick position.
 88//
 89// Example:
 90//
 91// ```gno
 92//
 93//	feeGrowth0, feeGrowth1 := pool.getFeeGrowthInside(
 94//	    100, 200, 150, globalFeeGrowth0, globalFeeGrowth1,
 95//	)
 96//	println("Fee Growth Inside (Token 0):", feeGrowth0)
 97//	println("Fee Growth Inside (Token 1):", feeGrowth1)
 98//
 99// ```
100func getFeeGrowthInside(
101	p *pl.Pool,
102	tickLower int32,
103	tickUpper int32,
104	tickCurrent int32,
105	feeGrowthGlobal0X128 *u256.Uint,
106	feeGrowthGlobal1X128 *u256.Uint,
107) (*u256.Uint, *u256.Uint) {
108	lower := getTick(p, tickLower)
109	upper := getTick(p, tickUpper)
110
111	feeGrowthBelow0X128, feeGrowthBelow1X128 := getFeeGrowthBelowX128(tickLower, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128, lower)
112	feeGrowthAbove0X128, feeGrowthAbove1X128 := getFeeGrowthAboveX128(tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128, upper)
113
114	feeGrowthInside0X128 := u256.Zero().Sub(u256.Zero().Sub(feeGrowthGlobal0X128, feeGrowthBelow0X128), feeGrowthAbove0X128)
115	feeGrowthInside1X128 := u256.Zero().Sub(u256.Zero().Sub(feeGrowthGlobal1X128, feeGrowthBelow1X128), feeGrowthAbove1X128)
116
117	return feeGrowthInside0X128, feeGrowthInside1X128
118}
119
120// tickUpdate updates the state of a specific tick.
121//
122// This function applies a given liquidity change (liquidityDelta) to the specified tick, updates
123// the fee growth values if necessary, and adjusts the net liquidity based on whether the tick
124// is an upper or lower boundary. It also verifies that the total liquidity does not exceed the
125// maximum allowed value and ensures the net liquidity stays within the valid int128 range.
126//
127// Parameters:
128//   - tick:          int32, the index of the tick to update.
129//   - tickCurrent:   int32, the current active tick index.
130//   - liquidityDelta: *i256.Int, the amount of liquidity to add or remove.
131//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth value for token 0.
132//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth value for token 1.
133//   - upper:         bool, indicates if this is the upper boundary (true for upper, false for lower).
134//   - maxLiquidity:  *u256.Uint, the maximum allowed liquidity.
135//
136// Returns:
137//   - flipped: bool, indicates if the tick's initialization state has changed.
138//     (e.g., liquidity transitioning from zero to non-zero, or vice versa)
139//
140// Workflow:
141// 1. Nil input values are replaced with zero.
142// 2. The function retrieves the tick information for the specified tick index.
143// 3. Applies the liquidityDelta to compute the new total liquidity (liquidityGross).
144//   - If the total liquidity exceeds the maximum allowed value, the function panics.
145//     4. Checks whether the tick's initialized state has changed and sets the `flipped` flag.
146//     5. If the tick was previously uninitialized and its index is less than or equal to the current tick,
147//     the fee growth values are initialized to the current global values.
148//     6. Updates the tick's net liquidity:
149//   - For an upper boundary, it subtracts liquidityDelta.
150//   - For a lower boundary, it adds liquidityDelta.
151//   - Ensures the net liquidity remains within the int128 range using `checkOverFlowInt128`.
152//     7. Updates the tick's state with the new values.
153//     8. Returns whether the tick's initialized state has flipped.
154//
155// Panic Conditions:
156// - The total liquidity (liquidityGross) exceeds the maximum allowed liquidity (maxLiquidity).
157// - The net liquidity (liquidityNet) exceeds the int128 range.
158//
159// Example:
160//
161// ```gno
162//
163//	flipped := pool.tickUpdate(10, 5, liquidityDelta, feeGrowth0, feeGrowth1, true, maxLiquidity)
164//	println("Tick flipped:", flipped)
165//
166// ```
167func tickUpdate(
168	p *pl.Pool,
169	tick int32,
170	tickCurrent int32,
171	liquidityDelta *i256.Int,
172	feeGrowthGlobal0X128 *u256.Uint,
173	feeGrowthGlobal1X128 *u256.Uint,
174	upper bool,
175	maxLiquidity *u256.Uint,
176) (flipped bool) {
177	tickInfo := getTick(p, tick)
178
179	liquidityGrossBefore := tickInfo.LiquidityGross().Clone()
180	liquidityGrossAfter := common.LiquidityMathAddDelta(liquidityGrossBefore, liquidityDelta)
181
182	if !liquidityGrossAfter.Lte(maxLiquidity) {
183		panic(newErrorWithDetail(
184			errLiquidityCalculation,
185			ufmt.Sprintf("liquidityGrossAfter(%s) overflows maxLiquidity(%s)", liquidityGrossAfter.ToString(), maxLiquidity.ToString()),
186		))
187	}
188
189	flipped = liquidityGrossAfter.IsZero() != liquidityGrossBefore.IsZero()
190
191	if liquidityGrossBefore.IsZero() {
192		if tick <= tickCurrent {
193			tickInfo.SetFeeGrowthOutside0X128(feeGrowthGlobal0X128.Clone())
194			tickInfo.SetFeeGrowthOutside1X128(feeGrowthGlobal1X128.Clone())
195		}
196		tickInfo.SetInitialized(true)
197	}
198
199	tickInfo.SetLiquidityGross(liquidityGrossAfter.Clone())
200
201	if upper {
202		tickInfo.SetLiquidityNet(i256.Zero().Sub(tickInfo.LiquidityNet(), liquidityDelta))
203		checkOverFlowInt128(tickInfo.LiquidityNet())
204	} else {
205		tickInfo.SetLiquidityNet(i256.Zero().Add(tickInfo.LiquidityNet(), liquidityDelta))
206		checkOverFlowInt128(tickInfo.LiquidityNet())
207	}
208
209	setTick(p, tick, tickInfo)
210
211	return flipped
212}
213
214// tickCross updates a tick's state when it is crossed and returns the liquidity net.
215// Updates fee growth and oracle accumulator values for the tick.
216func tickCross(
217	p *pl.Pool,
218	tick int32,
219	feeGrowthGlobal0X128 *u256.Uint,
220	feeGrowthGlobal1X128 *u256.Uint,
221	secondsPerLiquidityCumulativeX128 *u256.Uint,
222	tickCumulative int64,
223	blockTimestamp int64,
224) *i256.Int {
225	thisTick := getTick(p, tick)
226
227	thisTick.SetFeeGrowthOutside0X128(u256.Zero().Sub(feeGrowthGlobal0X128, thisTick.FeeGrowthOutside0X128()))
228	thisTick.SetFeeGrowthOutside1X128(u256.Zero().Sub(feeGrowthGlobal1X128, thisTick.FeeGrowthOutside1X128()))
229
230	// Handle oracle fields - use zero values if nil
231	tickSecondsPerLiquidity := thisTick.SecondsPerLiquidityOutsideX128()
232	if tickSecondsPerLiquidity == nil {
233		tickSecondsPerLiquidity = u256.Zero()
234	}
235	thisTick.SetSecondsPerLiquidityOutsideX128(u256.Zero().Sub(secondsPerLiquidityCumulativeX128, tickSecondsPerLiquidity))
236	thisTick.SetTickCumulativeOutside(tickCumulative - thisTick.TickCumulativeOutside())
237	thisTick.SetSecondsOutside(uint32(blockTimestamp) - thisTick.SecondsOutside())
238
239	setTick(p, tick, thisTick)
240
241	return thisTick.LiquidityNet().Clone()
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	// Floor MIN_TICK and MAX_TICK to the nearest multiple of tickSpacing
320	// This ensures that the tick range is properly aligned with the tickSpacing
321	// For example, if tickSpacing is 60 and MIN_TICK is -887272:
322	// -887272 / 60 = -14787.866... -> -14787 * 60 = -887220
323	minTick := (MIN_TICK / tickSpacing) * tickSpacing
324	maxTick := (MAX_TICK / tickSpacing) * tickSpacing
325	numTicks := uint64((maxTick-minTick)/tickSpacing) + 1
326
327	return u256.Zero().Div(maxUint128FromDecimal, u256.NewUint(numTicks))
328}
329
330// getFeeGrowthBelowX128 calculates the fee growth below a specified tick.
331//
332// This function computes the fee growth for token 0 and token 1 below a given tick (`tickLower`)
333// relative to the current tick (`tickCurrent`). The fee growth values are adjusted based on whether
334// the `tickCurrent` is above or below the `tickLower`.
335//
336// Parameters:
337//   - tickLower: int32, the lower tick boundary for fee calculation.
338//   - tickCurrent: int32, the current tick index.
339//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
340//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
341//   - lowerTick: TickInfo, the fee growth and liquidity details for the lower tick.
342//
343// Returns:
344//   - *u256.Uint: Fee growth below `tickLower` for token 0.
345//   - *u256.Uint: Fee growth below `tickLower` for token 1.
346//
347// Workflow:
348//  1. If `tickCurrent` is greater than or equal to `tickLower`:
349//     - Return the `feeGrowthOutside0X128` and `feeGrowthOutside1X128` values of the `lowerTick`.
350//  2. If `tickCurrent` is below `tickLower`:
351//     - Compute the fee growth below the lower tick by subtracting `feeGrowthOutside` values
352//     from the global fee growth values (`feeGrowthGlobal0X128` and `feeGrowthGlobal1X128`).
353//  3. Return the calculated fee growth values for both tokens.
354//
355// Behavior:
356//   - If `tickCurrent >= tickLower`, the fee growth outside the lower tick is returned as-is.
357//   - If `tickCurrent < tickLower`, the fee growth is calculated as:
358//     feeGrowthBelow = feeGrowthGlobal - feeGrowthOutside
359//
360// Example:
361//
362// ```gno
363//
364//	feeGrowth0, feeGrowth1 := getFeeGrowthBelowX128(
365//	    100, 150, globalFeeGrowth0, globalFeeGrowth1, lowerTickInfo,
366//	)
367//	println("Fee Growth Below:", feeGrowth0, feeGrowth1)
368func getFeeGrowthBelowX128(
369	tickLower, tickCurrent int32,
370	feeGrowthGlobal0X128, feeGrowthGlobal1X128 *u256.Uint,
371	lowerTick pl.TickInfo,
372) (*u256.Uint, *u256.Uint) {
373	if tickCurrent >= tickLower {
374		return lowerTick.FeeGrowthOutside0X128(), lowerTick.FeeGrowthOutside1X128()
375	}
376
377	feeGrowthBelow0X128 := u256.Zero().Sub(feeGrowthGlobal0X128, lowerTick.FeeGrowthOutside0X128())
378	feeGrowthBelow1X128 := u256.Zero().Sub(feeGrowthGlobal1X128, lowerTick.FeeGrowthOutside1X128())
379
380	return feeGrowthBelow0X128, feeGrowthBelow1X128
381}
382
383// getFeeGrowthAboveX128 calculates the fee growth above a specified tick.
384//
385// This function computes the fee growth for token 0 and token 1 above a given tick (`tickUpper`)
386// relative to the current tick (`tickCurrent`). The fee growth values are adjusted based on whether
387// the `tickCurrent` is above or below the `tickUpper`.
388//
389// Parameters:
390//   - tickUpper: int32, the upper tick boundary for fee calculation.
391//   - tickCurrent: int32, the current tick index.
392//   - feeGrowthGlobal0X128: *u256.Uint, the global fee growth for token 0 in X128 precision.
393//   - feeGrowthGlobal1X128: *u256.Uint, the global fee growth for token 1 in X128 precision.
394//   - upperTick: TickInfo, the fee growth and liquidity details for the upper tick.
395//
396// Returns:
397//   - *u256.Uint: Fee growth above `tickUpper` for token 0.
398//   - *u256.Uint: Fee growth above `tickUpper` for token 1.
399//
400// Workflow:
401//  1. If `tickCurrent` is less than `tickUpper`:
402//     - Return the `feeGrowthOutside0X128` and `feeGrowthOutside1X128` values of the `upperTick`.
403//  2. If `tickCurrent` is greater than or equal to `tickUpper`:
404//     - Compute the fee growth above the upper tick by subtracting `feeGrowthOutside` values
405//     from the global fee growth values (`feeGrowthGlobal0X128` and `feeGrowthGlobal1X128`).
406//  3. Return the calculated fee growth values for both tokens.
407//
408// Behavior:
409//   - If `tickCurrent < tickUpper`, the fee growth outside the upper tick is returned as-is.
410//   - If `tickCurrent >= tickUpper`, the fee growth is calculated as:
411//     feeGrowthAbove = feeGrowthGlobal - feeGrowthOutside
412//
413// Example:
414//
415//	feeGrowth0, feeGrowth1 := getFeeGrowthAboveX128(
416//	    200, 150, globalFeeGrowth0, globalFeeGrowth1, upperTickInfo,
417//	)
418//	println("Fee Growth Above:", feeGrowth0, feeGrowth1)
419//
420// ```
421func getFeeGrowthAboveX128(
422	tickUpper, tickCurrent int32,
423	feeGrowthGlobal0X128, feeGrowthGlobal1X128 *u256.Uint,
424	upperTick pl.TickInfo,
425) (*u256.Uint, *u256.Uint) {
426	if tickCurrent < tickUpper {
427		return upperTick.FeeGrowthOutside0X128(), upperTick.FeeGrowthOutside1X128()
428	}
429
430	feeGrowthAbove0X128 := u256.Zero().Sub(feeGrowthGlobal0X128, upperTick.FeeGrowthOutside0X128())
431	feeGrowthAbove1X128 := u256.Zero().Sub(feeGrowthGlobal1X128, upperTick.FeeGrowthOutside1X128())
432
433	return feeGrowthAbove0X128, feeGrowthAbove1X128
434}
435
436// validateTicks validates the tick range for a liquidity position.
437//
438// This function performs three essential checks to ensure the provided
439// tick values are valid before creating or modifying a liquidity position.
440func validateTicks(tickLower, tickUpper int32) error {
441	if tickLower >= tickUpper {
442		return makeErrorWithDetails(
443			errInvalidTickRange,
444			ufmt.Sprintf("tickLower(%d), tickUpper(%d)", tickLower, tickUpper),
445		)
446	}
447
448	if tickLower < MIN_TICK {
449		return makeErrorWithDetails(
450			errTickLowerInvalid,
451			ufmt.Sprintf("tickLower(%d) < MIN_TICK(%d)", tickLower, MIN_TICK),
452		)
453	}
454
455	if tickUpper > MAX_TICK {
456		return makeErrorWithDetails(
457			errTickUpperInvalid,
458			ufmt.Sprintf("tickUpper(%d) > MAX_TICK(%d)", tickUpper, MAX_TICK),
459		)
460	}
461
462	return nil
463}