Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}