Search Apps Documentation Source Content File Folder Download Copy Actions Download

tick_math.gno

10.95 Kb ยท 326 lines
  1package common
  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
 10// Pre-calculated ratio constants for performance optimization.
 11// These values are pre-computed to avoid runtime decimal parsing.
 12var (
 13	// Initial ratio constants - exactly matching Uniswap V3
 14	ratio0 = u256.MustFromDecimal("340265354078544963557816517032075149313") // 0xfffcb933bd6fad37aa2d162d1a594001
 15	ratio1 = u256.MustFromDecimal("340282366920938463463374607431768211456") // 0x100000000000000000000000000000000 (2^128)
 16
 17	// Bit mask ratio constants in order (bit 1 to bit 19)
 18	ratioConstants = []*u256.Uint{
 19		u256.MustFromDecimal("340248342086729790484326174814286782778"), // 0xfff97272373d413259a46990580e213a (bit 1)
 20		u256.MustFromDecimal("340214320654664324051920982716015181260"), // 0xfff2e50f5f656932ef12357cf3c7fdcc (bit 2)
 21		u256.MustFromDecimal("340146287995602323631171512101879684304"), // 0xffe5caca7e10e4e61c3624eaa0941cd0 (bit 3)
 22		u256.MustFromDecimal("340010263488231146823593991679159461444"), // 0xffcb9843d60f6159c9db58835c926644 (bit 4)
 23		u256.MustFromDecimal("339738377640345403697157401104375502016"), // 0xff973b41fa98c081472e6896dfb254c0 (bit 5)
 24		u256.MustFromDecimal("339195258003219555707034227454543997025"), // 0xff2ea16466c96a3843ec78b326b52861 (bit 6)
 25		u256.MustFromDecimal("338111622100601834656805679988414885971"), // 0xfe5dee046a99a2a811c461f1969c3053 (bit 7)
 26		u256.MustFromDecimal("335954724994790223023589805789778977700"), // 0xfcbe86c7900a88aedcffc83b479aa3a4 (bit 8)
 27		u256.MustFromDecimal("331682121138379247127172139078559817300"), // 0xf987a7253ac413176f2b074cf7815e54 (bit 9)
 28		u256.MustFromDecimal("323299236684853023288211250268160618739"), // 0xf3392b0822b70005940c7a398e4b70f3 (bit 10)
 29		u256.MustFromDecimal("307163716377032989948697243942600083929"), // 0xe7159475a2c29b7443b29c7fa6e889d9 (bit 11)
 30		u256.MustFromDecimal("277268403626896220162999269216087595045"), // 0xd097f3bdfd2022b8845ad8f792aa5825 (bit 12)
 31		u256.MustFromDecimal("225923453940442621947126027127485391333"), // 0xa9f746462d870fdf8a65dc1f90e061e5 (bit 13)
 32		u256.MustFromDecimal("149997214084966997727330242082538205943"), // 0x70d869a156d2a1b890bb3df62baf32f7 (bit 14)
 33		u256.MustFromDecimal("66119101136024775622716233608466517926"),  // 0x31be135f97d08fd981231505542fcfa6 (bit 15)
 34		u256.MustFromDecimal("12847376061809297530290974190478138313"),  // 0x9aa508b5b7a84e1c677de54f3e99bc9 (bit 16)
 35		u256.MustFromDecimal("485053260817066172746253684029974020"),    // 0x5d6af8dedb81196699c329225ee604 (bit 17)
 36		u256.MustFromDecimal("691415978906521570653435304214168"),       // 0x2216e584f5fa1ea926041bedfe98 (bit 18)
 37		u256.MustFromDecimal("1404880482679654955896180642"),            // 0x48a170391f7dc42444e8fa2 (bit 19)
 38	}
 39
 40	// Pre-computed constants for optimization
 41	maxUint256   = u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") // 2^256 - 1
 42	minSqrtRatio = u256.MustFromDecimal("4295128739")                                                                     // same as TickMathGetSqrtRatioAtTick(minTick)
 43	maxSqrtRatio = u256.MustFromDecimal("1461446703485210103287273052203988822378723970342")                              // same as TickMathGetSqrtRatioAtTick(maxTick)
 44
 45	// MSB calculation thresholds - pre-computed for performance
 46	msb128Threshold = u256.MustFromDecimal("340282366920938463463374607431768211455") // 2^128 - 1
 47	msb64Threshold  = u256.MustFromDecimal("18446744073709551615")                    // 2^64 - 1
 48	msb32Threshold  = u256.MustFromDecimal("4294967295")                              // 2^32 - 1
 49	msb16Threshold  = u256.NewUint(65535)                                             // 2^16 - 1
 50	msb8Threshold   = u256.NewUint(255)                                               // 2^8 - 1
 51	msb4Threshold   = u256.NewUint(15)                                                // 2^4 - 1
 52	msb2Threshold   = u256.NewUint(3)                                                 // 2^2 - 1
 53	msb1Threshold   = u256.One()                                                      // 1
 54
 55	// Pre-computed constants for tick calculation
 56	log2Multiplier = i256.MustFromDecimal("255738958999603826347141")
 57	tickLowOffset  = i256.MustFromDecimal("3402992956809132418596140100660247210")
 58	tickHiOffset   = i256.MustFromDecimal("291339464771989622907027621153398088495")
 59
 60	oneLsh32 = u256.Zero().Lsh(u256.One(), 32) // 1 << 32
 61)
 62
 63// TickMathGetSqrtRatioAtTick calculates sqrt price ratio for given tick.
 64//
 65// Converts tick index to square root price in Q64.96 fixed-point format.
 66// Based on Uniswap V3's mathematical formula: price = 1.0001^tick.
 67// Uses bit manipulation for gas-efficient calculation.
 68//
 69// Parameters:
 70//   - tick: Tick index in range [-887272, 887272]
 71//
 72// Returns:
 73//   - Square root of price ratio as Q64.96 fixed-point
 74//   - Result represents sqrt(token1/token0) price
 75//
 76// Mathematical formula:
 77//
 78//	sqrtPriceX96 = sqrt(1.0001^tick) * 2^96
 79//
 80// Panics if tick outside valid range.
 81// Critical for all price calculations in concentrated liquidity.
 82func TickMathGetSqrtRatioAtTick(tick int32) *u256.Uint {
 83	assertValidTickRange(tick)
 84	absTick := abs(tick)
 85
 86	// Validate critical constants are initialized
 87	if ratio0 == nil || ratio1 == nil {
 88		panic(newErrorWithDetail(
 89			errInvalidInput,
 90			"ratio constants are not initialized",
 91		))
 92	}
 93
 94	// Initialize ratio based on LSB - exactly like Uniswap V3
 95	var ratio *u256.Uint
 96
 97	if absTick&0x1 != 0 {
 98		ratio = ratio0.Clone()
 99	} else {
100		ratio = ratio1.Clone()
101	}
102
103	temp := u256.Zero()
104
105	// Apply bit masks using optimized loop - maintains exact same logic
106	for i := 1; i < 20; i++ {
107		if absTick&(1<<uint(i)) != 0 {
108			// Use temporary variables to avoid memory allocation in hot path
109			r := ratioConstants[i-1]
110			if r == nil {
111				panic(newErrorWithDetail(
112					errInvalidInput,
113					"ratio constants are not initialized",
114				))
115			}
116			temp, overflow := temp.MulOverflow(ratio, r)
117			if overflow {
118				panic(errOverflow)
119			}
120			ratio = ratio.Rsh(temp, 128)
121		}
122	}
123
124	// Invert ratio for positive ticks
125	if tick > 0 {
126		ratio = temp.Div(maxUint256, ratio)
127	}
128
129	// Convert from Q128.128 to Q128.96 with rounding up.
130	// This divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96
131	upper := u256.Zero().Rsh(ratio, 32)           // ratio >> 32
132	remainder := u256.Zero().Mod(ratio, oneLsh32) // ratio % (1 << 32)
133
134	// Round up: add 1 if remainder != 0
135	if !remainder.IsZero() {
136		upper = u256.Zero().Add(upper, u256.One())
137	}
138
139	return upper
140}
141
142// getMostSignificantBit returns the position of the most significant bit (MSB) in a uint256 value.
143// Uses binary search with pre-computed thresholds for efficient calculation.
144//
145// Parameters:
146//   - r: uint256 value to find MSB for
147//
148// Returns:
149//   - msb: position of the most significant bit (0-255)
150//
151// Used internally for logarithm calculations in tick math.
152func getMostSignificantBit(r *u256.Uint) (msb uint64) {
153	temp := r.Clone()
154
155	// Optimized MSB calculation using pre-computed thresholds
156	if temp.Gt(msb128Threshold) {
157		msb |= 128
158		temp = temp.Rsh(temp, 128)
159	}
160
161	if temp.Gt(msb64Threshold) {
162		msb |= 64
163		temp = temp.Rsh(temp, 64)
164	}
165
166	if temp.Gt(msb32Threshold) {
167		msb |= 32
168		temp = temp.Rsh(temp, 32)
169	}
170
171	if temp.Gt(msb16Threshold) {
172		msb |= 16
173		temp = temp.Rsh(temp, 16)
174	}
175
176	if temp.Gt(msb8Threshold) {
177		msb |= 8
178		temp = temp.Rsh(temp, 8)
179	}
180
181	if temp.Gt(msb4Threshold) {
182		msb |= 4
183		temp = temp.Rsh(temp, 4)
184	}
185
186	if temp.Gt(msb2Threshold) {
187		msb |= 2
188		temp = temp.Rsh(temp, 2)
189	}
190
191	if temp.Gt(msb1Threshold) {
192		msb |= 1
193	}
194
195	return
196}
197
198// TickMathGetTickAtSqrtRatio calculates the tick index for a given square root price ratio.
199//
200// Converts a square root price ratio in Q64.96 format back to its tick index,
201// returning the greatest tick where TickMathGetSqrtRatioAtTick(tick) <= sqrtPriceX96.
202// This matches Uniswap V3's behavior exactly.
203//
204// Parameters:
205//   - sqrtPriceX96: Square root price ratio in Q64.96 format
206//
207// Returns:
208//   - Tick index corresponding to the price
209//
210// Algorithm:
211//  1. Scales ratio from Q64.96 to Q96.128 by left-shifting 32 bits
212//  2. Finds MSB (most significant bit) to determine magnitude
213//  3. Calculates log_2 using fixed-point arithmetic
214//  4. Converts log_2 to log_sqrt(1.0001) to get tick
215//  5. Returns appropriate tick based on bounds checking
216//
217// Panics if sqrtPriceX96 is nil or outside valid range [minSqrtRatio, maxSqrtRatio).
218// Critical for converting prices to ticks for position management.
219func TickMathGetTickAtSqrtRatio(sqrtPriceX96 *u256.Uint) int32 {
220	if sqrtPriceX96 == nil {
221		panic(newErrorWithDetail(
222			errInvalidInput,
223			"sqrtPriceX96 cannot be nil",
224		))
225	}
226
227	if sqrtPriceX96.Lt(minSqrtRatio) || sqrtPriceX96.Gte(maxSqrtRatio) {
228		panic(newErrorWithDetail(
229			errOutOfRange,
230			ufmt.Sprintf("sqrtPriceX96(%s) is out of range", sqrtPriceX96.ToString()),
231		))
232	}
233
234	// Scale ratio by 32 bits to convert from Q64.96 to Q96.128
235	ratio := u256.Zero().Lsh(sqrtPriceX96, 32)
236
237	// Find MSB using optimized calculation
238	msb := getMostSignificantBit(ratio)
239
240	// Adjust ratio based on MSB
241	var r *u256.Uint
242
243	if msb >= 128 {
244		r = u256.Zero().Rsh(ratio, uint(msb-127))
245	} else {
246		r = u256.Zero().Lsh(ratio, uint(127-msb))
247	}
248
249	// Calculate log_2 using fixed-point arithmetic
250	log2 := i256.NewInt(int64(msb) - 128)
251	log2 = i256.Zero().Lsh(log2, 64)
252
253	// Define temporary variables for optimization
254	tempR := u256.Zero()
255	tempF := u256.Zero()
256	tempI256 := i256.Zero()
257
258	// Optimized iterative calculation using loop - maintains exact same logic
259	for i := 0; i < 14; i++ {
260		tempR, overflow := tempR.MulOverflow(r, r)
261		if overflow {
262			panic(errOverflow)
263		}
264		r = tempR.Rsh(tempR, 127)
265
266		tempF = tempF.Rsh(r, 128)
267		tempI256 = i256.FromUint256(tempF)
268		f := tempF
269
270		tempI256 = tempI256.Lsh(tempI256, uint(63-i))
271		log2 = log2.Or(log2, tempI256)
272		r = r.Rsh(r, uint(f.Uint64()))
273	}
274
275	// Calculate tick from log_sqrt10001
276	logSqrt10001, overflow := i256.Zero().MulOverflow(log2, log2Multiplier)
277	if overflow {
278		panic(errOverflow)
279	}
280
281	// Calculate tick bounds
282	tickLow := i256.Zero().Sub(logSqrt10001, tickLowOffset)
283	tickLow = tickLow.Rsh(tickLow, 128)
284	tickLowInt32 := int32(tickLow.Int64())
285
286	tickHi := i256.Zero().Add(logSqrt10001, tickHiOffset)
287	tickHi = tickHi.Rsh(tickHi, 128)
288	tickHiInt32 := int32(tickHi.Int64())
289
290	// Select the appropriate tick
291	if tickLowInt32 == tickHiInt32 {
292		return tickLowInt32
293	}
294
295	if TickMathGetSqrtRatioAtTick(tickHiInt32).Lte(sqrtPriceX96) {
296		return tickHiInt32
297	}
298
299	return tickLowInt32
300}
301
302// abs returns the absolute value of a signed 32-bit integer.
303// Used internally for tick math calculations to handle negative tick indices.
304func abs(x int32) int32 {
305	if x < 0 {
306		return -x
307	}
308
309	return x
310}
311
312// assertValidTickRange panics if tick is outside valid range [-887272, 887272].
313func assertValidTickRange(tick int32) {
314	if tick > maxTick {
315		panic(newErrorWithDetail(
316			errOutOfRange,
317			ufmt.Sprintf("tick is out of range (larger than 887272), tick: %d", tick),
318		))
319	}
320	if tick < minTick {
321		panic(newErrorWithDetail(
322			errOutOfRange,
323			ufmt.Sprintf("tick is out of range (smaller than -887272), tick: %d", tick),
324		))
325	}
326}