Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize TickMath #273

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e22638d
Add forge tests and snapshots for `TickMath`
shuhuiluo Jun 17, 2023
7063bc8
Optimize `TickMath`
shuhuiluo Jun 18, 2023
dcd5130
Merge branch 'refs/heads/main' into optimize-tickmath
shuhuiluo Apr 4, 2024
1049dc2
Fix comment and incorrect threshold in `getTickAtSqrtRatio` test
shuhuiluo Apr 4, 2024
2983f17
Add gas benchmarks for `TickMath` functions
shuhuiluo Apr 4, 2024
b4aef33
Optimize `mostSignificantBit` in `getTickAtSqrtRatio` and take snapshot
shuhuiluo Apr 4, 2024
9454018
Remove stale snapshots
shuhuiluo Apr 4, 2024
51bd254
Refactor if statement in `TickMath`
shuhuiluo Apr 11, 2024
75f6eb1
Merge branch 'refs/heads/main' into optimize-tickmath
shuhuiluo Apr 11, 2024
5a14d34
Update gas snapshots
shuhuiluo Apr 11, 2024
ce6a1a0
Merge and update snapshots
shuhuiluo Apr 14, 2024
db230e5
Replace `shr` with `sar` in `getTickAtSqrtRatio` so the return value …
shuhuiluo Apr 14, 2024
b62e5df
Remove `signextend` in `getSqrtRatioAtTick`
shuhuiluo Apr 15, 2024
62623cb
Merge and update snapshots
shuhuiluo Apr 9, 2024
3c04936
Refactor bitwise operation in `TickMath`
shuhuiluo Apr 26, 2024
369ccfe
Merge and update snapshots
shuhuiluo May 17, 2024
dab473c
Refactor TickMath library for clarity and correctness
shuhuiluo May 17, 2024
2eb92a8
Refactor `getTickAtSqrtPrice` and remove unused Yul function
shuhuiluo May 17, 2024
5dc9e25
Merge and update snapshots
shuhuiluo May 17, 2024
bc1cce0
Uncheck math in `TickMath` gas benchmarks
shuhuiluo May 18, 2024
5bcccc8
Use Solady implementation to find the most significant bit in `TickMa…
shuhuiluo May 17, 2024
0b1e1b3
Refactor core assembly blocks in `TickMath.getTickAtSqrtPrice`
shuhuiluo May 18, 2024
38a9cf7
Update gas snapshots
shuhuiluo May 20, 2024
a1b62ad
Merge and update snapshots
shuhuiluo May 22, 2024
0b55af5
Merge and update snapshots
shuhuiluo May 22, 2024
5c61309
Merge and update snapshots
shuhuiluo May 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .forge-snapshots/TickMathGetTickAtSqrtPrice.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
216101
188818
2 changes: 1 addition & 1 deletion .forge-snapshots/initialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
61173
60888
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
19986
19788
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap with native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
114924
114650
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
130089
129815
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA fee on unspecified.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
179527
179253
Original file line number Diff line number Diff line change
@@ -1 +1 @@
110279
110005
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
121626
121352
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133455
133181
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn native 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
122743
122458
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint native output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
144531
144246
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
161161
160887
Original file line number Diff line number Diff line change
@@ -1 +1 @@
217571
217023
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
145362
145088
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
138626
138352
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with lp fee and protocol fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
176624
176350
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with return dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
152398
152124
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
155040
154766
250 changes: 119 additions & 131 deletions src/libraries/TickMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ library TickMath {
assembly {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
// Iterate through 1th to 19th bit of absTick because MAX_TICK < 2**20
// Equivalent to:
// for i in range(1, 20):
// if absTick & 2 ** i:
// price = price * (2 ** 128 / 1.0001 ** (2 ** (i - 1))) / 2 ** 128
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
Expand Down Expand Up @@ -117,7 +122,7 @@ library TickMath {
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be < because the price can never reach the price at the max tick
// second inequality must be >= because the price can never reach the price at the max tick
/// @solidity memory-safe-assembly
assembly {
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
Expand All @@ -129,146 +134,129 @@ library TickMath {
}
}

uint256 price = uint256(sqrtPriceX96) << 32;

uint256 r = price;
uint256 msb = 0;

assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
// Find the most significant bit of `sqrtPriceX96`, 160 > msb >= 32.
uint256 msb;
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
let x := sqrtPriceX96
msb := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
msb := or(msb, shl(6, lt(0xffffffffffffffff, shr(msb, x))))
msb := or(msb, shl(5, lt(0xffffffff, shr(msb, x))))
msb := or(msb, shl(4, lt(0xffff, shr(msb, x))))
msb := or(msb, shl(3, lt(0xff, shr(msb, x))))
msb :=
or(
msb,
byte(
and(0x1f, shr(shr(msb, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000
)
)
}

// 2**(msb - 95) > sqrtPrice >= 2**(msb - 96)
// the integer part of log_2(sqrtPrice) * 2**64 = (msb - 96) << 64, 8.64 number
int256 log_2 = (int256(msb) - 96) << 64;
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
// Get the first 128 significant figures of `sqrtPriceX96`.
// r = sqrtPriceX96 / 2**(msb - 127), where 2**128 > r >= 2**127
// sqrtPrice = 2**(msb - 96) * r / 2**127, in floating point math
// Shift left first because 160 > msb >= 32. If we shift right first, we'll lose precision.
let r := shr(sub(msb, 31), shl(96, sqrtPriceX96))

if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
// Approximate `log_2` to 14 binary digits after decimal
// Check whether r >= sqrt(2) * 2**127 for 2**128 > r >= 2**127
// 2**256 > square >= 2**254
let square := mul(r, r)
// f := square >= 2**255
let f := slt(square, 0)
// r := square >> 128 if square >= 2**255 else square >> 127
r := shr(127, shr(f, square))
log_2 := or(shl(63, f), log_2)

int256 log_2 = (int256(msb) - 128) << 64;
square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(62, f), log_2)

assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(61, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(60, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(59, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(58, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(57, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(56, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(55, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(54, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(53, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(52, f), log_2)

square := mul(r, r)
f := slt(square, 0)
r := shr(127, shr(f, square))
log_2 := or(shl(51, f), log_2)

log_2 := or(shl(50, slt(mul(r, r), 0)), log_2)
}

// sqrtPrice = sqrt(1.0001^tick)
// tick = log_{sqrt(1.0001)}(sqrtPrice) = log_2(sqrtPrice) / log_2(sqrt(1.0001))
// 2**64 / log_2(sqrt(1.0001)) = 255738958999603826347141
int24 tickLow;
int24 tickHi;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
let log_sqrt10001 := mul(log_2, 255738958999603826347141) // 128.128 number
tickLow := sar(128, sub(log_sqrt10001, 3402992956809132418596140100660247210))
tickHi := sar(128, add(log_sqrt10001, 291339464771989622907027621153398088495))
}

int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
// Equivalent: tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
if (tickLow != tickHi) {
uint160 sqrtPriceAtTickHi = getSqrtPriceAtTick(tickHi);
assembly {
tickLow := sub(tickHi, gt(sqrtPriceAtTickHi, sqrtPriceX96))
}
return tickLow;
} else {
return tickLow;
}
}
}
}
Loading