Skip to content

Commit

Permalink
Merge pull request #161 from notJoon/pool-tick-math
Browse files Browse the repository at this point in the history
refactor: pool/tick_math.gno
  • Loading branch information
r3v4s authored Feb 15, 2024
2 parents cc01ede + 124e573 commit 989905a
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 105 deletions.
65 changes: 65 additions & 0 deletions pool/_TEST_tick_math_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package pool

import (
"testing"
)

func TestFindMSB(t *testing.T) {
tests := []struct {
name string
ratio bigint
expectedMSB bigint
expectedRatio bigint
}{
{
name: "very low ratio",
ratio: bigint(1<<10),
expectedMSB: 10,
expectedRatio: 1,
},
{
name: "low ratio",
ratio: bigint(1<<30),
expectedMSB: 30,
expectedRatio: 1,
},
{
name: "medium ratio",
ratio: bigint(1<<50),
expectedMSB: 50,
expectedRatio: 1,
},
{
name: "high ratio",
ratio: 1<<100,
expectedMSB: 100,
expectedRatio: 1,
},
{
name: "very high ratio",
ratio: 1<<110,
expectedMSB: 110,
expectedRatio: 1,
},
{
name: "extreme ratio",
ratio: 1<<200,
expectedMSB: 200,
expectedRatio: 1,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
msb, ratio := findMSB(tt.ratio)

if msb != tt.expectedMSB {
t.Errorf("expected MSB to be %d, got %d", tt.expectedMSB, msb)
}

if ratio != tt.expectedRatio {
t.Errorf("expected ratio to be %d, got %d", tt.expectedRatio, ratio)
}
})
}
}
215 changes: 110 additions & 105 deletions pool/tick_math.gno
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,42 @@ import (
"gno.land/r/demo/consts"
)

var tickRatioMap = map[bigint]bigint{
0x1: 0xfffcb933bd6fad37aa2d162d1a594001,
0x2: 0xfff97272373d413259a46990580e213a,
0x4: 0xfff2e50f5f656932ef12357cf3c7fdcc,
0x8: 0xffe5caca7e10e4e61c3624eaa0941cd0,
0x10: 0xffcb9843d60f6159c9db58835c926644,
0x20: 0xff973b41fa98c081472e6896dfb254c0,
0x40: 0xff2ea16466c96a3843ec78b326b52861,
0x80: 0xfe5dee046a99a2a811c461f1969c3053,
0x100: 0xfcbe86c7900a88aedcffc83b479aa3a4,
0x200: 0xf987a7253ac413176f2b074cf7815e54,
0x400: 0xf3392b0822b70005940c7a398e4b70f3,
0x800: 0xe7159475a2c29b7443b29c7fa6e889d9,
0x1000: 0xd097f3bdfd2022b8845ad8f792aa5825,
0x2000: 0xa9f746462d870fdf8a65dc1f90e061e5,
0x4000: 0x70d869a156d2a1b890bb3df62baf32f7,
0x8000: 0x31be135f97d08fd981231505542fcfa6,
0x10000: 0x9aa508b5b7a84e1c677de54f3e99bc9,
0x20000: 0x5d6af8dedb81196699c329225ee604,
0x40000: 0x2216e584f5fa1ea926041bedfe98,
0x80000: 0x48a170391f7dc42444e8fa2,
}

var binaryLogConsts = [8]bigint{
0x0,
0x3,
0xF,
0xFF,
0xFFFF,
0xFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
}

func TickMathGetSqrtRatioAtTick(tick int32) bigint {
var absTick bigint
if tick < 0 {
absTick = -bigint(tick)
} else {
absTick = bigint(tick)
}
absTick := absTick(tick)
require(
absTick <= bigint(consts.MAX_TICK),
ufmt.Sprintf(
Expand All @@ -20,143 +49,111 @@ func TickMathGetSqrtRatioAtTick(tick int32) bigint {
),
)

var ratio bigint
if absTick&0x1 != 0 {
ratio = 0xfffcb933bd6fad37aa2d162d1a594001
} else {
ratio = 0x100000000000000000000000000000000
}

if absTick&0x2 != 0 {
ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128
}
if absTick&0x4 != 0 {
ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128
}
if absTick&0x8 != 0 {
ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128
}
if absTick&0x10 != 0 {
ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128
}
if absTick&0x20 != 0 {
ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128
}
if absTick&0x40 != 0 {
ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128
}
if absTick&0x80 != 0 {
ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128
}
if absTick&0x100 != 0 {
ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128
}
if absTick&0x200 != 0 {
ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128
}
if absTick&0x400 != 0 {
ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128
}
if absTick&0x800 != 0 {
ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128
}
if absTick&0x1000 != 0 {
ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128
}
if absTick&0x2000 != 0 {
ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128
}
if absTick&0x4000 != 0 {
ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128
}
if absTick&0x8000 != 0 {
ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128
}
if absTick&0x10000 != 0 {
ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128
}
if absTick&0x20000 != 0 {
ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128
}
if absTick&0x40000 != 0 {
ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128
}
if absTick&0x80000 != 0 {
ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128
ratio := consts.Q128
for mask, value := range tickRatioMap {
if absTick&mask != 0 {
ratio = (ratio * value) >> 128
}
}

if tick > 0 {
ratio = consts.MAX_UINT256 / ratio
}

shiftedRatio := ratio >> 32
shifted := ratio >> 32
remainder := ratio % (1 << 32)
if shiftedRatio+remainder == 0 {
return shiftedRatio + 0
} else {
return shiftedRatio + 1

if shifted+remainder == 0 {
return shifted + 0
}

return shifted + 1
}

func TickMathGetTickAtSqrtRatio(sqrtPriceX96 bigint) int32 {
require(
sqrtPriceX96 >= consts.MIN_SQRT_RATIO && sqrtPriceX96 < consts.MAX_SQRT_RATIO,
ufmt.Sprintf(
"[POOL] tick_math.gno__tickMathGetTickAtSqrtRatio() || sqrtPriceX96(%d) >= consts.MIN_SQRT_RATIO(%d) && sqrtPriceX96(%d) < consts.MAX_SQRT_RATIO(%d)",
sqrtPriceX96, consts.MIN_SQRT_RATIO, sqrtPriceX96, consts.MAX_SQRT_RATIO,
),
ufmt.Sprintf("[POOL] sqrtPriceX96(%d) is out of range [%d, %d)", sqrtPriceX96, consts.MIN_SQRT_RATIO, consts.MAX_SQRT_RATIO),
)
ratio := sqrtPriceX96 << 32

r := ratio
msb := bigint(0)
msb, adjustedRatio := findMSB(ratio)
adjustedRatio = adjustRatio(ratio, msb)

// array
_tv := [8]bigint{
0x0,
0x3,
0xF,
0xFF,
0xFFFF,
0xFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
}
log2 := calculateLog2(msb, adjustedRatio)
tick := getTickValue(log2, sqrtPriceX96)

return tick
}

// findMSB computes the MSB (most significant bit) of the given ratio.
func findMSB(ratio bigint) (bigint, bigint) {
msb := bigint(0)

for i := 7; i >= 1; i-- {
f := gt(r, _tv[i]) << i
f := gt(ratio, binaryLogConsts[i]) << i
msb = msb | bigint(f)
r = r >> f
ratio = ratio >> f
}

// handle the remaining bits
{
f := gt(r, 0x1)
f := gt(ratio, 0x1)
msb = msb | bigint(f)
}

return msb, ratio
}

// adjustRatio adjusts the given ratio based on the MSB found.
//
// This adjustment ensures that the ratio falls within the specific range.
func adjustRatio(ratio, msb bigint) bigint {
if msb >= 128 {
r = ratio >> uint64(msb-127)
} else {
r = ratio << uint64(127-msb)
return ratio >> uint64(msb-127)
}

return ratio << uint64(127-msb)
}

// calculateLog2 calculates the binary logarith, of the adjusted ratio using a fixed-point arithmetic.
//
// This function iteratively squares the ratio and adjusts the result to compute the log base 2, which will determine the tick value.
func calculateLog2(msb, ratio bigint) bigint {
log_2 := (msb - 128) << 64

for i := 63; i >= 51; i-- {
r = r * r >> 127
f := r >> 128
ratio = ratio * ratio >> 127
f := ratio >> 128
log_2 = log_2 | (f << i)
r = r >> uint64(f)
ratio = ratio >> uint64(f)
}

// handle the remaining bits
{
r = r * r >> 127
f := r >> 128
ratio = ratio * ratio >> 127
f := ratio >> 128
log_2 = log_2 | (f << 50)
}

log_sqrt10001 := log_2 * 255738958999603826347141
return log_2
}

// getTickValue determines the tick value corresponding to a given sqrtPriveX96.
//
// It calculates the upper and lower bounds for each tick, and selects the appropriate tock value
// based on the given sqrtPriceX96.
func getTickValue(log2, sqrtPriceX96 bigint) int32 {
// ref: https://github.com/Uniswap/v3-core/issues/500
// 2^64 / log2 (√1.0001) = 255738958999603826347141
log_sqrt10001 := log2 * 255738958999603826347141

// ref: https://ethereum.stackexchange.com/questions/113844/how-does-uniswap-v3s-logarithm-library-tickmath-sol-work/113912#113912
// 0.010000497 x 2^128 = 3402992956809132418596140100660247210
tickLow := int32(int64((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128))

// ref: https://ethereum.stackexchange.com/questions/113844/how-does-uniswap-v3s-logarithm-library-tickmath-sol-work/113912#113912
// 0.856 x 2^128 = 291339464771989622907027621153398088495
tickHi := int32(int64((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128))

var tick int32
Expand All @@ -174,7 +171,15 @@ func TickMathGetTickAtSqrtRatio(sqrtPriceX96 bigint) int32 {
func gt(x, y bigint) uint64 {
if x > y {
return 1
} else {
return 0
}

return 0
}

func absTick(n int32) bigint {
if n < 0 {
return -bigint(n)
}

return bigint(n)
}

0 comments on commit 989905a

Please sign in to comment.