Skip to content

Commit

Permalink
Optimize TickMath bounds check in assembly (#669)
Browse files Browse the repository at this point in the history
* Optimize bounds check in `TickMath.getTickAtSqrtPrice`

A new constant, MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE, is introduced for efficient bounds checking. The getTickAtSqrtPrice function is further optimized by using assembly-level operations for checking boundary conditions, which enhances the overall performance. It keeps the price within the specified bounds and throws an "InvalidSqrtPrice" error if the price is out of range.

* Do `absTick` bounds check in assembly in `getSqrtPriceAtTick`

Updated the MAX_TICK constant value from `-MIN_TICK` to `887272` in TickMath.sol. Enhanced safety check within `getSqrtPriceAtTick` function, replacing previous condition with an assembly block to add memory-safe verification. The new check reverts with `InvalidTick()` if the absolute value of `tick` exceeds `MAX_TICK`.

* Add fuzz tests for boundary conditions in `TickMath`

This commit adds fuzz tests in the `TickMath` test suite to verify that an exception is thrown when exceeding the allowed tick and square root price ranges. The test cases are designed to provide random values that exceed the range of values that the functions `getSqrtPriceAtTick` and `getTickAtSqrtPrice` can operate on without errors.

* Update gas snapshots
  • Loading branch information
shuhuiluo committed May 21, 2024
1 parent eb6a671 commit c32b28b
Show file tree
Hide file tree
Showing 35 changed files with 78 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/TickMathGetTickAtSqrtPrice.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
224621
218521
Original file line number Diff line number Diff line change
@@ -1 +1 @@
153427
153346
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
331093
331012
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
286086
286005
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
143637
143556
Original file line number Diff line number Diff line change
@@ -1 +1 @@
301605
301524
2 changes: 1 addition & 1 deletion .forge-snapshots/initialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
61775
61714
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
21575
21463
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
186559
186473
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
122927
122841
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
119714
119628
Original file line number Diff line number Diff line change
@@ -1 +1 @@
104746
104665
2 changes: 1 addition & 1 deletion .forge-snapshots/simple addLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
167238
167157
Original file line number Diff line number Diff line change
@@ -1 +1 @@
98343
98257
2 changes: 1 addition & 1 deletion .forge-snapshots/simple removeLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
90383
90297
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 @@
116430
116207
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
131591
131368
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 @@
182729
182506
Original file line number Diff line number Diff line change
@@ -1 +1 @@
112303
112161
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123646
123504
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 @@
135638
135496
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 @@
124901
124759
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 @@
146895
146753
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 @@
163445
163222
Original file line number Diff line number Diff line change
@@ -1 +1 @@
221557
221192
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 @@
147646
147423
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123658
123516
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 @@
179826
179603
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 @@
155505
155282
Original file line number Diff line number Diff line change
@@ -1 +1 @@
192
197
Original file line number Diff line number Diff line change
@@ -1 +1 @@
192
197
Original file line number Diff line number Diff line change
@@ -1 +1 @@
192
197
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 @@
158108
157885
28 changes: 25 additions & 3 deletions src/libraries/TickMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
int24 internal constant MAX_TICK = 887272;

/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
Expand All @@ -24,6 +24,9 @@ library TickMath {
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;

/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
Expand Down Expand Up @@ -55,7 +58,15 @@ library TickMath {
// Either case, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
if (absTick > uint256(int256(MAX_TICK))) revert InvalidTick();
// Equivalent: if (absTick > MAX_TICK) revert InvalidTick();
/// @solidity memory-safe-assembly
assembly {
if gt(absTick, MAX_TICK) {
// store 4-byte selector of "InvalidTick()" at memory [0x1c, 0x20)
mstore(0, 0xce8ef7fc)
revert(0x1c, 0x04)
}
}

uint256 price =
absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
Expand Down Expand Up @@ -100,8 +111,19 @@ library TickMath {
/// @return tick The greatest tick for which the price is less than or equal to the input price
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
if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
/// @solidity memory-safe-assembly
assembly {
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if gt(sub(sqrtPriceX96, MIN_SQRT_PRICE), MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
// store 4-byte selector of "InvalidSqrtPrice()" at memory [0x1c, 0x20)
mstore(0, 0x31efafe8)
revert(0x1c, 0x04)
}
}

uint256 price = uint256(sqrtPriceX96) << 32;

uint256 r = price;
Expand Down
20 changes: 20 additions & 0 deletions test/libraries/TickMath.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ contract TickMathTestTest is Test, JavascriptFfi, GasSnapshot {
tickMath.getSqrtPriceAtTick(MAX_TICK + 1);
}

function test_fuzz_getSqrtPriceAtTick_throwsForTooLarge(int24 tick) public {
if (tick > 0) {
tick = int24(bound(tick, MAX_TICK + 1, type(int24).max));
} else {
tick = int24(bound(tick, type(int24).min, MIN_TICK - 1));
}
vm.expectRevert(TickMath.InvalidTick.selector);
tickMath.getSqrtPriceAtTick(tick);
}

function test_getSqrtPriceAtTick_isValidMinTick() public view {
assertEq(tickMath.getSqrtPriceAtTick(MIN_TICK), tickMath.MIN_SQRT_PRICE());
assertEq(tickMath.getSqrtPriceAtTick(MIN_TICK), 4295128739);
Expand Down Expand Up @@ -102,6 +112,16 @@ contract TickMathTestTest is Test, JavascriptFfi, GasSnapshot {
tickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE);
}

function test_fuzz_getTickAtSqrtPrice_throwsForInvalid(uint160 sqrtPriceX96, bool gte) public {
if (gte) {
sqrtPriceX96 = uint160(bound(sqrtPriceX96, MAX_SQRT_PRICE, type(uint160).max));
} else {
sqrtPriceX96 = uint160(bound(sqrtPriceX96, 0, MIN_SQRT_PRICE - 1));
}
vm.expectRevert(TickMath.InvalidSqrtPrice.selector);
tickMath.getTickAtSqrtPrice(sqrtPriceX96);
}

function test_getTickAtSqrtPrice_isValidMinSqrtPrice() public view {
assertEq(tickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE), MIN_TICK);
}
Expand Down

0 comments on commit c32b28b

Please sign in to comment.