Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

tick.gno

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