mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
232 lines
6.6 KiB
Go
232 lines
6.6 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
)
|
|
|
|
// TickMath implements tick <-> sqrtPrice conversions matching Uniswap V4's TickMath.sol.
|
|
//
|
|
// sqrtPriceX96 = sqrt(1.0001^tick) * 2^96
|
|
//
|
|
// The implementation uses the exact same magic constants and binary decomposition
|
|
// approach as Uniswap V4. Each tick is decomposed into powers of 2, and the
|
|
// corresponding sqrt ratio factors are multiplied/divided.
|
|
|
|
// GetSqrtRatioAtTick computes sqrtPriceX96 from a tick value.
|
|
// Returns sqrt(1.0001^tick) * 2^96 as a uint160 in *big.Int.
|
|
// tick must be in [MinTick, MaxTick].
|
|
func GetSqrtRatioAtTick(tick int32) (*big.Int, error) {
|
|
if tick < MinTick || tick > MaxTick {
|
|
return nil, fmt.Errorf("tick %d out of range [%d, %d]", tick, MinTick, MaxTick)
|
|
}
|
|
|
|
absTick := tick
|
|
if absTick < 0 {
|
|
absTick = -absTick
|
|
}
|
|
|
|
// Start with Q128 (1.0 in Q128.128 format)
|
|
// We multiply by factors for each set bit in absTick.
|
|
// These magic constants are from Uniswap V4's TickMath.sol.
|
|
ratio := new(big.Int)
|
|
|
|
if absTick&0x1 != 0 {
|
|
ratio.SetString("fffcb933bd6fad37aa2d162d1a594001", 16)
|
|
} else {
|
|
ratio.SetString("100000000000000000000000000000000", 16)
|
|
}
|
|
|
|
mulShift := func(r *big.Int, factor string) {
|
|
f, _ := new(big.Int).SetString(factor, 16)
|
|
r.Mul(r, f)
|
|
r.Rsh(r, 128)
|
|
}
|
|
|
|
if absTick&0x2 != 0 {
|
|
mulShift(ratio, "fff97272373d413259a46990580e213a")
|
|
}
|
|
if absTick&0x4 != 0 {
|
|
mulShift(ratio, "fff2e50f5f656932ef12357cf3c7fdcc")
|
|
}
|
|
if absTick&0x8 != 0 {
|
|
mulShift(ratio, "ffe5caca7e10e4e61c3624eaa0941cd0")
|
|
}
|
|
if absTick&0x10 != 0 {
|
|
mulShift(ratio, "ffcb9843d60f6159c9db58835c926644")
|
|
}
|
|
if absTick&0x20 != 0 {
|
|
mulShift(ratio, "ff973b41fa98c081472e6896dfb254c0")
|
|
}
|
|
if absTick&0x40 != 0 {
|
|
mulShift(ratio, "ff2ea16466c96a3843ec78b326b52861")
|
|
}
|
|
if absTick&0x80 != 0 {
|
|
mulShift(ratio, "fe5dee046a99a2a811c461f1969c3053")
|
|
}
|
|
if absTick&0x100 != 0 {
|
|
mulShift(ratio, "fcbe86c7900a88aedcffc83b479aa3a4")
|
|
}
|
|
if absTick&0x200 != 0 {
|
|
mulShift(ratio, "f987a7253ac413176f2b074cf7815e54")
|
|
}
|
|
if absTick&0x400 != 0 {
|
|
mulShift(ratio, "f3392b0822b70005940c7a398e4b70f3")
|
|
}
|
|
if absTick&0x800 != 0 {
|
|
mulShift(ratio, "e7159475a2c29b7443b29c7fa6e889d9")
|
|
}
|
|
if absTick&0x1000 != 0 {
|
|
mulShift(ratio, "d097f3bdfd2022b8845ad8f792aa5825")
|
|
}
|
|
if absTick&0x2000 != 0 {
|
|
mulShift(ratio, "a9f746462d870fdf8a65dc1f90e061e5")
|
|
}
|
|
if absTick&0x4000 != 0 {
|
|
mulShift(ratio, "70d869a156d2a1b890bb3df62baf32f7")
|
|
}
|
|
if absTick&0x8000 != 0 {
|
|
mulShift(ratio, "31be135f97d08fd981231505542fcfa6")
|
|
}
|
|
if absTick&0x10000 != 0 {
|
|
mulShift(ratio, "9aa508b5b7a84e1c677de54f3e99bc9")
|
|
}
|
|
if absTick&0x20000 != 0 {
|
|
mulShift(ratio, "5d6af8dedb81196699c329225ee604")
|
|
}
|
|
if absTick&0x40000 != 0 {
|
|
mulShift(ratio, "2216e584f5fa1ea926041bedfe98")
|
|
}
|
|
if absTick&0x80000 != 0 {
|
|
mulShift(ratio, "48a170391f7dc42444e8fa2")
|
|
}
|
|
|
|
// If tick is positive, invert: ratio = type(uint256).max / ratio
|
|
if tick > 0 {
|
|
ratio.Div(maxU256, ratio)
|
|
}
|
|
|
|
// Convert from Q128.128 to Q96: shift right by 32 bits, rounding up
|
|
// sqrtPriceX96 = (ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)
|
|
remainder := new(big.Int).And(ratio, new(big.Int).Sub(new(big.Int).Lsh(bigOne, 32), bigOne))
|
|
ratio.Rsh(ratio, 32)
|
|
if remainder.Sign() > 0 {
|
|
ratio.Add(ratio, bigOne)
|
|
}
|
|
|
|
return ratio, nil
|
|
}
|
|
|
|
// GetTickAtSqrtRatio computes the greatest tick value such that
|
|
// getSqrtRatioAtTick(tick) <= sqrtPriceX96.
|
|
// sqrtPriceX96 must be in [MIN_SQRT_RATIO, MAX_SQRT_RATIO).
|
|
//
|
|
// Implementation matches Uniswap V4 TickMath.sol exactly: unsigned 256-bit
|
|
// arithmetic with two's complement interpretation for signed results.
|
|
func GetTickAtSqrtRatio(sqrtPriceX96 *big.Int) (int32, error) {
|
|
if sqrtPriceX96.Cmp(MinSqrtRatio) < 0 || sqrtPriceX96.Cmp(MaxSqrtRatio) >= 0 {
|
|
return 0, fmt.Errorf("sqrtPriceX96 %s out of range", sqrtPriceX96)
|
|
}
|
|
|
|
// ratio = sqrtPriceX96 << 32 (Q128.128 format)
|
|
ratio := new(big.Int).Lsh(sqrtPriceX96, 32)
|
|
|
|
// msb = most significant bit index
|
|
msb := uint(msb256(ratio))
|
|
|
|
// Normalize ratio to [2^127, 2^128)
|
|
if msb >= 128 {
|
|
ratio.Rsh(ratio, msb-127)
|
|
} else {
|
|
ratio.Lsh(ratio, 127-msb)
|
|
}
|
|
|
|
// log_2 as int256 (two's complement in 256 bits)
|
|
// Integer part: (msb - 128) << 64 in two's complement uint256
|
|
u256 := new(big.Int).Lsh(bigOne, 256)
|
|
log2 := big.NewInt(int64(msb) - 128)
|
|
log2.Lsh(log2, 64)
|
|
if log2.Sign() < 0 {
|
|
log2.Add(log2, u256) // Convert to two's complement unsigned
|
|
}
|
|
|
|
// Fractional log2 via repeated squaring (14 iterations matching V4)
|
|
for i := uint(63); i >= 50; i-- {
|
|
ratio.Mul(ratio, ratio)
|
|
ratio.Rsh(ratio, 127)
|
|
|
|
f := new(big.Int).Rsh(ratio, 128)
|
|
// log_2 = log_2 | (f << i)
|
|
log2.Or(log2, new(big.Int).Lsh(f, i))
|
|
// ratio >>= f
|
|
if f.Sign() > 0 {
|
|
ratio.Rsh(ratio, uint(f.Uint64()))
|
|
}
|
|
|
|
if i == 50 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Keep log2 in uint256 range
|
|
log2.And(log2, maxU256)
|
|
|
|
// log_sqrt10001 = log_2 * 255738958999603826347141 (signed multiply in two's complement)
|
|
// First convert log2 to signed
|
|
log2Signed := uint256ToInt256(log2)
|
|
|
|
factor, _ := new(big.Int).SetString("255738958999603826347141", 10)
|
|
logSqrt10001 := new(big.Int).Mul(log2Signed, factor)
|
|
|
|
// tickLow = int256(logSqrt10001 - 3402992956809132418596140100660247210) >> 128
|
|
sub1, _ := new(big.Int).SetString("3402992956809132418596140100660247210", 10)
|
|
tickLowVal := new(big.Int).Sub(logSqrt10001, sub1)
|
|
tickLow := signedRsh128(tickLowVal)
|
|
|
|
// tickHigh = int256(logSqrt10001 + 291339464771989622907027621153398088495) >> 128
|
|
add1, _ := new(big.Int).SetString("291339464771989622907027621153398088495", 10)
|
|
tickHighVal := new(big.Int).Add(logSqrt10001, add1)
|
|
tickHigh := signedRsh128(tickHighVal)
|
|
|
|
if tickLow == tickHigh {
|
|
return tickLow, nil
|
|
}
|
|
|
|
sqrtAtHighTick, err := GetSqrtRatioAtTick(tickHigh)
|
|
if err != nil {
|
|
return tickLow, nil
|
|
}
|
|
if sqrtAtHighTick.Cmp(sqrtPriceX96) <= 0 {
|
|
return tickHigh, nil
|
|
}
|
|
return tickLow, nil
|
|
}
|
|
|
|
// uint256ToInt256 interprets a uint256 value as a signed int256 (two's complement).
|
|
func uint256ToInt256(x *big.Int) *big.Int {
|
|
result := new(big.Int).Set(x)
|
|
// If bit 255 is set, the value is negative
|
|
max := new(big.Int).Lsh(bigOne, 255)
|
|
if result.Cmp(max) >= 0 {
|
|
result.Sub(result, new(big.Int).Lsh(bigOne, 256))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// signedRsh128 performs arithmetic right shift by 128 on a signed big.Int.
|
|
// Go's big.Int.Rsh is already an arithmetic right shift for signed values.
|
|
func signedRsh128(x *big.Int) int32 {
|
|
return int32(new(big.Int).Rsh(x, 128).Int64())
|
|
}
|
|
|
|
// msb256 finds the index of the most significant bit of a positive big.Int.
|
|
func msb256(x *big.Int) int {
|
|
if x.Sign() <= 0 {
|
|
return 0
|
|
}
|
|
return x.BitLen() - 1
|
|
}
|