From 677b62f6a1542d9163d1d58c102a39516614b1e6 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 01:12:45 -0700 Subject: [PATCH 01/25] Add `SqrtPriceMath` forge tests and snapshot --- .gas-snapshot | 139 +++++++ contracts/test/SqrtPriceMathTest.sol | 32 ++ test/__snapshots__/SqrtPriceMath.spec.ts.snap | 2 +- test/foundry-tests/SqrtPriceMath.t.sol | 382 ++++++++++++++++++ 4 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 .gas-snapshot create mode 100644 test/foundry-tests/SqrtPriceMath.t.sol diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 000000000..5b920f4f0 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,139 @@ +FeesTest:testCollectFees() (gas: 677978) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595461, ~: 603454) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170668, ~: 180674) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87728, ~: 92991) +FeesTest:testInitializeFailsNoHook() (gas: 18262) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125539, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 62944, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 62851, ~: 65866) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615596) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944544, ~: 944544) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561690, ~: 563432) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631917) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778188) +HooksTest:testAfterDonateInvalidReturn() (gas: 442640) +HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) +HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323999) +HooksTest:testAfterSwapInvalidReturn() (gas: 159503) +HooksTest:testBeforeDonateInvalidReturn() (gas: 442660) +HooksTest:testBeforeInitializeInvalidReturn() (gas: 844205) +HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) +HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) +HooksTest:testDonateSucceedsWithHook() (gas: 531649) +HooksTest:testGas() (gas: 38344) +HooksTest:testInitializeSucceedsWithHook() (gas: 6724248) +HooksTest:testInvalidIfNoFlags() (gas: 1108) +HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) +HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) +HooksTest:testIsValidIfDynamicFee() (gas: 935) +HooksTest:testModifyPositionSucceedsWithHook() (gas: 280476) +HooksTest:testSwapSucceedsWithHook() (gas: 109164) +HooksTest:testValidateHookAddressAfterDonate(uint152) (runs: 256, μ: 1854, ~: 1854) +HooksTest:testValidateHookAddressAfterInitialize(uint152) (runs: 256, μ: 1855, ~: 1855) +HooksTest:testValidateHookAddressAfterModify(uint152) (runs: 256, μ: 1835, ~: 1835) +HooksTest:testValidateHookAddressAfterSwap(uint152) (runs: 256, μ: 1855, ~: 1855) +HooksTest:testValidateHookAddressAllHooks(uint152) (runs: 256, μ: 1667, ~: 1667) +HooksTest:testValidateHookAddressBeforeAndAfterDonate(uint152) (runs: 256, μ: 1804, ~: 1804) +HooksTest:testValidateHookAddressBeforeAndAfterInitialize(uint152) (runs: 256, μ: 1828, ~: 1828) +HooksTest:testValidateHookAddressBeforeAndAfterModify(uint152) (runs: 256, μ: 1793, ~: 1793) +HooksTest:testValidateHookAddressBeforeAndAfterSwap(uint152) (runs: 256, μ: 1785, ~: 1785) +HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: 1812) +HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) +HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) +HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4772, ~: 4715) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4811, ~: 4758) +HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) +OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) +OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) +OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105451, ~: 105623) +PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) +PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240263) +PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501748) +PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559682) +PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118730) +PoolManagerTest:testExtsloadForPoolPrice() (gas: 76983) +PoolManagerTest:testExtsloadMultipleSlots() (gas: 7371631) +PoolManagerTest:testGasDonateOneToken() (gas: 513217) +PoolManagerTest:testGasMint() (gas: 320788) +PoolManagerTest:testGasMintWithHooks() (gas: 939648) +PoolManagerTest:testGasMintWithNative() (gas: 305794) +PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) +PoolManagerTest:testGasSwap() (gas: 197796) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 670079) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711413) +PoolManagerTest:testGasSwapWithHooks() (gas: 778549) +PoolManagerTest:testGasSwapWithNative() (gas: 197848) +PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) +PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) +PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009699) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 262288, ~: 275477) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264643, ~: 266141) +PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946423) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807650, ~: 809548) +PoolManagerTest:testNoOpLockIsOk() (gas: 87262) +PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112582, ~: 112734) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18547, ~: 13419) +PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) +PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) +PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) +PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58914, ~: 59255) +PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680457, ~: 680631) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609305, ~: 609469) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50216, ~: 50391) +PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) +PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085999) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536789) +PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 108007) +PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 983116) +PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650654) +PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106299) +PoolManagerTest:testSwapUse1155AsInput() (gas: 714715) +PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 12094, ~: 5734) +PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 780473, ~: 7280) +SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) +SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) +SafeCastTest:testToInt256(uint256) (runs: 256, μ: 993, ~: 450) +SafeCastTest:testToUint160(uint256) (runs: 256, μ: 1231, ~: 454) +TestBalanceDelta:testAdd(int128,int128,int128,int128) (runs: 256, μ: 4750, ~: 4750) +TestBalanceDelta:testSub(int128,int128,int128,int128) (runs: 256, μ: 4724, ~: 4724) +TestBalanceDelta:testToBalanceDelta() (gas: 968) +TestBalanceDelta:testToBalanceDelta(int128,int128) (runs: 256, μ: 613, ~: 613) +TestBitMath:testLeastSignificantBit(uint256) (runs: 256, μ: 4182, ~: 3748) +TestBitMath:testLeastSignificantBitMaxUint256() (gas: 697) +TestBitMath:testLeastSignificantBitOne() (gas: 715) +TestBitMath:testLeastSignificantBitPowersOfTwo() (gas: 151449) +TestBitMath:testLeastSignificantBitTwo() (gas: 673) +TestBitMath:testLeastSignificantBitZero() (gas: 3086) +TestBitMath:testLsbGas() (gas: 115496) +TestBitMath:testMostSignificantBit(uint256) (runs: 256, μ: 14893, ~: 7241) +TestBitMath:testMostSignificantBitMaxUint256() (gas: 650) +TestBitMath:testMostSignificantBitOne() (gas: 538) +TestBitMath:testMostSignificantBitPowersOfTwo() (gas: 125789) +TestBitMath:testMostSignificantBitTwo() (gas: 523) +TestBitMath:testMostSignificantBitZero() (gas: 3087) +TestBitMath:testMsbGas() (gas: 115335) +TestDelegateCall:testCanCallIntoPrivateMethodWithModifier() (gas: 5426) +TestDelegateCall:testCannotDelegateCallPrivateMethodWithModifier() (gas: 8641) +TestDelegateCall:testDelegateCallNoModifier() (gas: 5734) +TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) +TestDelegateCall:testGasOverhead() (gas: 13709) +TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) +TestDynamicFees:testSwapWorks() (gas: 104536) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13597, ~: 13389) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13381, ~: 13034) +TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13125, ~: 13021) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18919, ~: 19046) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21815, ~: 21117) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 14020, ~: 13273) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14578, ~: 14222) +TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 363498) +TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 337873) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 315422) \ No newline at end of file diff --git a/contracts/test/SqrtPriceMathTest.sol b/contracts/test/SqrtPriceMathTest.sol index d4bfd66a2..d78eb9d7a 100644 --- a/contracts/test/SqrtPriceMathTest.sol +++ b/contracts/test/SqrtPriceMathTest.sol @@ -4,6 +4,22 @@ pragma solidity ^0.8.19; import {SqrtPriceMath} from "../libraries/SqrtPriceMath.sol"; contract SqrtPriceMathTest { + function getNextSqrtPriceFromAmount0RoundingUp(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + external + pure + returns (uint160) + { + return SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + } + + function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + external + pure + returns (uint160) + { + return SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + } + function getNextSqrtPriceFromInput(uint160 sqrtP, uint128 liquidity, uint256 amountIn, bool zeroForOne) external pure @@ -76,4 +92,20 @@ contract SqrtPriceMathTest { SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); return gasBefore - gasleft(); } + + function getAmount0DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + external + pure + returns (int256) + { + return SqrtPriceMath.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } + + function getAmount1DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + external + pure + returns (int256) + { + return SqrtPriceMath.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } } diff --git a/test/__snapshots__/SqrtPriceMath.spec.ts.snap b/test/__snapshots__/SqrtPriceMath.spec.ts.snap index d6e34f8f5..823609edf 100644 --- a/test/__snapshots__/SqrtPriceMath.spec.ts.snap +++ b/test/__snapshots__/SqrtPriceMath.spec.ts.snap @@ -10,7 +10,7 @@ exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = tru exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `567`; -exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `761`; +exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `750`; exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `859`; diff --git a/test/foundry-tests/SqrtPriceMath.t.sol b/test/foundry-tests/SqrtPriceMath.t.sol new file mode 100644 index 000000000..41a180829 --- /dev/null +++ b/test/foundry-tests/SqrtPriceMath.t.sol @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {Test} from "forge-std/Test.sol"; +import {TickMath} from "../../contracts/libraries/TickMath.sol"; +import {SqrtPriceMathTest} from "../../contracts/test/SqrtPriceMathTest.sol"; +import "../../contracts/libraries/SqrtPriceMath.sol"; + +contract TestSqrtPriceMath is Test, GasSnapshot { + // Wrapper contracts that expose the SqrtPriceMath library. Useful for catching reverts. + SqrtPriceMathTest internal wrapper; + SqrtPriceMathReference internal refWrapper; + + function setUp() public { + wrapper = new SqrtPriceMathTest(); + refWrapper = new SqrtPriceMathReference(); + } + + /// @dev Bound a `uint160` to between `MIN_SQRT_RATIO` and `MAX_SQRT_RATIO`. + function boundUint160(uint160 x) internal view returns (uint160) { + return uint160(bound(x, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO)); + } + + /// @dev Get a deterministic pseudo-random number. + function pseudoRandom(uint256 seed) internal pure returns (uint256) { + return uint256(keccak256(abi.encode(seed))); + } + + function pseudoRandomUint160(uint256 seed) internal pure returns (uint160) { + return uint160(pseudoRandom(seed)); + } + + function pseudoRandomUint128(uint256 seed) internal pure returns (uint128) { + return uint128(pseudoRandom(seed)); + } + + function pseudoRandomInt128(uint256 seed) internal pure returns (int128) { + return int128(int256(pseudoRandom(seed))); + } + + function testFuzzGetNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external { + sqrtPX96 = boundUint160(sqrtPX96); + try refWrapper.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add) returns ( + uint160 expected + ) { + assertEq(wrapper.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + } + } + + function testFuzzGetNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external { + liquidity = uint128(bound(liquidity, 1, type(uint128).max)); + sqrtPX96 = boundUint160(sqrtPX96); + amount = bound(amount, 0, FullMath.mulDiv(type(uint160).max, liquidity, FixedPoint96.Q96)); + try refWrapper.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add) returns ( + uint160 expected + ) { + assertEq(wrapper.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + } + } + + function testFuzzGetNextSqrtPriceFromInput(uint160 sqrtPX96, uint128 liquidity, uint256 amountIn, bool zeroForOne) + external + { + try refWrapper.getNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) returns (uint160 expected) { + assertEq(wrapper.getNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne); + } + } + + function testFuzzGetNextSqrtPriceFromOutput(uint160 sqrtPX96, uint128 liquidity, uint256 amountOut, bool zeroForOne) + external + { + try refWrapper.getNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) returns (uint160 expected) + { + assertEq(wrapper.getNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne); + } + } + + function testFuzzGetAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + external + { + try refWrapper.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) returns (uint256 expected) { + assertEq(wrapper.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp); + } + } + + function testFuzzGetAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + external + { + try refWrapper.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) returns (uint256 expected) { + assertEq(wrapper.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp); + } + } + + function testFuzzGetAmount0DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) external { + try refWrapper.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity) returns (int256 expected) { + assertEq(wrapper.getAmount0DeltaSigned(sqrtRatioAX96, sqrtRatioBX96, liquidity), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getAmount0DeltaSigned(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } + } + + function testFuzzGetAmount1DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) external { + try refWrapper.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity) returns (int256 expected) { + assertEq(wrapper.getAmount1DeltaSigned(sqrtRatioAX96, sqrtRatioBX96, liquidity), expected); + } catch (bytes memory) { + vm.expectRevert(); + wrapper.getAmount1DeltaSigned(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } + } + + function testGasGetNextSqrtPriceFromInput() external view { + for (uint256 i; i < 100; ++i) { + try wrapper.getNextSqrtPriceFromInput( + pseudoRandomUint160(i), pseudoRandomUint128(i ** 2), pseudoRandom(i ** 3), i % 2 == 0 + ) {} catch {} + } + } + + function testGasGetNextSqrtPriceFromOutput() external view { + for (uint256 i; i < 100; ++i) { + try wrapper.getNextSqrtPriceFromOutput( + pseudoRandomUint160(i), pseudoRandomUint128(i ** 2), pseudoRandom(i ** 3), i % 2 == 0 + ) {} catch {} + } + } + + function testGasGetAmount0DeltaSigned() external view { + for (uint256 i; i < 100; ++i) { + try wrapper.getAmount0DeltaSigned( + pseudoRandomUint160(i), pseudoRandomUint160(i ** 2), pseudoRandomInt128(i ** 3) + ) {} catch {} + } + } + + function testGasGetAmount1DeltaSigned() external view { + for (uint256 i; i < 100; ++i) { + try wrapper.getAmount1DeltaSigned( + pseudoRandomUint160(i), pseudoRandomUint160(i ** 2), pseudoRandomInt128(i ** 3) + ) {} catch {} + } + } +} + +/// @notice A reference implementation of the functions in SqrtPriceMath +contract SqrtPriceMathReference { + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of currency0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the currency0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of currency0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of currency0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + public + pure + returns (uint160) + { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + unchecked { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) { + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + } + } + // denominator is checked for overflow + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount)); + } else { + unchecked { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of currency1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the currency1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of currency1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of currency1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + public + pure + returns (uint160) + { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return (uint256(sqrtPX96) + quotient).toUint160(); + } else { + uint256 quotient = ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + + /// @notice Gets the next sqrt price given an input amount of currency0 or currency1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of currency0, or currency1, is being swapped in + /// @param zeroForOne Whether the amount in is currency0 or currency1 + /// @return sqrtQX96 The price after adding the input amount to currency0 or currency1 + function getNextSqrtPriceFromInput(uint160 sqrtPX96, uint128 liquidity, uint256 amountIn, bool zeroForOne) + public + pure + returns (uint160 sqrtQX96) + { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of currency0 or currency1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of currency0, or currency1, is being swapped out + /// @param zeroForOne Whether the amount out is currency0 or currency1 + /// @return sqrtQX96 The price after removing the output amount of currency0 or currency1 + function getNextSqrtPriceFromOutput(uint160 sqrtPX96, uint128 liquidity, uint256 amountOut, bool zeroForOne) + public + pure + returns (uint160 sqrtQX96) + { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of currency0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + public + pure + returns (uint256 amount0) + { + unchecked { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return roundUp + ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), sqrtRatioAX96) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of currency1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + public + pure + returns (uint256 amount1) + { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed currency0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of currency0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + public + pure + returns (int256 amount0) + { + unchecked { + return liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + } + + /// @notice Helper that gets signed currency1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of currency1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + public + pure + returns (int256 amount1) + { + unchecked { + return liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + } +} From 5d4f82f4779acada4b9b02566ce8c5cc36df75ed Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 02:10:06 -0700 Subject: [PATCH 02/25] Optimize `getNextSqrtPriceFromAmount0RoundingUp` --- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .gas-snapshot | 70 +++++++++---------- contracts/libraries/SqrtPriceMath.sol | 24 ++++--- contracts/libraries/UnsafeMath.sol | 24 +++++++ test/__snapshots__/SqrtPriceMath.spec.ts.snap | 4 +- 6 files changed, 77 insertions(+), 49 deletions(-) diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 1fcf323aa..d21929d1e 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -161637 \ No newline at end of file +161620 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 1396b4fbf..5b056ed13 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -146344 \ No newline at end of file +146327 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index 5b920f4f0..bcc2b3081 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,14 +1,14 @@ FeesTest:testCollectFees() (gas: 677978) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595461, ~: 603454) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170668, ~: 180674) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87728, ~: 92991) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595220, ~: 603454) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 169503, ~: 180657) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87883, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125539, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 62944, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 62851, ~: 65866) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125550, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63210, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63294, ~: 65866) FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615596) FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944544, ~: 944544) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561690, ~: 563432) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561504, ~: 563432) FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631917) FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778188) HooksTest:testAfterDonateInvalidReturn() (gas: 442640) @@ -21,7 +21,7 @@ HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) HooksTest:testDonateSucceedsWithHook() (gas: 531649) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6724248) +HooksTest:testInitializeSucceedsWithHook() (gas: 6716228) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) @@ -41,13 +41,13 @@ HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4772, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4811, ~: 4758) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4766, ~: 4715) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4815, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105451, ~: 105623) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105448, ~: 105623) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240263) PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501748) @@ -61,40 +61,40 @@ PoolManagerTest:testGasMintWithHooks() (gas: 939648) PoolManagerTest:testGasMintWithNative() (gas: 305794) PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) PoolManagerTest:testGasSwap() (gas: 197796) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 670079) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711413) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 670052) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711386) PoolManagerTest:testGasSwapWithHooks() (gas: 778549) PoolManagerTest:testGasSwapWithNative() (gas: 197848) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009699) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 262288, ~: 275477) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264643, ~: 266141) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 261232, ~: 275472) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264660, ~: 266140) PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946423) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807650, ~: 809548) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807700, ~: 809548) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112582, ~: 112734) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18547, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112571, ~: 112734) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18882, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58914, ~: 59255) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58935, ~: 59255) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680457, ~: 680631) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609305, ~: 609469) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50216, ~: 50391) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680465, ~: 680631) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609308, ~: 609469) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50211, ~: 50392) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085999) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536789) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536776) PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 108007) PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 983116) PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650654) PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106299) -PoolManagerTest:testSwapUse1155AsInput() (gas: 714715) +PoolManagerTest:testSwapUse1155AsInput() (gas: 714658) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 12094, ~: 5734) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11376, ~: 5734) PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 780473, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) @@ -125,15 +125,15 @@ TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) TestDynamicFees:testSwapWorks() (gas: 104536) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13597, ~: 13389) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13381, ~: 13034) -TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13125, ~: 13021) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18919, ~: 19046) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21815, ~: 21117) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 14020, ~: 13273) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14578, ~: 14222) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13616, ~: 13451) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13384, ~: 13034) +TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13332, ~: 13176) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13132, ~: 13021) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18841, ~: 18992) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21804, ~: 21107) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 14008, ~: 13260) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14544, ~: 13867) TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 363498) TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 337873) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 315422) \ No newline at end of file +TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 334273) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 313272) \ No newline at end of file diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index 8ee37f947..309149964 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -11,6 +11,7 @@ import {FixedPoint96} from "./FixedPoint96.sol"; /// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas library SqrtPriceMath { using SafeCast for uint256; + using UnsafeMath for *; /// @notice Gets the next sqrt price given a delta of currency0 /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least @@ -18,7 +19,7 @@ library SqrtPriceMath { /// price less in order to not send too much output. /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). - /// @param sqrtPX96 The starting price, i.e. before accounting for the currency0 delta + /// @param sqrtPX96 The starting price, i.e. before accounting for the currency0 delta, must be checked to be > 0 /// @param liquidity The amount of usable liquidity /// @param amount How much of currency0 to add or remove from virtual reserves /// @param add Whether to add or remove the amount of currency0 @@ -34,8 +35,9 @@ library SqrtPriceMath { if (add) { unchecked { - uint256 product; - if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 product = amount * sqrtPX96; + // checks for overflow + if (product.div(amount) == sqrtPX96) { uint256 denominator = numerator1 + product; if (denominator >= numerator1) { // always fits in 160 bits @@ -44,16 +46,18 @@ library SqrtPriceMath { } } // denominator is checked for overflow - return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount)); + return uint160(numerator1.divRoundingUp(numerator1.div(sqrtPX96) + amount)); } else { - unchecked { - uint256 product; + uint256 denominator; + /// @solidity memory-safe-assembly + assembly { // if the product overflows, we know the denominator underflows // in addition, we must check that the denominator does not underflow - require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); - uint256 denominator = numerator1 - product; - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + let product := mul(amount, sqrtPX96) + if iszero(and(eq(div(product, amount), sqrtPX96), gt(numerator1, product))) { revert(0, 0) } + denominator := sub(numerator1, product) } + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); } } @@ -63,7 +67,7 @@ library SqrtPriceMath { /// price less in order to not send too much output. /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity /// @param sqrtPX96 The starting price, i.e., before accounting for the currency1 delta - /// @param liquidity The amount of usable liquidity + /// @param liquidity The amount of usable liquidity, must be checked to be > 0 externally /// @param amount How much of currency1 to add, or remove, from virtual reserves /// @param add Whether to add, or remove, the amount of currency1 /// @return The price after adding or removing `amount` diff --git a/contracts/libraries/UnsafeMath.sol b/contracts/libraries/UnsafeMath.sol index c34d45c44..30cc998ac 100644 --- a/contracts/libraries/UnsafeMath.sol +++ b/contracts/libraries/UnsafeMath.sol @@ -4,6 +4,30 @@ pragma solidity ^0.8.19; /// @title Math functions that do not check inputs or outputs /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks library UnsafeMath { + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(x, y) + } + } + + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := sub(x, y) + } + } + + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := mul(x, y) + } + } + + function div(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := div(x, y) + } + } + /// @notice Returns ceil(x / y) /// @dev division by 0 has unspecified behavior, and must be checked externally /// @param x The dividend diff --git a/test/__snapshots__/SqrtPriceMath.spec.ts.snap b/test/__snapshots__/SqrtPriceMath.spec.ts.snap index 823609edf..ed689c7e5 100644 --- a/test/__snapshots__/SqrtPriceMath.spec.ts.snap +++ b/test/__snapshots__/SqrtPriceMath.spec.ts.snap @@ -10,8 +10,8 @@ exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = tru exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `567`; -exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `750`; +exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `733`; -exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `859`; +exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `805`; exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `500`; From 072a4c2a139cc9ac63bbd343b5a0a708a3014626 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 02:17:45 -0700 Subject: [PATCH 03/25] Optimize `getNextSqrtPriceFromAmount1RoundingDown` --- .gas-snapshot | 66 +++++++++---------- contracts/libraries/SqrtPriceMath.sol | 20 +++--- test/__snapshots__/SqrtPriceMath.spec.ts.snap | 4 +- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index bcc2b3081..305fcbf2b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,16 +1,16 @@ -FeesTest:testCollectFees() (gas: 677978) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595220, ~: 603454) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 169503, ~: 180657) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87883, ~: 92991) +FeesTest:testCollectFees() (gas: 677940) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595201, ~: 603454) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 169355, ~: 180657) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 88028, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125550, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63210, ~: 65959) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 126042, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63387, ~: 65959) FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63294, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615596) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944544, ~: 944544) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561504, ~: 563432) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631917) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778188) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615558) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944505, ~: 944505) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561877, ~: 563432) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631879) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778150) HooksTest:testAfterDonateInvalidReturn() (gas: 442640) HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323999) @@ -21,7 +21,7 @@ HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) HooksTest:testDonateSucceedsWithHook() (gas: 531649) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6716228) +HooksTest:testInitializeSucceedsWithHook() (gas: 6711619) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) @@ -41,13 +41,13 @@ HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4766, ~: 4715) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4768, ~: 4715) HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4815, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105448, ~: 105623) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105470, ~: 105624) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240263) PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501748) @@ -68,23 +68,23 @@ PoolManagerTest:testGasSwapWithNative() (gas: 197848) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009699) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 261232, ~: 275472) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264660, ~: 266140) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260998, ~: 260747) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264590, ~: 262573) PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946423) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807700, ~: 809548) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807620, ~: 809548) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112571, ~: 112734) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18882, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112570, ~: 112733) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18536, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58935, ~: 59255) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58940, ~: 59257) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680465, ~: 680631) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609308, ~: 609469) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50211, ~: 50392) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680480, ~: 680631) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609313, ~: 609475) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50237, ~: 50392) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085999) PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536776) @@ -94,7 +94,7 @@ PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650654) PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106299) PoolManagerTest:testSwapUse1155AsInput() (gas: 714658) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11376, ~: 5734) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11627, ~: 5734) PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 780473, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) @@ -125,15 +125,15 @@ TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) TestDynamicFees:testSwapWorks() (gas: 104536) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13616, ~: 13451) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13384, ~: 13034) -TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13332, ~: 13176) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13132, ~: 13021) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18841, ~: 18992) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21804, ~: 21107) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 14008, ~: 13260) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14544, ~: 13867) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13604, ~: 13389) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13411, ~: 13319) +TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13145, ~: 13021) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18869, ~: 18992) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21762, ~: 21080) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13959, ~: 13212) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14524, ~: 13867) TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 363498) TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 334273) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 313272) \ No newline at end of file +TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 312372) \ No newline at end of file diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index 309149964..692a7bcf4 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -70,32 +70,34 @@ library SqrtPriceMath { /// @param liquidity The amount of usable liquidity, must be checked to be > 0 externally /// @param amount How much of currency1 to add, or remove, from virtual reserves /// @param add Whether to add, or remove, the amount of currency1 - /// @return The price after adding or removing `amount` + /// @return nextSqrtPrice The price after adding or removing `amount` function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) internal pure - returns (uint160) + returns (uint160 nextSqrtPrice) { // if we're adding (subtracting), rounding down requires rounding the quotient down (up) // in both cases, avoid a mulDiv for most inputs if (add) { uint256 quotient = ( amount <= type(uint160).max - ? (amount << FixedPoint96.RESOLUTION) / liquidity + ? (amount << FixedPoint96.RESOLUTION).div(liquidity) : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) ); - return (uint256(sqrtPX96) + quotient).toUint160(); + nextSqrtPrice = (sqrtPX96 + quotient).toUint160(); } else { uint256 quotient = ( amount <= type(uint160).max - ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + ? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity) : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) ); - - require(sqrtPX96 > quotient); - // always fits 160 bits - return uint160(sqrtPX96 - quotient); + /// @solidity memory-safe-assembly + assembly { + if iszero(gt(sqrtPX96, quotient)) { revert(0, 0) } + // always fits 160 bits + nextSqrtPrice := sub(sqrtPX96, quotient) + } } } diff --git a/test/__snapshots__/SqrtPriceMath.spec.ts.snap b/test/__snapshots__/SqrtPriceMath.spec.ts.snap index ed689c7e5..2d9e0b8e7 100644 --- a/test/__snapshots__/SqrtPriceMath.spec.ts.snap +++ b/test/__snapshots__/SqrtPriceMath.spec.ts.snap @@ -8,10 +8,10 @@ exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = fal exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = true 1`] = `603`; -exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `567`; +exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `519`; exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `733`; exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `805`; -exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `500`; +exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `379`; From d8af24f14abcd538255132c3379f1e946cff3633 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 02:23:32 -0700 Subject: [PATCH 04/25] Optimize `getNextSqrtPriceFromInput` and `getNextSqrtPriceFromOutput` by replacing `require` statements with inline assembly --- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .gas-snapshot | 78 +++++++++---------- contracts/libraries/SqrtPriceMath.sol | 14 ++-- test/__snapshots__/SqrtPriceMath.spec.ts.snap | 8 +- 5 files changed, 53 insertions(+), 51 deletions(-) diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index d21929d1e..e95519d24 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -161620 \ No newline at end of file +161570 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 5b056ed13..178516984 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -146327 \ No newline at end of file +146277 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index 305fcbf2b..f2fe862e1 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,16 +1,16 @@ -FeesTest:testCollectFees() (gas: 677940) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595201, ~: 603454) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 169355, ~: 180657) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 88028, ~: 92991) +FeesTest:testCollectFees() (gas: 677900) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 597096, ~: 603454) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170910, ~: 180665) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 88039, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 126042, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63387, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63294, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615558) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944505, ~: 944505) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561877, ~: 563432) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631879) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778150) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125719, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63032, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63117, ~: 65866) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615518) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944465, ~: 944465) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561939, ~: 563432) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631839) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778110) HooksTest:testAfterDonateInvalidReturn() (gas: 442640) HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323999) @@ -21,7 +21,7 @@ HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) HooksTest:testDonateSucceedsWithHook() (gas: 531649) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6711619) +HooksTest:testInitializeSucceedsWithHook() (gas: 6700390) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) @@ -41,13 +41,13 @@ HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4768, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4815, ~: 4758) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4774, ~: 4715) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4810, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105470, ~: 105624) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105466, ~: 105624) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240263) PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501748) @@ -61,40 +61,40 @@ PoolManagerTest:testGasMintWithHooks() (gas: 939648) PoolManagerTest:testGasMintWithNative() (gas: 305794) PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) PoolManagerTest:testGasSwap() (gas: 197796) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 670052) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711386) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 669972) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711306) PoolManagerTest:testGasSwapWithHooks() (gas: 778549) PoolManagerTest:testGasSwapWithNative() (gas: 197848) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009699) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260998, ~: 260747) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264590, ~: 262573) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 261911, ~: 275473) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264671, ~: 266142) PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946423) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807620, ~: 809548) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807818, ~: 809548) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112570, ~: 112733) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18536, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112571, ~: 112734) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18569, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58940, ~: 59257) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58948, ~: 59257) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680480, ~: 680631) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609313, ~: 609475) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50237, ~: 50392) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680467, ~: 680630) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609306, ~: 609475) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50226, ~: 50392) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085999) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536776) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536736) PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 108007) PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 983116) PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650654) PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106299) -PoolManagerTest:testSwapUse1155AsInput() (gas: 714658) +PoolManagerTest:testSwapUse1155AsInput() (gas: 714578) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11627, ~: 5734) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11836, ~: 5734) PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 780473, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) @@ -125,15 +125,15 @@ TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) TestDynamicFees:testSwapWorks() (gas: 104536) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13604, ~: 13389) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13411, ~: 13319) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13612, ~: 13389) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13390, ~: 13145) TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13145, ~: 13021) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18869, ~: 18992) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21762, ~: 21080) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13959, ~: 13212) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14524, ~: 13867) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13131, ~: 13021) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18830, ~: 18992) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21696, ~: 21080) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13932, ~: 13162) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14463, ~: 13817) TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 363498) TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 334273) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 312372) \ No newline at end of file +TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329273) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307372) \ No newline at end of file diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index 692a7bcf4..31c545242 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -113,9 +113,10 @@ library SqrtPriceMath { pure returns (uint160 sqrtQX96) { - require(sqrtPX96 > 0); - require(liquidity > 0); - + /// @solidity memory-safe-assembly + assembly { + if or(iszero(sqrtPX96), iszero(liquidity)) { revert(0, 0) } + } // round to make sure that we don't pass the target price return zeroForOne ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) @@ -134,9 +135,10 @@ library SqrtPriceMath { pure returns (uint160 sqrtQX96) { - require(sqrtPX96 > 0); - require(liquidity > 0); - + /// @solidity memory-safe-assembly + assembly { + if or(iszero(sqrtPX96), iszero(liquidity)) { revert(0, 0) } + } // round to make sure that we pass the target price return zeroForOne ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) diff --git a/test/__snapshots__/SqrtPriceMath.spec.ts.snap b/test/__snapshots__/SqrtPriceMath.spec.ts.snap index 2d9e0b8e7..596aa8b88 100644 --- a/test/__snapshots__/SqrtPriceMath.spec.ts.snap +++ b/test/__snapshots__/SqrtPriceMath.spec.ts.snap @@ -8,10 +8,10 @@ exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = fal exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = true 1`] = `603`; -exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `519`; +exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `469`; -exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `733`; +exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `683`; -exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `805`; +exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `755`; -exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `379`; +exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `329`; From c66b805c70083591bea32af67d0e807bd11beaa6 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 02:46:55 -0700 Subject: [PATCH 05/25] Optimize `getAmount0Delta` by introducing `sort2` and removing branching --- .forge-snapshots/mint with empty hook.snap | 2 +- .forge-snapshots/mint with native token.snap | 2 +- .forge-snapshots/mint.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .forge-snapshots/swap with native.snap | 2 +- .gas-snapshot | 132 +++++++++--------- contracts/libraries/SqrtPriceMath.sol | 48 +++++-- test/__snapshots__/SqrtPriceMath.spec.ts.snap | 8 +- 11 files changed, 115 insertions(+), 89 deletions(-) diff --git a/.forge-snapshots/mint with empty hook.snap b/.forge-snapshots/mint with empty hook.snap index 026af48e2..1a44fb207 100644 --- a/.forge-snapshots/mint with empty hook.snap +++ b/.forge-snapshots/mint with empty hook.snap @@ -1 +1 @@ -320430 \ No newline at end of file +320325 \ No newline at end of file diff --git a/.forge-snapshots/mint with native token.snap b/.forge-snapshots/mint with native token.snap index 650a967fa..db3374f15 100644 --- a/.forge-snapshots/mint with native token.snap +++ b/.forge-snapshots/mint with native token.snap @@ -1 +1 @@ -294443 \ No newline at end of file +294338 \ No newline at end of file diff --git a/.forge-snapshots/mint.snap b/.forge-snapshots/mint.snap index c9f33fe3a..9545b360b 100644 --- a/.forge-snapshots/mint.snap +++ b/.forge-snapshots/mint.snap @@ -1 +1 @@ -313107 \ No newline at end of file +313002 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index e7f314da2..3af0fc7b0 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -67800 \ No newline at end of file +67726 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index e95519d24..d30f152dc 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -161570 \ No newline at end of file +161360 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 178516984..020d3b091 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -146277 \ No newline at end of file +146067 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 09ee532a8..78fa0887f 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -67775 \ No newline at end of file +67701 \ No newline at end of file diff --git a/.forge-snapshots/swap with native.snap b/.forge-snapshots/swap with native.snap index e7f314da2..3af0fc7b0 100644 --- a/.forge-snapshots/swap with native.snap +++ b/.forge-snapshots/swap with native.snap @@ -1 +1 @@ -67800 \ No newline at end of file +67726 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index f2fe862e1..f43a5e4a5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,33 +1,33 @@ -FeesTest:testCollectFees() (gas: 677900) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 597096, ~: 603454) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170910, ~: 180665) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 88039, ~: 92991) +FeesTest:testCollectFees() (gas: 677852) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 596558, ~: 603407) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170287, ~: 180657) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87417, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125719, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63032, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63117, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615518) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944465, ~: 944465) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561939, ~: 563432) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631839) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778110) -HooksTest:testAfterDonateInvalidReturn() (gas: 442640) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 124822, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63210, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63205, ~: 65866) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615471) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944371, ~: 944371) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561767, ~: 563384) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631792) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778100) +HooksTest:testAfterDonateInvalidReturn() (gas: 442535) HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) -HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323999) -HooksTest:testAfterSwapInvalidReturn() (gas: 159503) -HooksTest:testBeforeDonateInvalidReturn() (gas: 442660) +HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323894) +HooksTest:testAfterSwapInvalidReturn() (gas: 159549) +HooksTest:testBeforeDonateInvalidReturn() (gas: 442555) HooksTest:testBeforeInitializeInvalidReturn() (gas: 844205) HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) -HooksTest:testDonateSucceedsWithHook() (gas: 531649) +HooksTest:testDonateSucceedsWithHook() (gas: 531565) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6700390) +HooksTest:testInitializeSucceedsWithHook() (gas: 6692361) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) HooksTest:testIsValidIfDynamicFee() (gas: 935) -HooksTest:testModifyPositionSucceedsWithHook() (gas: 280476) -HooksTest:testSwapSucceedsWithHook() (gas: 109164) +HooksTest:testModifyPositionSucceedsWithHook() (gas: 280392) +HooksTest:testSwapSucceedsWithHook() (gas: 109200) HooksTest:testValidateHookAddressAfterDonate(uint152) (runs: 256, μ: 1854, ~: 1854) HooksTest:testValidateHookAddressAfterInitialize(uint152) (runs: 256, μ: 1855, ~: 1855) HooksTest:testValidateHookAddressAfterModify(uint152) (runs: 256, μ: 1835, ~: 1835) @@ -41,61 +41,61 @@ HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4774, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4810, ~: 4758) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4768, ~: 4715) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4816, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105466, ~: 105624) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105456, ~: 105624) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) -PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240263) -PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501748) -PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559682) -PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118730) +PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240158) +PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501664) +PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559598) +PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118625) PoolManagerTest:testExtsloadForPoolPrice() (gas: 76983) -PoolManagerTest:testExtsloadMultipleSlots() (gas: 7371631) -PoolManagerTest:testGasDonateOneToken() (gas: 513217) -PoolManagerTest:testGasMint() (gas: 320788) -PoolManagerTest:testGasMintWithHooks() (gas: 939648) -PoolManagerTest:testGasMintWithNative() (gas: 305794) +PoolManagerTest:testExtsloadMultipleSlots() (gas: 7335999) +PoolManagerTest:testGasDonateOneToken() (gas: 513133) +PoolManagerTest:testGasMint() (gas: 320704) +PoolManagerTest:testGasMintWithHooks() (gas: 939543) +PoolManagerTest:testGasMintWithNative() (gas: 305710) PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) -PoolManagerTest:testGasSwap() (gas: 197796) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 669972) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 711306) -PoolManagerTest:testGasSwapWithHooks() (gas: 778549) -PoolManagerTest:testGasSwapWithNative() (gas: 197848) +PoolManagerTest:testGasSwap() (gas: 197618) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 669542) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710876) +PoolManagerTest:testGasSwapWithHooks() (gas: 778327) +PoolManagerTest:testGasSwapWithNative() (gas: 197670) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) -PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009699) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 261911, ~: 275473) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264671, ~: 266142) -PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946423) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807818, ~: 809548) +PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009594) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 262260, ~: 275481) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264572, ~: 266140) +PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946318) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807690, ~: 809548) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112571, ~: 112734) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18569, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112582, ~: 112734) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18518, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58948, ~: 59257) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58931, ~: 59257) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680467, ~: 680630) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609306, ~: 609475) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50226, ~: 50392) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680465, ~: 680631) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609295, ~: 609468) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50233, ~: 50392) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) -PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085999) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536736) -PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 108007) -PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 983116) -PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650654) -PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106299) -PoolManagerTest:testSwapUse1155AsInput() (gas: 714578) +PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085598) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536474) +PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 107888) +PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982715) +PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650506) +PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106180) +PoolManagerTest:testSwapUse1155AsInput() (gas: 714390) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11836, ~: 5734) -PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 780473, ~: 7280) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11652, ~: 5734) +PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 772527, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) SafeCastTest:testToInt256(uint256) (runs: 256, μ: 993, ~: 450) @@ -124,16 +124,16 @@ TestDelegateCall:testDelegateCallNoModifier() (gas: 5734) TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) -TestDynamicFees:testSwapWorks() (gas: 104536) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13612, ~: 13389) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13390, ~: 13145) +TestDynamicFees:testSwapWorks() (gas: 104572) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13546, ~: 13261) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13343, ~: 13129) TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13131, ~: 13021) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18830, ~: 18992) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21696, ~: 21080) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13932, ~: 13162) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14463, ~: 13817) -TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 363498) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13132, ~: 13021) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18854, ~: 18992) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21749, ~: 21080) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13922, ~: 13162) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14489, ~: 14118) +TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 354325) TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329273) TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307372) \ No newline at end of file diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index 31c545242..d09e824b0 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -145,6 +145,16 @@ library SqrtPriceMath { : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); } + /// @notice Sorts two `uint160`s and returns them in ascending order + function sort2(uint160 a, uint160 b) internal pure returns (uint160, uint160) { + assembly { + let diff := mul(xor(a, b), lt(b, a)) + a := xor(a, diff) + b := xor(b, diff) + } + return (a, b); + } + /// @notice Gets the amount0 delta between two prices /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) @@ -158,17 +168,33 @@ library SqrtPriceMath { pure returns (uint256 amount0) { - unchecked { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; - - require(sqrtRatioAX96 > 0); - - return roundUp - ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), sqrtRatioAX96) - : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + (sqrtRatioAX96, sqrtRatioBX96) = sort2(sqrtRatioAX96, sqrtRatioBX96); + /// @solidity memory-safe-assembly + assembly { + if iszero(sqrtRatioAX96) { revert(0, 0) } + } + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96.sub(sqrtRatioAX96); + /** + * Equivalent to: + * roundUp + * ? FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96).divRoundingUp(sqrtRatioAX96) + * : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + * If `md = mulDiv(n1, n2, srb) == mulDivRoundingUp(n1, n2, srb)`, then `mulmod(n1, n2, srb) == 0`. + * Add `roundUp && md % sra > 0` to `div(md, sra)`. + * If `md = mulDiv(n1, n2, srb)` and `mulDivRoundingUp(n1, n2, srb)` differs by 1 and `sra > 0`, + * then `(md + 1).divRoundingUp(sra) == md.div(sra) + 1` whether `sra` fully divides `md` or not. + */ + uint256 mulDivResult = FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96); + assembly { + amount0 := + add( + div(mulDivResult, sqrtRatioAX96), + and( + gt(or(mod(mulDivResult, sqrtRatioAX96), mulmod(numerator1, numerator2, sqrtRatioBX96)), 0), + roundUp + ) + ) } } diff --git a/test/__snapshots__/SqrtPriceMath.spec.ts.snap b/test/__snapshots__/SqrtPriceMath.spec.ts.snap index 596aa8b88..032f974ea 100644 --- a/test/__snapshots__/SqrtPriceMath.spec.ts.snap +++ b/test/__snapshots__/SqrtPriceMath.spec.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 1`] = `603`; +exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 1`] = `475`; -exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 2`] = `483`; +exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 2`] = `475`; -exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = false 1`] = `483`; +exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = false 1`] = `475`; -exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = true 1`] = `603`; +exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = true 1`] = `475`; exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `469`; From 59026139a0cfbbbcf71d44d23390ac36c917a3ea Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 03:07:17 -0700 Subject: [PATCH 06/25] Optimize `getAmount1Delta` by introducing `mulDiv96` --- .forge-snapshots/mint with empty hook.snap | 2 +- .forge-snapshots/mint with native token.snap | 2 +- .forge-snapshots/mint.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .forge-snapshots/swap with native.snap | 2 +- .gas-snapshot | 139 +++++++++--------- contracts/libraries/FullMath.sol | 29 ++++ contracts/libraries/SqrtPriceMath.sol | 24 +-- test/foundry-tests/SqrtPriceMath.t.sol | 16 ++ 12 files changed, 138 insertions(+), 86 deletions(-) diff --git a/.forge-snapshots/mint with empty hook.snap b/.forge-snapshots/mint with empty hook.snap index 1a44fb207..c4f005436 100644 --- a/.forge-snapshots/mint with empty hook.snap +++ b/.forge-snapshots/mint with empty hook.snap @@ -1 +1 @@ -320325 \ No newline at end of file +320162 \ No newline at end of file diff --git a/.forge-snapshots/mint with native token.snap b/.forge-snapshots/mint with native token.snap index db3374f15..a9867ad89 100644 --- a/.forge-snapshots/mint with native token.snap +++ b/.forge-snapshots/mint with native token.snap @@ -1 +1 @@ -294338 \ No newline at end of file +294175 \ No newline at end of file diff --git a/.forge-snapshots/mint.snap b/.forge-snapshots/mint.snap index 9545b360b..f3c142e8e 100644 --- a/.forge-snapshots/mint.snap +++ b/.forge-snapshots/mint.snap @@ -1 +1 @@ -313002 \ No newline at end of file +312839 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 3af0fc7b0..79f8dab51 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -67726 \ No newline at end of file +67662 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index d30f152dc..ed4d61579 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -161360 \ No newline at end of file +161296 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 020d3b091..5da556cc0 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -146067 \ No newline at end of file +146003 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 78fa0887f..4d5235a23 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -67701 \ No newline at end of file +67637 \ No newline at end of file diff --git a/.forge-snapshots/swap with native.snap b/.forge-snapshots/swap with native.snap index 3af0fc7b0..79f8dab51 100644 --- a/.forge-snapshots/swap with native.snap +++ b/.forge-snapshots/swap with native.snap @@ -1 +1 @@ -67726 \ No newline at end of file +67662 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index f43a5e4a5..3c5cf1670 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,33 +1,33 @@ -FeesTest:testCollectFees() (gas: 677852) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 596558, ~: 603407) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170287, ~: 180657) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87417, ~: 92991) +FeesTest:testCollectFees() (gas: 677387) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 594491, ~: 603200) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170205, ~: 180657) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87339, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 124822, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63210, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63205, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615471) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 944371, ~: 944371) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561767, ~: 563384) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631792) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 778100) -HooksTest:testAfterDonateInvalidReturn() (gas: 442535) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125260, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63564, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 62673, ~: 65866) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615005) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 943699, ~: 943699) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561250, ~: 563178) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631326) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 777583) +HooksTest:testAfterDonateInvalidReturn() (gas: 442372) HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) -HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323894) -HooksTest:testAfterSwapInvalidReturn() (gas: 159549) -HooksTest:testBeforeDonateInvalidReturn() (gas: 442555) +HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323731) +HooksTest:testAfterSwapInvalidReturn() (gas: 159386) +HooksTest:testBeforeDonateInvalidReturn() (gas: 442392) HooksTest:testBeforeInitializeInvalidReturn() (gas: 844205) HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) -HooksTest:testDonateSucceedsWithHook() (gas: 531565) +HooksTest:testDonateSucceedsWithHook() (gas: 531435) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6692361) +HooksTest:testInitializeSucceedsWithHook() (gas: 6690352) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) HooksTest:testIsValidIfDynamicFee() (gas: 935) -HooksTest:testModifyPositionSucceedsWithHook() (gas: 280392) -HooksTest:testSwapSucceedsWithHook() (gas: 109200) +HooksTest:testModifyPositionSucceedsWithHook() (gas: 280261) +HooksTest:testSwapSucceedsWithHook() (gas: 109070) HooksTest:testValidateHookAddressAfterDonate(uint152) (runs: 256, μ: 1854, ~: 1854) HooksTest:testValidateHookAddressAfterInitialize(uint152) (runs: 256, μ: 1855, ~: 1855) HooksTest:testValidateHookAddressAfterModify(uint152) (runs: 256, μ: 1835, ~: 1835) @@ -42,60 +42,60 @@ HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4768, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4816, ~: 4758) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4813, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105456, ~: 105624) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105457, ~: 105624) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) -PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1240158) -PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501664) -PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559598) -PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118625) +PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1239964) +PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501509) +PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559443) +PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118431) PoolManagerTest:testExtsloadForPoolPrice() (gas: 76983) -PoolManagerTest:testExtsloadMultipleSlots() (gas: 7335999) -PoolManagerTest:testGasDonateOneToken() (gas: 513133) -PoolManagerTest:testGasMint() (gas: 320704) -PoolManagerTest:testGasMintWithHooks() (gas: 939543) -PoolManagerTest:testGasMintWithNative() (gas: 305710) +PoolManagerTest:testExtsloadMultipleSlots() (gas: 7234506) +PoolManagerTest:testGasDonateOneToken() (gas: 512978) +PoolManagerTest:testGasMint() (gas: 320573) +PoolManagerTest:testGasMintWithHooks() (gas: 939380) +PoolManagerTest:testGasMintWithNative() (gas: 305580) PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) -PoolManagerTest:testGasSwap() (gas: 197618) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 669542) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710876) -PoolManagerTest:testGasSwapWithHooks() (gas: 778327) -PoolManagerTest:testGasSwapWithNative() (gas: 197670) +PoolManagerTest:testGasSwap() (gas: 197464) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 669233) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710568) +PoolManagerTest:testGasSwapWithHooks() (gas: 778135) +PoolManagerTest:testGasSwapWithNative() (gas: 197516) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) -PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009594) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 262260, ~: 275481) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264572, ~: 266140) -PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946318) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807690, ~: 809548) +PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009431) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260980, ~: 275317) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264542, ~: 265987) +PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946155) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807567, ~: 809354) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112582, ~: 112734) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18518, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112574, ~: 112733) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18544, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58931, ~: 59257) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58927, ~: 59255) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680465, ~: 680631) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609295, ~: 609468) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50233, ~: 50392) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680463, ~: 680630) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609312, ~: 609474) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50229, ~: 50392) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) -PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085598) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536474) -PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 107888) -PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982715) -PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650506) -PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106180) -PoolManagerTest:testSwapUse1155AsInput() (gas: 714390) +PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085179) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536216) +PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 107786) +PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982296) +PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650378) +PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106078) +PoolManagerTest:testSwapUse1155AsInput() (gas: 713977) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11652, ~: 5734) -PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 772527, ~: 7280) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11098, ~: 5734) +PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 755595, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) SafeCastTest:testToInt256(uint256) (runs: 256, μ: 993, ~: 450) @@ -124,16 +124,17 @@ TestDelegateCall:testDelegateCallNoModifier() (gas: 5734) TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) -TestDynamicFees:testSwapWorks() (gas: 104572) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13546, ~: 13261) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13343, ~: 13129) -TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13333, ~: 13176) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13132, ~: 13021) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18854, ~: 18992) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21749, ~: 21080) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13922, ~: 13162) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14489, ~: 14118) -TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 354325) -TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 365584) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329273) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307372) \ No newline at end of file +TestDynamicFees:testSwapWorks() (gas: 104442) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13570, ~: 13283) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13280, ~: 12952) +TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13149, ~: 13071) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 12945, ~: 12894) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18898, ~: 19014) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21776, ~: 21109) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13931, ~: 13162) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14512, ~: 14140) +TestSqrtPriceMath:testFuzzMulDiv96(uint256,uint256) (runs: 256, μ: 3733, ~: 3677) +TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 354280) +TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 317026) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329295) +TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307328) \ No newline at end of file diff --git a/contracts/libraries/FullMath.sol b/contracts/libraries/FullMath.sol index b2616c9f0..e89fe7974 100644 --- a/contracts/libraries/FullMath.sol +++ b/contracts/libraries/FullMath.sol @@ -103,6 +103,35 @@ library FullMath { } } + /// @notice Calculates x * y / 2^96 with full precision. + function mulDiv96(uint256 x, uint256 y) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + // 512-bit multiply `[prod1 prod0] = x * y`. + // Compute the product mod `2**256` and mod `2**256 - 1` + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that `product = prod1 * 2**256 + prod0`. + + // Least significant 256 bits of the product. + let prod0 := mul(x, y) + let mm := mulmod(x, y, not(0)) + // Most significant 256 bits of the product. + let prod1 := sub(mm, add(prod0, lt(mm, prod0))) + + // Make sure the result is less than `2**256`. + if iszero(gt(0x1000000000000000000000000, prod1)) { + // Store the function selector of `FullMulDivFailed()`. + mstore(0x00, 0xae47f702) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Divide [prod1 prod0] by 2^96. + result := or(shr(96, prod0), shl(160, prod1)) + } + } + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 /// @param a The multiplicand /// @param b The multiplier diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index d09e824b0..83085b071 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -190,10 +190,7 @@ library SqrtPriceMath { amount0 := add( div(mulDivResult, sqrtRatioAX96), - and( - gt(or(mod(mulDivResult, sqrtRatioAX96), mulmod(numerator1, numerator2, sqrtRatioBX96)), 0), - roundUp - ) + and(gt(or(mod(mulDivResult, sqrtRatioAX96), mulmod(numerator1, numerator2, sqrtRatioBX96)), 0), roundUp) ) } } @@ -210,11 +207,20 @@ library SqrtPriceMath { pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - return roundUp - ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) - : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + (sqrtRatioAX96, sqrtRatioBX96) = sort2(sqrtRatioAX96, sqrtRatioBX96); + uint256 numerator = sqrtRatioBX96.sub(sqrtRatioAX96); + uint256 denominator = FixedPoint96.Q96; + /** + * Equivalent to: + * amount1 = roundUp + * ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + * : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. + */ + amount1 = FullMath.mulDiv96(liquidity, numerator); + assembly { + amount1 := add(amount1, and(gt(mulmod(liquidity, numerator, denominator), 0), roundUp)) + } } /// @notice Helper that gets signed currency0 delta diff --git a/test/foundry-tests/SqrtPriceMath.t.sol b/test/foundry-tests/SqrtPriceMath.t.sol index 41a180829..a2b294efa 100644 --- a/test/foundry-tests/SqrtPriceMath.t.sol +++ b/test/foundry-tests/SqrtPriceMath.t.sol @@ -39,6 +39,22 @@ contract TestSqrtPriceMath is Test, GasSnapshot { return int128(int256(pseudoRandom(seed))); } + /// @notice Test `mulDiv96` against `mulDiv` with a denominator of `Q96`. + /// TODO: move it to FullMath.t.sol when available + function testFuzzMulDiv96(uint256 a, uint256 b) external { + // Most significant 256 bits of the product. + uint256 prod1; + assembly { + // Least significant 256 bits of the product. + let prod0 := mul(a, b) + let mm := mulmod(a, b, not(0)) + prod1 := sub(mm, add(prod0, lt(mm, prod0))) + } + // assume that the `mulDiv` will not overflow + vm.assume(FixedPoint96.Q96 > prod1); + assertEq(FullMath.mulDiv96(a, b), FullMath.mulDiv(a, b, FixedPoint96.Q96)); + } + function testFuzzGetNextSqrtPriceFromAmount0RoundingUp( uint160 sqrtPX96, uint128 liquidity, From ced1a76991d068d2f1a29cfd98c3da831ba38b33 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <shuhui@aperture.finance> Date: Fri, 16 Jun 2023 03:20:42 -0700 Subject: [PATCH 07/25] Optimize signed `getAmount0Delta` and `getAmount1Delta` using bit ops --- .forge-snapshots/mint with empty hook.snap | 2 +- .forge-snapshots/mint with native token.snap | 2 +- .forge-snapshots/mint.snap | 2 +- .gas-snapshot | 118 +++++++++--------- contracts/libraries/SqrtPriceMath.sol | 58 +++++++-- .../PoolManager.gas.spec.ts.snap | 72 +++++------ test/__snapshots__/PoolManager.spec.ts.snap | 2 +- test/__snapshots__/SwapMath.spec.ts.snap | 16 +-- 8 files changed, 157 insertions(+), 115 deletions(-) diff --git a/.forge-snapshots/mint with empty hook.snap b/.forge-snapshots/mint with empty hook.snap index c4f005436..88b6c6c7d 100644 --- a/.forge-snapshots/mint with empty hook.snap +++ b/.forge-snapshots/mint with empty hook.snap @@ -1 +1 @@ -320162 \ No newline at end of file +320128 \ No newline at end of file diff --git a/.forge-snapshots/mint with native token.snap b/.forge-snapshots/mint with native token.snap index a9867ad89..8053a9004 100644 --- a/.forge-snapshots/mint with native token.snap +++ b/.forge-snapshots/mint with native token.snap @@ -1 +1 @@ -294175 \ No newline at end of file +294141 \ No newline at end of file diff --git a/.forge-snapshots/mint.snap b/.forge-snapshots/mint.snap index f3c142e8e..feeb5f0f9 100644 --- a/.forge-snapshots/mint.snap +++ b/.forge-snapshots/mint.snap @@ -1 +1 @@ -312839 \ No newline at end of file +312805 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index 3c5cf1670..f3e7f65a9 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,32 +1,32 @@ -FeesTest:testCollectFees() (gas: 677387) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 594491, ~: 603200) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170205, ~: 180657) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87339, ~: 92991) +FeesTest:testCollectFees() (gas: 677360) +FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595613, ~: 603144) +FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170445, ~: 180657) +FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87794, ~: 92991) FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125260, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63564, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 62673, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 615005) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 943699, ~: 943699) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561250, ~: 563178) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631326) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 777583) -HooksTest:testAfterDonateInvalidReturn() (gas: 442372) +FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125827, ~: 131284) +FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63121, ~: 65959) +FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63117, ~: 65866) +FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 614978) +FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 943616, ~: 943616) +FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561754, ~: 563122) +FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631299) +FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 777527) +HooksTest:testAfterDonateInvalidReturn() (gas: 442338) HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) -HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323731) +HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323697) HooksTest:testAfterSwapInvalidReturn() (gas: 159386) -HooksTest:testBeforeDonateInvalidReturn() (gas: 442392) +HooksTest:testBeforeDonateInvalidReturn() (gas: 442358) HooksTest:testBeforeInitializeInvalidReturn() (gas: 844205) HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) -HooksTest:testDonateSucceedsWithHook() (gas: 531435) +HooksTest:testDonateSucceedsWithHook() (gas: 531408) HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6690352) +HooksTest:testInitializeSucceedsWithHook() (gas: 6685733) HooksTest:testInvalidIfNoFlags() (gas: 1108) HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) HooksTest:testIsValidIfDynamicFee() (gas: 935) -HooksTest:testModifyPositionSucceedsWithHook() (gas: 280261) +HooksTest:testModifyPositionSucceedsWithHook() (gas: 280234) HooksTest:testSwapSucceedsWithHook() (gas: 109070) HooksTest:testValidateHookAddressAfterDonate(uint152) (runs: 256, μ: 1854, ~: 1854) HooksTest:testValidateHookAddressAfterInitialize(uint152) (runs: 256, μ: 1855, ~: 1855) @@ -41,61 +41,61 @@ HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4768, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4813, ~: 4758) +HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4772, ~: 4715) +HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4810, ~: 4758) HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105457, ~: 105624) +PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105461, ~: 105624) PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) -PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1239964) -PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501509) -PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559443) -PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118431) +PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1239930) +PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501482) +PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559416) +PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118397) PoolManagerTest:testExtsloadForPoolPrice() (gas: 76983) -PoolManagerTest:testExtsloadMultipleSlots() (gas: 7234506) -PoolManagerTest:testGasDonateOneToken() (gas: 512978) -PoolManagerTest:testGasMint() (gas: 320573) -PoolManagerTest:testGasMintWithHooks() (gas: 939380) -PoolManagerTest:testGasMintWithNative() (gas: 305580) +PoolManagerTest:testExtsloadMultipleSlots() (gas: 7234472) +PoolManagerTest:testGasDonateOneToken() (gas: 512951) +PoolManagerTest:testGasMint() (gas: 320546) +PoolManagerTest:testGasMintWithHooks() (gas: 939346) +PoolManagerTest:testGasMintWithNative() (gas: 305552) PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) PoolManagerTest:testGasSwap() (gas: 197464) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 669233) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710568) +PoolManagerTest:testGasSwapAgainstLiq() (gas: 669206) +PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710540) PoolManagerTest:testGasSwapWithHooks() (gas: 778135) PoolManagerTest:testGasSwapWithNative() (gas: 197516) PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) -PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009431) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260980, ~: 275317) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264542, ~: 265987) -PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946155) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807567, ~: 809354) +PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009397) +PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260271, ~: 245910) +PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264514, ~: 265972) +PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946121) +PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807556, ~: 809337) PoolManagerTest:testNoOpLockIsOk() (gas: 87262) PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112574, ~: 112733) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18544, ~: 13419) +PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112560, ~: 112734) +PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18529, ~: 13419) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58927, ~: 59255) +PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58875, ~: 59255) PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680463, ~: 680630) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609312, ~: 609474) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50229, ~: 50392) +PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680464, ~: 680631) +PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609320, ~: 609474) +PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50234, ~: 50393) PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) -PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085179) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536216) +PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085145) +PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536189) PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 107786) -PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982296) +PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982262) PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650378) PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106078) -PoolManagerTest:testSwapUse1155AsInput() (gas: 713977) +PoolManagerTest:testSwapUse1155AsInput() (gas: 713950) PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 11098, ~: 5734) -PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 755595, ~: 7280) +PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 10739, ~: 5734) +PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 755614, ~: 7280) SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) SafeCastTest:testToInt256(uint256) (runs: 256, μ: 993, ~: 450) @@ -125,16 +125,16 @@ TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) TestDelegateCall:testGasOverhead() (gas: 13709) TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) TestDynamicFees:testSwapWorks() (gas: 104442) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13570, ~: 13283) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13280, ~: 12952) +TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13580, ~: 13303) +TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13280, ~: 13052) TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13149, ~: 13071) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 12945, ~: 12894) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18898, ~: 19014) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21776, ~: 21109) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13931, ~: 13162) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14512, ~: 14140) -TestSqrtPriceMath:testFuzzMulDiv96(uint256,uint256) (runs: 256, μ: 3733, ~: 3677) -TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 354280) -TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 317026) +TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 12932, ~: 12876) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18877, ~: 19014) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21848, ~: 21332) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13924, ~: 13162) +TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14508, ~: 14140) +TestSqrtPriceMath:testFuzzMulDiv96(uint256,uint256) (runs: 256, μ: 3727, ~: 3677) +TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 352321) +TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 315067) TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329295) TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307328) \ No newline at end of file diff --git a/contracts/libraries/SqrtPriceMath.sol b/contracts/libraries/SqrtPriceMath.sol index 83085b071..9627fa8aa 100644 --- a/contracts/libraries/SqrtPriceMath.sol +++ b/contracts/libraries/SqrtPriceMath.sol @@ -233,10 +233,31 @@ library SqrtPriceMath { pure returns (int256 amount0) { - unchecked { - return liquidity < 0 - ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + /** + * Equivalent to: + * amount0 = liquidity < 0 + * ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + * : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + */ + bool sign; + uint256 mask; + uint128 liquidityAbs; + assembly { + // In case the upper bits are not clean. + liquidity := signextend(15, liquidity) + // sign = 1 if liquidity >= 0 else 0 + sign := iszero(slt(liquidity, 0)) + // mask = 0 if liquidity >= 0 else -1 + mask := sub(sign, 1) + liquidityAbs := xor(mask, add(mask, liquidity)) + } + // amount0Abs = liquidity / sqrt(lower) - liquidity / sqrt(upper) < type(uint224).max + // always fits in 224 bits, no need for toInt256() + uint256 amount0Abs = getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, sign); + assembly { + // If liquidity >= 0, amount0 = |amount0| = 0 ^ |amount0| + // If liquidity < 0, amount0 = -|amount0| = ~|amount0| + 1 = (-1) ^ |amount0| - (-1) + amount0 := sub(xor(amount0Abs, mask), mask) } } @@ -250,10 +271,31 @@ library SqrtPriceMath { pure returns (int256 amount1) { - unchecked { - return liquidity < 0 - ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + /** + * Equivalent to: + * amount1 = liquidity < 0 + * ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + * : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + */ + bool sign; + uint256 mask; + uint128 liquidityAbs; + assembly { + // In case the upper bits are not clean. + liquidity := signextend(15, liquidity) + // sign = 1 if liquidity >= 0 else 0 + sign := iszero(slt(liquidity, 0)) + // mask = 0 if liquidity >= 0 else -1 + mask := sub(sign, 1) + liquidityAbs := xor(mask, add(mask, liquidity)) + } + // amount1Abs = liquidity * (sqrt(upper) - sqrt(lower)) < type(uint192).max + // always fits in 192 bits, no need for toInt256() + uint256 amount1Abs = getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, sign); + assembly { + // If liquidity >= 0, amount1 = |amount1| = 0 ^ |amount1| + // If liquidity < 0, amount1 = -|amount1| = ~|amount1| + 1 = (-1) ^ |amount1| - (-1) + amount1 := sub(xor(amount1Abs, mask), mask) } } } diff --git a/test/__snapshots__/PoolManager.gas.spec.ts.snap b/test/__snapshots__/PoolManager.gas.spec.ts.snap index 055acac04..10dc8ea02 100644 --- a/test/__snapshots__/PoolManager.gas.spec.ts.snap +++ b/test/__snapshots__/PoolManager.gas.spec.ts.snap @@ -10,251 +10,251 @@ Object { exports[`PoolManager gas tests ERC20 tokens #mint above current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 158192, + "gasUsed": 158094, } `; exports[`PoolManager gas tests ERC20 tokens #mint above current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 217934, + "gasUsed": 217836, } `; exports[`PoolManager gas tests ERC20 tokens #mint above current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 158192, + "gasUsed": 158094, } `; exports[`PoolManager gas tests ERC20 tokens #mint around current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 209909, + "gasUsed": 209643, } `; exports[`PoolManager gas tests ERC20 tokens #mint around current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 319525, + "gasUsed": 319259, } `; exports[`PoolManager gas tests ERC20 tokens #mint around current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 209909, + "gasUsed": 209643, } `; exports[`PoolManager gas tests ERC20 tokens #mint below current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 158912, + "gasUsed": 158744, } `; exports[`PoolManager gas tests ERC20 tokens #mint below current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 286562, + "gasUsed": 286393, } `; exports[`PoolManager gas tests ERC20 tokens #mint below current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 158912, + "gasUsed": 158744, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 192451, + "gasUsed": 192092, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 first swap in block with no tick movement 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 186534, + "gasUsed": 186261, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 211422, + "gasUsed": 210904, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 253547, + "gasUsed": 252623, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 204482, + "gasUsed": 203828, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 192451, + "gasUsed": 192092, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 second swap in block with no tick movement 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 186623, + "gasUsed": 186350, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 205622, + "gasUsed": 205214, } `; exports[`PoolManager gas tests ERC20 tokens #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 247722, + "gasUsed": 246908, } `; exports[`PoolManager gas tests Native Tokens #mint above current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 151125, + "gasUsed": 151028, } `; exports[`PoolManager gas tests Native Tokens #mint above current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 210868, + "gasUsed": 210770, } `; exports[`PoolManager gas tests Native Tokens #mint above current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 151125, + "gasUsed": 151028, } `; exports[`PoolManager gas tests Native Tokens #mint around current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 202840, + "gasUsed": 202574, } `; exports[`PoolManager gas tests Native Tokens #mint around current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 312456, + "gasUsed": 312190, } `; exports[`PoolManager gas tests Native Tokens #mint around current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 202840, + "gasUsed": 202574, } `; exports[`PoolManager gas tests Native Tokens #mint below current price add to position existing 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 164264, + "gasUsed": 164096, } `; exports[`PoolManager gas tests Native Tokens #mint below current price new position mint first in range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 291914, + "gasUsed": 291745, } `; exports[`PoolManager gas tests Native Tokens #mint below current price second position in same range 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 164264, + "gasUsed": 164096, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 179806, + "gasUsed": 179448, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 first swap in block with no tick movement 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 173889, + "gasUsed": 173616, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 198777, + "gasUsed": 198259, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 240902, + "gasUsed": 239978, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 191837, + "gasUsed": 191184, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 179806, + "gasUsed": 179448, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 second swap in block with no tick movement 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 173978, + "gasUsed": 173705, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 192977, + "gasUsed": 192569, } `; exports[`PoolManager gas tests Native Tokens #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 235077, + "gasUsed": 234264, } `; diff --git a/test/__snapshots__/PoolManager.spec.ts.snap b/test/__snapshots__/PoolManager.spec.ts.snap index 374cc88e0..413a7bd20 100644 --- a/test/__snapshots__/PoolManager.spec.ts.snap +++ b/test/__snapshots__/PoolManager.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PoolManager bytecode size 1`] = `28949`; +exports[`PoolManager bytecode size 1`] = `28757`; diff --git a/test/__snapshots__/SwapMath.spec.ts.snap b/test/__snapshots__/SwapMath.spec.ts.snap index 55a87d526..73b88acf0 100644 --- a/test/__snapshots__/SwapMath.spec.ts.snap +++ b/test/__snapshots__/SwapMath.spec.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SwapMath #computeSwapStep gas swap one for zero exact in capped 1`] = `2221`; +exports[`SwapMath #computeSwapStep gas swap one for zero exact in capped 1`] = `1961`; -exports[`SwapMath #computeSwapStep gas swap one for zero exact in partial 1`] = `3049`; +exports[`SwapMath #computeSwapStep gas swap one for zero exact in partial 1`] = `2439`; -exports[`SwapMath #computeSwapStep gas swap one for zero exact out capped 1`] = `1968`; +exports[`SwapMath #computeSwapStep gas swap one for zero exact out capped 1`] = `1708`; -exports[`SwapMath #computeSwapStep gas swap one for zero exact out partial 1`] = `3049`; +exports[`SwapMath #computeSwapStep gas swap one for zero exact out partial 1`] = `2439`; -exports[`SwapMath #computeSwapStep gas swap zero for one exact in capped 1`] = `2210`; +exports[`SwapMath #computeSwapStep gas swap zero for one exact in capped 1`] = `1960`; -exports[`SwapMath #computeSwapStep gas swap zero for one exact in partial 1`] = `3208`; +exports[`SwapMath #computeSwapStep gas swap zero for one exact in partial 1`] = `2763`; -exports[`SwapMath #computeSwapStep gas swap zero for one exact out capped 1`] = `1957`; +exports[`SwapMath #computeSwapStep gas swap zero for one exact out capped 1`] = `1707`; -exports[`SwapMath #computeSwapStep gas swap zero for one exact out partial 1`] = `3208`; +exports[`SwapMath #computeSwapStep gas swap zero for one exact out partial 1`] = `2763`; From f2c7ea30fbf38021572aca5de9edb3f21801883a Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 4 Apr 2024 03:33:25 -0400 Subject: [PATCH 08/25] Fix `getNextSqrtPriceFromAmount0RoundingUp` --- .../SwapMath_oneForZero_exactInCapped.snap | 2 +- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutCapped.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInCapped.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ..._gasCostForAmount0WhereRoundUpIsFalse.snap | 2 +- ...a_gasCostForAmount0WhereRoundUpIsTrue.snap | 2 +- ..._gasCostForAmount1WhereRoundUpIsFalse.snap | 2 +- ...a_gasCostForAmount1WhereRoundUpIsTrue.snap | 2 +- ...iceFromInput_zeroForOneEqualsFalseGas.snap | 2 +- ...riceFromInput_zeroForOneEqualsTrueGas.snap | 2 +- ...ceFromOutput_zeroForOneEqualsFalseGas.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- .gas-snapshot | 140 ------------------ src/libraries/SqrtPriceMath.sol | 12 +- 35 files changed, 38 insertions(+), 180 deletions(-) delete mode 100644 .gas-snapshot diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index 27d48e9a4..6c66109a3 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -1947 \ No newline at end of file +1714 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 9560ba822..4f4963bcf 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -3238 \ No newline at end of file +2990 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index 758e03adc..9c5c74bae 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -2208 \ No newline at end of file +1975 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 9560ba822..4f4963bcf 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -3238 \ No newline at end of file +2990 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index 12057ad70..d7bda98e9 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -1937 \ No newline at end of file +1713 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index c173e591f..0203089ec 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2826 \ No newline at end of file +2488 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index c802063a0..062bc1c2d 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -2198 \ No newline at end of file +1974 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index c173e591f..0203089ec 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2826 \ No newline at end of file +2488 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index a57d22ba0..7ad7da911 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -265311 \ No newline at end of file +265003 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 1be361e02..255119f3e 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -140154 \ No newline at end of file +139846 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index 993394194..a465ecdfb 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -145471 \ No newline at end of file +145163 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap index 1dd3380cd..37e6f1741 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap @@ -1 +1 @@ -516 \ No newline at end of file +515 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap index b7f636c12..1dd3380cd 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap @@ -1 +1 @@ -665 \ No newline at end of file +516 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index 1fc188de7..50b04dff0 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -525 \ No newline at end of file +402 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index 929530e88..cdf1f34dc 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -653 \ No newline at end of file +403 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap index 7dfce3516..ea5ca3642 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -594 \ No newline at end of file +547 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap index 19e03cffa..e9769c199 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -776 \ No newline at end of file +759 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap index f8f450742..e2a83a8ce 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -878 \ No newline at end of file +855 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 39c68e33e..e75bd1282 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22974 \ No newline at end of file +22872 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index c94433f36..00846c721 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -56002 \ No newline at end of file +55993 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 36e50fa2b..e863f53df 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -148188 \ No newline at end of file +148179 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 245df4831..313ad3426 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -149652 \ No newline at end of file +149643 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 797d10395..a55b4a92e 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -132905 \ No newline at end of file +132605 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 483955831..e7cf00f83 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -146781 \ No newline at end of file +146481 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 6671e8870..e28062eab 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -72364 \ No newline at end of file +72117 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index c31794dbd..402c82c90 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -60367 \ No newline at end of file +60120 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 4cc58cdca..09918daf5 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -80397 \ No newline at end of file +80313 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index e67a9a599..2b87a0138 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -76394 \ No newline at end of file +76195 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index f504a2867..1f2738faf 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -138745 \ No newline at end of file +138393 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index d2db9e035..5673bef94 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -155596 \ No newline at end of file +155296 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index a901423df..ba714249b 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -89671 \ No newline at end of file +89371 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 29498b964..5ded03f06 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -60345 \ No newline at end of file +60098 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index d6bca4c74..9ad919c60 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -140297 \ No newline at end of file +139997 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot deleted file mode 100644 index f3e7f65a9..000000000 --- a/.gas-snapshot +++ /dev/null @@ -1,140 +0,0 @@ -FeesTest:testCollectFees() (gas: 677360) -FeesTest:testHookWithdrawFeeProtocolWithdrawFee(uint8,uint8) (runs: 256, μ: 595613, ~: 603144) -FeesTest:testInitializeAllFees(uint8,uint8,uint8,uint8) (runs: 256, μ: 170445, ~: 180657) -FeesTest:testInitializeBothHookFee(uint8,uint8) (runs: 256, μ: 87794, ~: 92991) -FeesTest:testInitializeFailsNoHook() (gas: 18262) -FeesTest:testInitializeHookProtocolSwapFee(uint8,uint8) (runs: 256, μ: 125827, ~: 131284) -FeesTest:testInitializeHookSwapFee(uint8) (runs: 256, μ: 63121, ~: 65959) -FeesTest:testInitializeHookWithdrawFee(uint8) (runs: 256, μ: 63117, ~: 65866) -FeesTest:testInitializeWithSwapProtocolFeeAndHookFeeDifferentDirections() (gas: 614978) -FeesTest:testNoHookProtocolFee(uint8,uint8) (runs: 256, μ: 943616, ~: 943616) -FeesTest:testProtocolFeeOnWithdrawalRemainsZeroIfNoHookWithdrawalFeeSet(uint8,uint8) (runs: 256, μ: 561754, ~: 563122) -FeesTest:testProtocolSwapFeeAndHookSwapFeeSameDirection() (gas: 631299) -FeesTest:testSwapWithProtocolFeeAllAndHookFeeAllButOnlySwapFlag() (gas: 777527) -HooksTest:testAfterDonateInvalidReturn() (gas: 442338) -HooksTest:testAfterInitializeInvalidReturn() (gas: 876328) -HooksTest:testAfterModifyPositionInvalidReturn() (gas: 323697) -HooksTest:testAfterSwapInvalidReturn() (gas: 159386) -HooksTest:testBeforeDonateInvalidReturn() (gas: 442358) -HooksTest:testBeforeInitializeInvalidReturn() (gas: 844205) -HooksTest:testBeforeModifyPositionInvalidReturn() (gas: 132872) -HooksTest:testBeforeSwapInvalidReturn() (gas: 133856) -HooksTest:testDonateSucceedsWithHook() (gas: 531408) -HooksTest:testGas() (gas: 38344) -HooksTest:testInitializeSucceedsWithHook() (gas: 6685733) -HooksTest:testInvalidIfNoFlags() (gas: 1108) -HooksTest:testIsValidHookAddressAnyFlags() (gas: 2099) -HooksTest:testIsValidHookAddressZeroAddress() (gas: 443) -HooksTest:testIsValidIfDynamicFee() (gas: 935) -HooksTest:testModifyPositionSucceedsWithHook() (gas: 280234) -HooksTest:testSwapSucceedsWithHook() (gas: 109070) -HooksTest:testValidateHookAddressAfterDonate(uint152) (runs: 256, μ: 1854, ~: 1854) -HooksTest:testValidateHookAddressAfterInitialize(uint152) (runs: 256, μ: 1855, ~: 1855) -HooksTest:testValidateHookAddressAfterModify(uint152) (runs: 256, μ: 1835, ~: 1835) -HooksTest:testValidateHookAddressAfterSwap(uint152) (runs: 256, μ: 1855, ~: 1855) -HooksTest:testValidateHookAddressAllHooks(uint152) (runs: 256, μ: 1667, ~: 1667) -HooksTest:testValidateHookAddressBeforeAndAfterDonate(uint152) (runs: 256, μ: 1804, ~: 1804) -HooksTest:testValidateHookAddressBeforeAndAfterInitialize(uint152) (runs: 256, μ: 1828, ~: 1828) -HooksTest:testValidateHookAddressBeforeAndAfterModify(uint152) (runs: 256, μ: 1793, ~: 1793) -HooksTest:testValidateHookAddressBeforeAndAfterSwap(uint152) (runs: 256, μ: 1785, ~: 1785) -HooksTest:testValidateHookAddressBeforeDonate(uint152) (runs: 256, μ: 1812, ~: 1812) -HooksTest:testValidateHookAddressBeforeInitialize(uint152) (runs: 256, μ: 1837, ~: 1837) -HooksTest:testValidateHookAddressBeforeModify(uint152) (runs: 256, μ: 1863, ~: 1863) -HooksTest:testValidateHookAddressBeforeSwap(uint152) (runs: 256, μ: 1856, ~: 1856) -HooksTest:testValidateHookAddressFailsAllHooks(uint152,uint8) (runs: 256, μ: 4772, ~: 4715) -HooksTest:testValidateHookAddressFailsNoHooks(uint152,uint8) (runs: 256, μ: 4810, ~: 4758) -HooksTest:testValidateHookAddressNoHooks(uint152) (runs: 256, μ: 1845, ~: 1845) -OwnedTest:testConstructor(address) (runs: 256, μ: 159689, ~: 159767) -OwnedTest:testSetOwnerFromNonOwner(address,address) (runs: 256, μ: 160761, ~: 160925) -OwnedTest:testSetOwnerFromOwner(address,address) (runs: 256, μ: 162996, ~: 162996) -PoolManagerTest:testDonateFailsIfNoLiquidity(uint160) (runs: 256, μ: 105461, ~: 105624) -PoolManagerTest:testDonateFailsIfNotInitialized() (gas: 69411) -PoolManagerTest:testDonateFailsWithIncorrectSelectors() (gas: 1239930) -PoolManagerTest:testDonateSucceedsForNativeTokensWhenPoolHasLiquidity() (gas: 501482) -PoolManagerTest:testDonateSucceedsWhenPoolHasLiquidity() (gas: 559416) -PoolManagerTest:testDonateSucceedsWithCorrectSelectors() (gas: 1118397) -PoolManagerTest:testExtsloadForPoolPrice() (gas: 76983) -PoolManagerTest:testExtsloadMultipleSlots() (gas: 7234472) -PoolManagerTest:testGasDonateOneToken() (gas: 512951) -PoolManagerTest:testGasMint() (gas: 320546) -PoolManagerTest:testGasMintWithHooks() (gas: 939346) -PoolManagerTest:testGasMintWithNative() (gas: 305552) -PoolManagerTest:testGasPoolManagerInitialize() (gas: 73311) -PoolManagerTest:testGasSwap() (gas: 197464) -PoolManagerTest:testGasSwapAgainstLiq() (gas: 669206) -PoolManagerTest:testGasSwapAgainstLiqWithNative() (gas: 710540) -PoolManagerTest:testGasSwapWithHooks() (gas: 778135) -PoolManagerTest:testGasSwapWithNative() (gas: 197516) -PoolManagerTest:testLockEmitsCorrectId() (gas: 48033) -PoolManagerTest:testMintFailsIfNotInitialized() (gas: 70806) -PoolManagerTest:testMintFailsWithIncorrectSelectors() (gas: 1009397) -PoolManagerTest:testMintSucceedsForNativeTokensIfInitialized(uint160) (runs: 256, μ: 260271, ~: 245910) -PoolManagerTest:testMintSucceedsIfInitialized(uint160) (runs: 256, μ: 264514, ~: 265972) -PoolManagerTest:testMintSucceedsWithCorrectSelectors() (gas: 946121) -PoolManagerTest:testMintSucceedsWithHooksIfInitialized(uint160) (runs: 256, μ: 807556, ~: 809337) -PoolManagerTest:testNoOpLockIsOk() (gas: 87262) -PoolManagerTest:testPoolManagerFeeControllerSet() (gas: 35374) -PoolManagerTest:testPoolManagerFetchFeeWhenController(uint160) (runs: 256, μ: 112560, ~: 112734) -PoolManagerTest:testPoolManagerInitialize((address,address,uint24,int24,address),uint160) (runs: 256, μ: 18529, ~: 13419) -PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceNeg(uint160) (runs: 256, μ: 15348, ~: 15348) -PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceTooLarge(uint160) (runs: 256, μ: 16296, ~: 16296) -PoolManagerTest:testPoolManagerInitializeFailsIfTickSpaceZero(uint160) (runs: 256, μ: 15349, ~: 15349) -PoolManagerTest:testPoolManagerInitializeFailsWithIncorrectSelectors() (gas: 714337) -PoolManagerTest:testPoolManagerInitializeForNativeTokens(uint160) (runs: 256, μ: 58875, ~: 59255) -PoolManagerTest:testPoolManagerInitializeSucceedsWithCorrectSelectors() (gas: 714353) -PoolManagerTest:testPoolManagerInitializeSucceedsWithEmptyHooks(uint160) (runs: 256, μ: 680464, ~: 680631) -PoolManagerTest:testPoolManagerInitializeSucceedsWithHooks(uint160) (runs: 256, μ: 609320, ~: 609474) -PoolManagerTest:testPoolManagerInitializeSucceedsWithMaxTickSpacing(uint160) (runs: 256, μ: 50234, ~: 50393) -PoolManagerTest:testSwapFailsIfNotInitialized(uint160) (runs: 256, μ: 73083, ~: 73083) -PoolManagerTest:testSwapFailsWithIncorrectSelectors() (gas: 1085145) -PoolManagerTest:testSwapMintERC1155IfOutputNotTaken() (gas: 536189) -PoolManagerTest:testSwapSucceedsIfInitialized() (gas: 107786) -PoolManagerTest:testSwapSucceedsWithCorrectSelectors() (gas: 982262) -PoolManagerTest:testSwapSucceedsWithHooksIfInitialized() (gas: 650378) -PoolManagerTest:testSwapSucceedsWithNativeTokensIfInitialized() (gas: 106078) -PoolManagerTest:testSwapUse1155AsInput() (gas: 713950) -PoolTest:testModifyPosition(uint160,(address,int24,int24,int128,int24)) (runs: 256, μ: 20427, ~: 6970) -PoolTest:testPoolInitialize(uint160,uint8,uint8) (runs: 256, μ: 10739, ~: 5734) -PoolTest:testSwap(uint160,(uint24,int24,bool,int256,uint160)) (runs: 256, μ: 755614, ~: 7280) -SafeCastTest:testToInt128(int256) (runs: 256, μ: 1352, ~: 511) -SafeCastTest:testToInt128(uint256) (runs: 256, μ: 1355, ~: 390) -SafeCastTest:testToInt256(uint256) (runs: 256, μ: 993, ~: 450) -SafeCastTest:testToUint160(uint256) (runs: 256, μ: 1231, ~: 454) -TestBalanceDelta:testAdd(int128,int128,int128,int128) (runs: 256, μ: 4750, ~: 4750) -TestBalanceDelta:testSub(int128,int128,int128,int128) (runs: 256, μ: 4724, ~: 4724) -TestBalanceDelta:testToBalanceDelta() (gas: 968) -TestBalanceDelta:testToBalanceDelta(int128,int128) (runs: 256, μ: 613, ~: 613) -TestBitMath:testLeastSignificantBit(uint256) (runs: 256, μ: 4182, ~: 3748) -TestBitMath:testLeastSignificantBitMaxUint256() (gas: 697) -TestBitMath:testLeastSignificantBitOne() (gas: 715) -TestBitMath:testLeastSignificantBitPowersOfTwo() (gas: 151449) -TestBitMath:testLeastSignificantBitTwo() (gas: 673) -TestBitMath:testLeastSignificantBitZero() (gas: 3086) -TestBitMath:testLsbGas() (gas: 115496) -TestBitMath:testMostSignificantBit(uint256) (runs: 256, μ: 14893, ~: 7241) -TestBitMath:testMostSignificantBitMaxUint256() (gas: 650) -TestBitMath:testMostSignificantBitOne() (gas: 538) -TestBitMath:testMostSignificantBitPowersOfTwo() (gas: 125789) -TestBitMath:testMostSignificantBitTwo() (gas: 523) -TestBitMath:testMostSignificantBitZero() (gas: 3087) -TestBitMath:testMsbGas() (gas: 115335) -TestDelegateCall:testCanCallIntoPrivateMethodWithModifier() (gas: 5426) -TestDelegateCall:testCannotDelegateCallPrivateMethodWithModifier() (gas: 8641) -TestDelegateCall:testDelegateCallNoModifier() (gas: 5734) -TestDelegateCall:testDelegateCallWithModifier() (gas: 8575) -TestDelegateCall:testGasOverhead() (gas: 13709) -TestDynamicFees:testSwapFailsWithTooLargeFee() (gas: 99044) -TestDynamicFees:testSwapWorks() (gas: 104442) -TestSqrtPriceMath:testFuzzGetAmount0Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13580, ~: 13303) -TestSqrtPriceMath:testFuzzGetAmount0DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 13280, ~: 13052) -TestSqrtPriceMath:testFuzzGetAmount1Delta(uint160,uint160,uint128,bool) (runs: 256, μ: 13149, ~: 13071) -TestSqrtPriceMath:testFuzzGetAmount1DeltaSigned(uint160,uint160,int128) (runs: 256, μ: 12932, ~: 12876) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount0RoundingUp(uint160,uint128,uint256,bool) (runs: 256, μ: 18877, ~: 19014) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromAmount1RoundingDown(uint160,uint128,uint256,bool) (runs: 256, μ: 21848, ~: 21332) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromInput(uint160,uint128,uint256,bool) (runs: 256, μ: 13924, ~: 13162) -TestSqrtPriceMath:testFuzzGetNextSqrtPriceFromOutput(uint160,uint128,uint256,bool) (runs: 256, μ: 14508, ~: 14140) -TestSqrtPriceMath:testFuzzMulDiv96(uint256,uint256) (runs: 256, μ: 3727, ~: 3677) -TestSqrtPriceMath:testGasGetAmount0DeltaSigned() (gas: 352321) -TestSqrtPriceMath:testGasGetAmount1DeltaSigned() (gas: 315067) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromInput() (gas: 329295) -TestSqrtPriceMath:testGasGetNextSqrtPriceFromOutput() (gas: 307328) \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 4527da438..793ce22db 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -53,16 +53,14 @@ library SqrtPriceMath { // denominator is checked for overflow return uint160(numerator1.divRoundingUp(numerator1.div(sqrtPX96) + amount)); } else { - uint256 denominator; - /// @solidity memory-safe-assembly - assembly { + unchecked { + uint256 product = amount * sqrtPX96; // if the product overflows, we know the denominator underflows // in addition, we must check that the denominator does not underflow - let product := mul(amount, sqrtPX96) - if iszero(and(eq(div(product, amount), sqrtPX96), gt(numerator1, product))) { revert(0, 0) } - denominator := sub(numerator1, product) + if (!(product.div(amount) == sqrtPX96 && numerator1 > product)) revert PriceOverflow(); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); } - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); } } From 97134326fb45583b0c8c19aedb71247cacf768d5 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 4 Apr 2024 03:57:39 -0400 Subject: [PATCH 09/25] Refactor `FullMath.mulDiv96` and add tests --- .../poolManager bytecode size.snap | 2 +- src/libraries/FullMath.sol | 16 +++++----- test/libraries/FullMath.t.sol | 29 +++++++++++++++++-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index e75bd1282..10acd5f3b 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22872 \ No newline at end of file +22862 \ No newline at end of file diff --git a/src/libraries/FullMath.sol b/src/libraries/FullMath.sol index 709d04a47..19f32e598 100644 --- a/src/libraries/FullMath.sol +++ b/src/libraries/FullMath.sol @@ -102,7 +102,10 @@ library FullMath { } /// @notice Calculates x * y / 2^96 with full precision. - function mulDiv96(uint256 x, uint256 y) internal pure returns (uint256 result) { + /// @param a The multiplicand + /// @param b The multiplier + /// @return result The 256-bit result + function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // 512-bit multiply `[prod1 prod0] = x * y`. @@ -112,18 +115,13 @@ library FullMath { // variables such that `product = prod1 * 2**256 + prod0`. // Least significant 256 bits of the product. - let prod0 := mul(x, y) - let mm := mulmod(x, y, not(0)) + let prod0 := mul(a, b) + let mm := mulmod(a, b, not(0)) // Most significant 256 bits of the product. let prod1 := sub(mm, add(prod0, lt(mm, prod0))) // Make sure the result is less than `2**256`. - if iszero(gt(0x1000000000000000000000000, prod1)) { - // Store the function selector of `FullMulDivFailed()`. - mstore(0x00, 0xae47f702) - // Revert with (offset, size). - revert(0x1c, 0x04) - } + if shr(96, prod1) { revert(0, 0) } // Divide [prod1 prod0] by 2^96. result := or(shr(96, prod0), shl(160, prod1)) diff --git a/test/libraries/FullMath.t.sol b/test/libraries/FullMath.t.sol index b7b311910..ec0fe6f06 100644 --- a/test/libraries/FullMath.t.sol +++ b/test/libraries/FullMath.t.sol @@ -7,8 +7,9 @@ import {FullMath} from "src/libraries/FullMath.sol"; contract FullMathTest is Test { using FullMath for uint256; - uint256 constant Q128 = 2 ** 128; - uint256 constant MAX_UINT256 = type(uint256).max; + uint256 internal constant Q96 = 2 ** 96; + uint256 internal constant Q128 = 2 ** 128; + uint256 internal constant MAX_UINT256 = type(uint256).max; function test_mulDiv_revertsWith0Denominator(uint256 x, uint256 y) public { vm.expectRevert(); @@ -56,6 +57,30 @@ contract FullMathTest is Test { assertEq(FullMath.mulDiv(x, y, d), x * y / d); } + function test_mulDiv96_revertsIfOutputOverflows() public { + vm.expectRevert(); + FullMath.mulDiv96(1 << 176, 1 << 176); + } + + function test_mulDiv96_validWithPhantomOverflow() public { + assertEq(FullMath.mulDiv96(MAX_UINT256, Q96), MAX_UINT256); + } + + /// @notice Test `mulDiv96` against `mulDiv` with a denominator of `Q96`. + function test_mulDiv96_fuzz(uint256 a, uint256 b) public { + // Most significant 256 bits of the product. + uint256 prod1; + assembly { + // Least significant 256 bits of the product. + let prod0 := mul(a, b) + let mm := mulmod(a, b, not(0)) + prod1 := sub(mm, add(prod0, lt(mm, prod0))) + } + // assume that the `mulDiv` will not overflow + vm.assume(Q96 > prod1); + assertEq(FullMath.mulDiv96(a, b), FullMath.mulDiv(a, b, Q96)); + } + function test_mulDivRoundingUp_revertsWith0Denominator(uint256 x, uint256 y) public { vm.expectRevert(); x.mulDivRoundingUp(y, 0); From cc0623d6bb93b707964f095b46703c1167160636 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 4 Apr 2024 04:10:16 -0400 Subject: [PATCH 10/25] Revert changes to `SqrtPriceMathTest` --- src/test/SqrtPriceMathTest.sol | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/test/SqrtPriceMathTest.sol b/src/test/SqrtPriceMathTest.sol index d2e60aa93..7247b373e 100644 --- a/src/test/SqrtPriceMathTest.sol +++ b/src/test/SqrtPriceMathTest.sol @@ -4,22 +4,6 @@ pragma solidity ^0.8.20; import {SqrtPriceMath} from "../libraries/SqrtPriceMath.sol"; contract SqrtPriceMathTest { - function getNextSqrtPriceFromAmount0RoundingUp(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) - external - pure - returns (uint160) - { - return SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); - } - - function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) - external - pure - returns (uint160) - { - return SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); - } - function getNextSqrtPriceFromInput(uint160 sqrtP, uint128 liquidity, uint256 amountIn, bool zeroForOne) external pure @@ -92,20 +76,4 @@ contract SqrtPriceMathTest { SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); return gasBefore - gasleft(); } - - function getAmount0DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) - external - pure - returns (int256) - { - return SqrtPriceMath.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity); - } - - function getAmount1DeltaSigned(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) - external - pure - returns (int256) - { - return SqrtPriceMath.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity); - } } From 5232cda6fb35888a7e936174e93953e6424ddf3d Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:02:50 -0400 Subject: [PATCH 11/25] Update method name and snapshot values Renamed the method `mulDiv96` to `mulDivQ96` in FullMath.sol and SqrtPriceMath.sol to better reflect its usage. Additionally, snapshot files were updated with new resultant values reflecting the changes in the computations. --- .forge-snapshots/addLiquidity with empty hook.snap | 2 +- .forge-snapshots/addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity with empty hook.snap | 2 +- .forge-snapshots/removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap burn 6909 for input.snap | 2 +- .forge-snapshots/swap burn native 6909 for input.snap | 2 +- .forge-snapshots/swap mint native output as 6909.snap | 2 +- .forge-snapshots/swap mint output as 6909.snap | 2 +- .../swap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .forge-snapshots/update dynamic fee in before swap.snap | 2 +- src/libraries/FullMath.sol | 2 +- src/libraries/SqrtPriceMath.sol | 2 +- test/libraries/FullMath.t.sol | 6 +++--- 22 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 019c77f29..2217eb647 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -265381 \ No newline at end of file +265073 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index d072cabb1..e4214e73f 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -140224 \ No newline at end of file +139916 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index 267b1c2f5..b4d2c97e5 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -145541 \ No newline at end of file +145233 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 2654fd3e0..bd62c03ff 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23083 \ No newline at end of file +22971 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index a7b4c4134..7c285c5a5 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -56072 \ No newline at end of file +56063 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 5710db49c..ebe77bca3 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -148258 \ No newline at end of file +148249 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 1bcdd5bfe..39ceb4a66 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -149722 \ No newline at end of file +149713 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 92d965caa..d54e1972a 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -132902 \ No newline at end of file +132602 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index e03d94df2..9c62001be 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -146778 \ No newline at end of file +146478 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 3039612cb..a1aeaa858 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -72361 \ No newline at end of file +72114 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 13788d81c..08c86fff8 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -60364 \ No newline at end of file +60117 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 07f2cf54d..b3c8aa5e0 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -80394 \ No newline at end of file +80310 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 505243eb6..f6f64ffa9 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -76391 \ No newline at end of file +76192 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index cad93df0b..b61490571 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -138742 \ No newline at end of file +138390 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 134896538..6db8d15a8 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -155593 \ No newline at end of file +155293 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 41ac906bf..5052be065 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -156254 \ No newline at end of file +155707 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 1456957a2..345d23197 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -89668 \ No newline at end of file +89368 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 2f3e10ebe..ab6fe9d44 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -60342 \ No newline at end of file +60095 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index f44d09345..08c01bdac 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -140294 \ No newline at end of file +139994 \ No newline at end of file diff --git a/src/libraries/FullMath.sol b/src/libraries/FullMath.sol index 19f32e598..ce6bbc04f 100644 --- a/src/libraries/FullMath.sol +++ b/src/libraries/FullMath.sol @@ -105,7 +105,7 @@ library FullMath { /// @param a The multiplicand /// @param b The multiplier /// @return result The 256-bit result - function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256 result) { + function mulDivQ96(uint256 a, uint256 b) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // 512-bit multiply `[prod1 prod0] = x * y`. diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 793ce22db..9814fb33e 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -215,7 +215,7 @@ library SqrtPriceMath { * : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. */ - amount1 = FullMath.mulDiv96(liquidity, numerator); + amount1 = FullMath.mulDivQ96(liquidity, numerator); assembly { amount1 := add(amount1, and(gt(mulmod(liquidity, numerator, denominator), 0), roundUp)) } diff --git a/test/libraries/FullMath.t.sol b/test/libraries/FullMath.t.sol index ec0fe6f06..7d838affd 100644 --- a/test/libraries/FullMath.t.sol +++ b/test/libraries/FullMath.t.sol @@ -59,11 +59,11 @@ contract FullMathTest is Test { function test_mulDiv96_revertsIfOutputOverflows() public { vm.expectRevert(); - FullMath.mulDiv96(1 << 176, 1 << 176); + FullMath.mulDivQ96(1 << 176, 1 << 176); } function test_mulDiv96_validWithPhantomOverflow() public { - assertEq(FullMath.mulDiv96(MAX_UINT256, Q96), MAX_UINT256); + assertEq(FullMath.mulDivQ96(MAX_UINT256, Q96), MAX_UINT256); } /// @notice Test `mulDiv96` against `mulDiv` with a denominator of `Q96`. @@ -78,7 +78,7 @@ contract FullMathTest is Test { } // assume that the `mulDiv` will not overflow vm.assume(Q96 > prod1); - assertEq(FullMath.mulDiv96(a, b), FullMath.mulDiv(a, b, Q96)); + assertEq(FullMath.mulDivQ96(a, b), FullMath.mulDiv(a, b, Q96)); } function test_mulDivRoundingUp_revertsWith0Denominator(uint256 x, uint256 y) public { From d829c2b54c28f5c62af4808eeb2d4237cc31c25b Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:20:44 -0400 Subject: [PATCH 12/25] Refactor function & variable names for clarity Several function and variable names have been updated in the SqrtPriceMath.sol and FullMath.t.sol files for improved clarity. In the SqrtPriceMath file, the getNextSqrtPriceFromAmount1RoundingDown return name has been removed. In the FullMath.t.sol file, functions with 'mulDiv96' have been renamed to 'mulDivQ96'. --- src/libraries/SqrtPriceMath.sol | 4 ++-- test/libraries/FullMath.t.sol | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 9814fb33e..3ec40fb5e 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -73,11 +73,11 @@ library SqrtPriceMath { /// @param liquidity The amount of usable liquidity /// @param amount How much of currency1 to add, or remove, from virtual reserves /// @param add Whether to add, or remove, the amount of currency1 - /// @return nextSqrtPrice The price after adding or removing `amount` + /// @return The price after adding or removing `amount` function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) internal pure - returns (uint160 nextSqrtPrice) + returns (uint160) { // if we're adding (subtracting), rounding down requires rounding the quotient down (up) // in both cases, avoid a mulDiv for most inputs diff --git a/test/libraries/FullMath.t.sol b/test/libraries/FullMath.t.sol index 7d838affd..c139cc7ef 100644 --- a/test/libraries/FullMath.t.sol +++ b/test/libraries/FullMath.t.sol @@ -57,17 +57,17 @@ contract FullMathTest is Test { assertEq(FullMath.mulDiv(x, y, d), x * y / d); } - function test_mulDiv96_revertsIfOutputOverflows() public { + function test_mulDivQ96_revertsIfOutputOverflows() public { vm.expectRevert(); FullMath.mulDivQ96(1 << 176, 1 << 176); } - function test_mulDiv96_validWithPhantomOverflow() public { + function test_mulDivQ96_validWithPhantomOverflow() public { assertEq(FullMath.mulDivQ96(MAX_UINT256, Q96), MAX_UINT256); } - /// @notice Test `mulDiv96` against `mulDiv` with a denominator of `Q96`. - function test_mulDiv96_fuzz(uint256 a, uint256 b) public { + /// @notice Test `mulDivQ96` against `mulDiv` with a denominator of `Q96`. + function test_mulDivQ96_fuzz(uint256 a, uint256 b) public { // Most significant 256 bits of the product. uint256 prod1; assembly { From 4d7f5fe02e3a020a43fca57d1a0d099bd730b441 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:36:55 -0400 Subject: [PATCH 13/25] Optimize comparison to `type(uint160).max` in `getNextSqrtPriceFromAmount1RoundingDown` This commit changes the way we check if an amount is within uint160 range in SqrtPriceMath.sol. Instead of comparing it with the max value of uint160, we now simply right shift by 160 and check if the result is zero. This substantially reduces execution costs, as seen in updated benchmarks and snapshots. --- .forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap | 2 +- .forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .../getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap | 2 +- .../getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .forge-snapshots/swap burn native 6909 for input.snap | 2 +- .forge-snapshots/swap mint native output as 6909.snap | 2 +- src/libraries/SqrtPriceMath.sol | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 0203089ec..f2eea84a3 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2488 \ No newline at end of file +2479 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index 0203089ec..f2eea84a3 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2488 \ No newline at end of file +2479 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap index ea5ca3642..be8a1b91d 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -547 \ No newline at end of file +538 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap index 4c9bbbfa9..c95da112f 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -468 \ No newline at end of file +459 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index bd62c03ff..14fa60d3b 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22971 \ No newline at end of file +22961 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index f6f64ffa9..4d1a6a7f0 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -76192 \ No newline at end of file +76183 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index b61490571..cb9b6b31b 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -138390 \ No newline at end of file +138381 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 3ec40fb5e..2835785cc 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -83,7 +83,7 @@ library SqrtPriceMath { // in both cases, avoid a mulDiv for most inputs if (add) { uint256 quotient = ( - amount <= type(uint160).max + amount >> 160 == 0 ? (amount << FixedPoint96.RESOLUTION).div(liquidity) : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) ); @@ -91,7 +91,7 @@ library SqrtPriceMath { return (uint256(sqrtPX96) + quotient).toUint160(); } else { uint256 quotient = ( - amount <= type(uint160).max + amount >> 160 == 0 ? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity) : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) ); From 5b183ba8b121a97beabbbb7b762cf29bd8164f32 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 7 Apr 2024 05:41:35 -0400 Subject: [PATCH 14/25] Update variable names in comments for `mulDivQ96` In the FullMath library, the variable names in the comments for the `mulDivQ96` function were updated from 'x' and 'y' to 'a' and 'b'. This change was implemented to match the actual variable names used in the function, making the code clearer and easier to understand. --- src/libraries/FullMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/FullMath.sol b/src/libraries/FullMath.sol index ce6bbc04f..9ae2998da 100644 --- a/src/libraries/FullMath.sol +++ b/src/libraries/FullMath.sol @@ -101,14 +101,14 @@ library FullMath { } } - /// @notice Calculates x * y / 2^96 with full precision. + /// @notice Calculates a * b / 2^96 with full precision. /// @param a The multiplicand /// @param b The multiplier /// @return result The 256-bit result function mulDivQ96(uint256 a, uint256 b) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { - // 512-bit multiply `[prod1 prod0] = x * y`. + // 512-bit multiply `[prod1 prod0] = a * b`. // Compute the product mod `2**256` and mod `2**256 - 1` // then use the Chinese Remainder Theorem to reconstruct // the 512 bit result. The result is stored in two 256 From 45f0f2aed5e1589abfa1263d96658401aed3b9fb Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Mon, 8 Apr 2024 00:23:35 -0400 Subject: [PATCH 15/25] Implement 'absDiff' function to improve calculation in `getAmount1Delta` The 'absDiff' function has been added in `SqrtPriceMath.sol` to replace direct subtraction in the `getAmount1Delta` method. This new function handles potential underflow by taking the absolute difference between inputs. Snapshots across different scenarios have been updated accordingly, reflecting more accurate value computations and slightly reduced gas costs. --- .../SwapMath_oneForZero_exactInCapped.snap | 2 +- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutCapped.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInCapped.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .forge-snapshots/addLiquidity with empty hook.snap | 2 +- .forge-snapshots/addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...t1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap | 2 +- ...nt1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .forge-snapshots/swap mint output as 6909.snap | 2 +- .../swap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 12 ++++++++++-- 30 files changed, 39 insertions(+), 31 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index 6c66109a3..d09be2fad 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -1714 \ No newline at end of file +1667 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 4f4963bcf..99a4beb95 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2990 \ No newline at end of file +2943 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index 9c5c74bae..4dfae6d59 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -1975 \ No newline at end of file +1928 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 4f4963bcf..99a4beb95 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2990 \ No newline at end of file +2943 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index d7bda98e9..773de0f75 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -1713 \ No newline at end of file +1666 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index f2eea84a3..05cfb717b 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2479 \ No newline at end of file +2385 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index 062bc1c2d..369598ce7 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -1974 \ No newline at end of file +1927 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index f2eea84a3..05cfb717b 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2479 \ No newline at end of file +2385 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 2217eb647..38bbfdeb9 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -265073 \ No newline at end of file +264972 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index e4214e73f..573d66671 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -139916 \ No newline at end of file +139815 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index b4d2c97e5..53d6b4b4e 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -145233 \ No newline at end of file +145132 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index 50b04dff0..ac471d3ba 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -402 \ No newline at end of file +355 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index cdf1f34dc..bbce65161 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -403 \ No newline at end of file +356 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 14fa60d3b..e6ea7c0d4 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22961 \ No newline at end of file +22943 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 7c285c5a5..491144804 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -56063 \ No newline at end of file +55962 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index ebe77bca3..b0c4802da 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -148249 \ No newline at end of file +148148 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 39ceb4a66..6fa583a8e 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -149713 \ No newline at end of file +149612 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index d54e1972a..d63998f7b 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -132602 \ No newline at end of file +132400 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 9c62001be..f3d9f20d6 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -146478 \ No newline at end of file +146276 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index a1aeaa858..aabb18aac 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -72114 \ No newline at end of file +72013 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 08c86fff8..1d710071c 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -60117 \ No newline at end of file +60016 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index b3c8aa5e0..eed64372e 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -80310 \ No newline at end of file +80209 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 4d1a6a7f0..def20b52c 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -76183 \ No newline at end of file +75981 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index cb9b6b31b..4649cedaa 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -138381 \ No newline at end of file +138179 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 6db8d15a8..425444ea7 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -155293 \ No newline at end of file +155091 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 5052be065..28dff80fd 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -155707 \ No newline at end of file +155404 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 345d23197..9decc4d58 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -89368 \ No newline at end of file +89166 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index ab6fe9d44..881924beb 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -60095 \ No newline at end of file +59994 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 08c01bdac..f3d7a1024 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -139994 \ No newline at end of file +139792 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 2835785cc..689886bfe 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -193,6 +193,15 @@ library SqrtPriceMath { } } + /// @notice Equivalent to: `a > b ? a - b : b - a` + function absDiff(uint160 a, uint160 b) internal pure returns (uint256 res) { + assembly { + let diff := sub(a, b) + let mask := sub(0, slt(diff, 0)) + res := xor(mask, add(mask, diff)) + } + } + /// @notice Gets the amount1 delta between two prices /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) /// @param sqrtRatioAX96 A sqrt price @@ -205,8 +214,7 @@ library SqrtPriceMath { pure returns (uint256 amount1) { - (sqrtRatioAX96, sqrtRatioBX96) = sort2(sqrtRatioAX96, sqrtRatioBX96); - uint256 numerator = sqrtRatioBX96.sub(sqrtRatioAX96); + uint256 numerator = absDiff(sqrtRatioAX96, sqrtRatioBX96); uint256 denominator = FixedPoint96.Q96; /** * Equivalent to: From f82203aeeb496cd32dc26029cb438b60cbf2017c Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 21 Apr 2024 06:56:35 -0400 Subject: [PATCH 16/25] Avoid casting and optimize further This update includes refactoring in the SqrtPriceMath.sol, avoiding unnecessary typecasting and using assembly code for optimal performance. Most of the changes involve replacing expressions with their equivalent assembly instructions for faster execution. This code optimization resulted in reduced gas cost in forge-snapshot files. --- .../SwapMath_oneForZero_exactInCapped.snap | 2 +- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutCapped.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInCapped.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ..._gasCostForAmount0WhereRoundUpIsFalse.snap | 2 +- ...a_gasCostForAmount0WhereRoundUpIsTrue.snap | 2 +- ..._gasCostForAmount1WhereRoundUpIsFalse.snap | 2 +- ...a_gasCostForAmount1WhereRoundUpIsTrue.snap | 2 +- ...iceFromInput_zeroForOneEqualsFalseGas.snap | 2 +- ...riceFromInput_zeroForOneEqualsTrueGas.snap | 2 +- ...ceFromOutput_zeroForOneEqualsFalseGas.snap | 2 +- ...iceFromOutput_zeroForOneEqualsTrueGas.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 37 ++++++++++++------- 36 files changed, 59 insertions(+), 48 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index d09be2fad..eaebab4d4 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -1667 \ No newline at end of file +1614 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 99a4beb95..ab991e61a 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2943 \ No newline at end of file +2832 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index 4dfae6d59..6e575a4c7 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -1928 \ No newline at end of file +1875 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 99a4beb95..ab991e61a 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2943 \ No newline at end of file +2832 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index 773de0f75..0eb5c3a21 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -1666 \ No newline at end of file +1613 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 05cfb717b..c3be2c20a 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2385 \ No newline at end of file +2297 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index 369598ce7..1276e5988 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -1927 \ No newline at end of file +1874 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index 05cfb717b..c3be2c20a 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2385 \ No newline at end of file +2297 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 6346ea1d4..b19aceb36 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -258909 \ No newline at end of file +258840 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 9246f08f3..183ba1c05 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -136252 \ No newline at end of file +136183 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index d1c07c6e0..43285f88a 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -139086 \ No newline at end of file +139017 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap index 37e6f1741..8cb9d8439 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap @@ -1 +1 @@ -515 \ No newline at end of file +475 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap index 1dd3380cd..d7b14a6c4 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap @@ -1 +1 @@ -516 \ No newline at end of file +476 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index ac471d3ba..136c8cacf 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -355 \ No newline at end of file +342 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index bbce65161..f64b48379 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -356 \ No newline at end of file +343 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap index be8a1b91d..1dd3380cd 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -538 \ No newline at end of file +516 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap index e9769c199..45b1ccee2 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -759 \ No newline at end of file +741 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap index e2a83a8ce..7e0a9d2ab 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -855 \ No newline at end of file +837 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap index c95da112f..3fa694f24 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -459 \ No newline at end of file +437 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index d033278c9..514739154 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22488 \ No newline at end of file +22377 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index ba3e5a170..d5144e7da 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -53683 \ No newline at end of file +53614 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 91562077e..fb4c0a1f9 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -141455 \ No newline at end of file +141386 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 89a33c4bb..d57ea880f 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -137733 \ No newline at end of file +137664 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 045c4f419..6ade0ddd7 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -126320 \ No newline at end of file +126168 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 9d618e7ac..cc22d0fb4 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -137713 \ No newline at end of file +137561 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index d91a1db2b..ce0ec7815 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -70809 \ No newline at end of file +70710 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 406d4c82e..6ce1746ef 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -61128 \ No newline at end of file +61029 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index d26c1112a..4c89f9fea 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -79184 \ No newline at end of file +79085 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 1de3e5ad3..d6c3ee9c9 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -74802 \ No newline at end of file +74726 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 9f091e531..5080cb8cb 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -135032 \ No newline at end of file +134956 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 5afe2397a..aba87cd92 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -151716 \ No newline at end of file +151564 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 2384586f1..965bb3999 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -157951 \ No newline at end of file +157700 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 0f1aa160e..39bea39fd 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -90203 \ No newline at end of file +90051 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 73c811de9..5723a78be 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -61107 \ No newline at end of file +61008 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 7874d13b0..46e167775 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -131229 \ No newline at end of file +131077 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 689886bfe..836103587 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -36,7 +36,10 @@ library SqrtPriceMath { { // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price if (amount == 0) return sqrtPX96; - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator1; + assembly { + numerator1 := shl(96, liquidity) + } if (add) { unchecked { @@ -79,21 +82,25 @@ library SqrtPriceMath { pure returns (uint160) { + uint256 liquidity256; + assembly { + liquidity256 := liquidity + } // if we're adding (subtracting), rounding down requires rounding the quotient down (up) // in both cases, avoid a mulDiv for most inputs if (add) { uint256 quotient = ( amount >> 160 == 0 - ? (amount << FixedPoint96.RESOLUTION).div(liquidity) - : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ? (amount << FixedPoint96.RESOLUTION).div(liquidity256) + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity256) ); return (uint256(sqrtPX96) + quotient).toUint160(); } else { uint256 quotient = ( amount >> 160 == 0 - ? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity) - : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity256) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity256) ); if (sqrtPX96 <= quotient) revert NotEnoughLiquidity(); @@ -171,8 +178,12 @@ library SqrtPriceMath { if (sqrtRatioAX96 == 0) revert InvalidPrice(); - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - uint256 numerator2 = sqrtRatioBX96.sub(sqrtRatioAX96); + uint256 numerator1; + uint256 numerator2; + assembly { + numerator1 := shl(96, liquidity) + numerator2 := sub(sqrtRatioBX96, sqrtRatioAX96) + } /** * Equivalent to: * roundUp @@ -216,6 +227,10 @@ library SqrtPriceMath { { uint256 numerator = absDiff(sqrtRatioAX96, sqrtRatioBX96); uint256 denominator = FixedPoint96.Q96; + uint256 liquidity256; + assembly { + liquidity256 := liquidity + } /** * Equivalent to: * amount1 = roundUp @@ -223,9 +238,9 @@ library SqrtPriceMath { * : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. */ - amount1 = FullMath.mulDivQ96(liquidity, numerator); + amount1 = FullMath.mulDivQ96(liquidity256, numerator); assembly { - amount1 := add(amount1, and(gt(mulmod(liquidity, numerator, denominator), 0), roundUp)) + amount1 := add(amount1, and(gt(mulmod(liquidity256, numerator, denominator), 0), roundUp)) } } @@ -248,8 +263,6 @@ library SqrtPriceMath { bool sign; uint128 liquidityAbs; assembly { - // In case the upper bits are not clean. - liquidity := signextend(15, liquidity) // sign = 1 if liquidity >= 0 else 0 sign := iszero(slt(liquidity, 0)) // mask = 0 if liquidity >= 0 else -1 @@ -287,8 +300,6 @@ library SqrtPriceMath { bool sign; uint128 liquidityAbs; assembly { - // In case the upper bits are not clean. - liquidity := signextend(15, liquidity) // sign = 1 if liquidity >= 0 else 0 sign := iszero(slt(liquidity, 0)) // mask = 0 if liquidity >= 0 else -1 From f3400fbdb08a6465826dfb93fbd7f21b33a23b5a Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:49:37 -0400 Subject: [PATCH 17/25] Refactor sign and mask calculation in `SqrtPriceMath` Updated the code to improve how sign and mask values are calculated within the SqrtPriceMath library. The new implementation uses shift arithmetic right (sar) operations for more efficient and clear code. Changes have been made in absDiff, getAmount0Delta and getAmount1Delta functions. --- .../SwapMath_oneForZero_exactInCapped.snap | 2 +- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutCapped.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInCapped.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .forge-snapshots/addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...Delta_gasCostForAmount1WhereRoundUpIsFalse.snap | 2 +- ...1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .forge-snapshots/swap mint output as 6909.snap | 2 +- .../swap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 14 +++++++------- 31 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index dee9fcb50..f5b9e8bf2 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -1915 \ No newline at end of file +1912 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 3e73ce981..793d8d9a2 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2326 \ No newline at end of file +2320 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index d2fc5e3e1..7f3589da4 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -1654 \ No newline at end of file +1651 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index b2989f42d..4f5a05a71 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2872 \ No newline at end of file +2869 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index 7a73ce32e..085a6265f 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -1914 \ No newline at end of file +1911 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 3ffdde987..31eb8b45f 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2682 \ No newline at end of file +2679 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index b69eea90e..08ae03db5 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -1653 \ No newline at end of file +1650 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index bc90f3f88..583f9c4eb 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2337 \ No newline at end of file +2331 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 796d85e26..b36d9df78 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -258734 \ No newline at end of file +258703 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 7ae6f1704..f1fdde239 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -136099 \ No newline at end of file +136068 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index 73908af51..76107ede8 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -138933 \ No newline at end of file +138902 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index 136c8cacf..832f62a3c 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -342 \ No newline at end of file +339 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index f64b48379..91a3d4243 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -343 \ No newline at end of file +340 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index bda0bb709..f40b1af7c 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22432 \ No newline at end of file +22424 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index d5144e7da..8c81d139c 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -53614 \ No newline at end of file +53583 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index fb4c0a1f9..8b75f242d 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -141386 \ No newline at end of file +141355 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index d57ea880f..4701bdff2 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -137664 \ No newline at end of file +137633 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 08bebf625..d75e44c37 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -126250 \ No newline at end of file +126244 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 85037fd6c..18daaea03 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -137643 \ No newline at end of file +137637 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 55285933a..fcdb1fde7 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -70752 \ No newline at end of file +70749 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index cd2d8238c..4c2c504e9 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -61071 \ No newline at end of file +61068 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index e8d6c8eb5..b0cde6945 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -79223 \ No newline at end of file +79220 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 1d442af46..3b12393a4 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -74864 \ No newline at end of file +74858 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 6a2a1bba7..da2a52397 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -134998 \ No newline at end of file +134992 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 84af98a85..7d108c48c 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -151646 \ No newline at end of file +151640 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index b0701664e..66be8c412 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -157818 \ No newline at end of file +157809 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index d8735a081..10b456de3 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -90133 \ No newline at end of file +90127 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 6a4cd9e69..326c368bb 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -61050 \ No newline at end of file +61047 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index 5adc9b842..6b938c6f0 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -150394 \ No newline at end of file +150385 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 22631bd86..a39174268 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -131160 \ No newline at end of file +131154 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 836103587..353c27d5f 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -208,7 +208,7 @@ library SqrtPriceMath { function absDiff(uint160 a, uint160 b) internal pure returns (uint256 res) { assembly { let diff := sub(a, b) - let mask := sub(0, slt(diff, 0)) + let mask := sar(255, diff) res := xor(mask, add(mask, diff)) } } @@ -263,10 +263,10 @@ library SqrtPriceMath { bool sign; uint128 liquidityAbs; assembly { - // sign = 1 if liquidity >= 0 else 0 - sign := iszero(slt(liquidity, 0)) // mask = 0 if liquidity >= 0 else -1 - let mask := sub(sign, 1) + let mask := sar(255, liquidity) + // sign = 1 if liquidity >= 0 else 0 + sign := iszero(mask) liquidityAbs := xor(mask, add(mask, liquidity)) } // amount0Abs = liquidity / sqrt(lower) - liquidity / sqrt(upper) < type(uint224).max @@ -300,10 +300,10 @@ library SqrtPriceMath { bool sign; uint128 liquidityAbs; assembly { - // sign = 1 if liquidity >= 0 else 0 - sign := iszero(slt(liquidity, 0)) // mask = 0 if liquidity >= 0 else -1 - let mask := sub(sign, 1) + let mask := sar(255, liquidity) + // sign = 1 if liquidity >= 0 else 0 + sign := iszero(mask) liquidityAbs := xor(mask, add(mask, liquidity)) } // amount1Abs = liquidity * (sqrt(upper) - sqrt(lower)) < type(uint192).max From 9eb9020f259c07f884651ad9ef1c0c0502eb824e Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 11 May 2024 01:26:47 -0400 Subject: [PATCH 18/25] Switch if else branches --- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- ...iceFromInput_zeroForOneEqualsFalseGas.snap | 2 +- ...riceFromInput_zeroForOneEqualsTrueGas.snap | 2 +- ...ceFromOutput_zeroForOneEqualsFalseGas.snap | 2 +- ...iceFromOutput_zeroForOneEqualsTrueGas.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap CA fee on unspecified.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 38 +++++++++---------- 24 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 793d8d9a2..1003f2e0b 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2320 \ No newline at end of file +2318 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 4f5a05a71..47fd2ab34 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2869 \ No newline at end of file +2865 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 31eb8b45f..fecee22c1 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2679 \ No newline at end of file +2677 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index 583f9c4eb..63e57e7d2 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2331 \ No newline at end of file +2327 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap index 1dd3380cd..54a584dec 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -516 \ No newline at end of file +514 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap index 45b1ccee2..ddc27b0c1 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -741 \ No newline at end of file +739 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap index 7e0a9d2ab..3e90fddab 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -837 \ No newline at end of file +833 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap index 3fa694f24..af40ff6b8 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -437 \ No newline at end of file +433 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 5aa9d241f..218c430d0 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23243 \ No newline at end of file +23241 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index e08863d4b..e6a5bfcc1 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -130380 \ No newline at end of file +130378 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 79c0c2d19..55fa6a234 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -148739 \ No newline at end of file +148737 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index b393f5ca6..b91e54790 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -184219 \ No newline at end of file +184217 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 38ed72cb6..d61eab076 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -113485 \ No newline at end of file +113483 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 23c7648dd..40990e3ce 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -124863 \ No newline at end of file +124861 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index ca88576b7..0e353b9c3 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -137031 \ No newline at end of file +137027 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 2491e3d1f..20419a8e6 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -125937 \ No newline at end of file +125933 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 55b1ce1f4..adf30baed 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -147808 \ No newline at end of file +147806 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 583451d8c..a3f36818c 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -164688 \ No newline at end of file +164686 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 204ff7d25..58d0cfe4a 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -223932 \ No newline at end of file +223928 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 2f01cb797..8110f6bb4 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -148955 \ No newline at end of file +148953 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index ec29d29f2..849458181 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -124875 \ No newline at end of file +124873 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index a9ad0050a..3afe4cff0 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -181246 \ No newline at end of file +181242 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 9448de3c5..76f540b6a 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -159598 \ No newline at end of file +159596 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 94baabf07..13d6c909c 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -41,7 +41,16 @@ library SqrtPriceMath { numerator1 := shl(96, liquidity) } - if (add) { + if (!add) { + unchecked { + uint256 product = amount * sqrtPX96; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + if (!(product.div(amount) == sqrtPX96 && numerator1 > product)) revert PriceOverflow(); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } else { unchecked { uint256 product = amount * sqrtPX96; // checks for overflow @@ -55,15 +64,6 @@ library SqrtPriceMath { } // denominator is checked for overflow return uint160(numerator1.divRoundingUp(numerator1.div(sqrtPX96) + amount)); - } else { - unchecked { - uint256 product = amount * sqrtPX96; - // if the product overflows, we know the denominator underflows - // in addition, we must check that the denominator does not underflow - if (!(product.div(amount) == sqrtPX96 && numerator1 > product)) revert PriceOverflow(); - uint256 denominator = numerator1 - product; - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); - } } } @@ -88,15 +88,7 @@ library SqrtPriceMath { } // if we're adding (subtracting), rounding down requires rounding the quotient down (up) // in both cases, avoid a mulDiv for most inputs - if (add) { - uint256 quotient = ( - amount >> 160 == 0 - ? (amount << FixedPoint96.RESOLUTION).div(liquidity256) - : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity256) - ); - - return (uint256(sqrtPX96) + quotient).toUint160(); - } else { + if (!add) { uint256 quotient = ( amount >> 160 == 0 ? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity256) @@ -108,6 +100,14 @@ library SqrtPriceMath { unchecked { return uint160(sqrtPX96 - quotient); } + } else { + uint256 quotient = ( + amount >> 160 == 0 + ? (amount << FixedPoint96.RESOLUTION).div(liquidity256) + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity256) + ); + + return (uint256(sqrtPX96) + quotient).toUint160(); } } From 435a4f3a14df7e2141b96491227c476ecd7d9a76 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 18 May 2024 13:40:15 -0400 Subject: [PATCH 19/25] Remove unused functions from `UnsafeMath` --- src/libraries/UnsafeMath.sol | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/libraries/UnsafeMath.sol b/src/libraries/UnsafeMath.sol index f969208b1..9758986df 100644 --- a/src/libraries/UnsafeMath.sol +++ b/src/libraries/UnsafeMath.sol @@ -4,24 +4,6 @@ pragma solidity ^0.8.20; /// @title Math functions that do not check inputs or outputs /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks library UnsafeMath { - function add(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := add(x, y) - } - } - - function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := sub(x, y) - } - } - - function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := mul(x, y) - } - } - function div(uint256 x, uint256 y) internal pure returns (uint256 z) { assembly { z := div(x, y) From bd96af98576604f314d7ba6ced617bb284baafaa Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 19 May 2024 04:25:13 -0400 Subject: [PATCH 20/25] Add `UnsafeMath.div` and optimize `SqrtPriceMath.getAmount0Delta` Added new division function in `UnsafeMath` library that doesn't perform safety checks and a function for sorting two uint values in `SqrtPriceMath`. Adjusted the calculation process for `getAmount0Delta` in `SqrtPriceMath` to use the new functions for better performance and safety checks. --- .../SwapMath_oneForZero_exactInCapped.snap | 2 +- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutCapped.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInCapped.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- ...o already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- ...new liquidity to a position with salt.snap | 2 +- ..._gasCostForAmount0WhereRoundUpIsFalse.snap | 2 +- ...a_gasCostForAmount0WhereRoundUpIsTrue.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap CA fee on unspecified.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../swap with return dynamic fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 42 ++++++++++++++++--- src/libraries/UnsafeMath.sol | 6 +++ 36 files changed, 76 insertions(+), 40 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index 19261c267..a2cb5787f 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -2248 \ No newline at end of file +2170 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 43e6c3379..90ab247d9 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -3016 \ No newline at end of file +2938 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index 01a646754..9d1049cde 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -1987 \ No newline at end of file +1909 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 72cd61fe0..8e022b381 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -3256 \ No newline at end of file +3100 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index b038f6888..936a4209a 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -2238 \ No newline at end of file +2159 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 3832db4f7..ac78499f4 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -3191 \ No newline at end of file +3033 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index ca9836afa..7934f8890 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -1977 \ No newline at end of file +1898 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index d872354f5..05c811e07 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2866 \ No newline at end of file +2787 \ No newline at end of file diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index ff76e7f86..d002014c9 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -153643 \ No newline at end of file +153618 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index b4678d19b..1ce71450e 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -331309 \ No newline at end of file +331284 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index c59df0298..0e0209a39 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -286302 \ No newline at end of file +286277 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index c5bed833e..d4f40dc8f 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -143853 \ No newline at end of file +143828 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index cbb3de708..767d1e160 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -301821 \ No newline at end of file +301796 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap index 1dd3380cd..00c22e598 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap @@ -1 +1 @@ -516 \ No newline at end of file +429 \ No newline at end of file diff --git a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap index b7f636c12..99fd4575a 100644 --- a/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap @@ -1 +1 @@ -665 \ No newline at end of file +577 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 81ca44cf1..12b5d2aeb 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -21709 \ No newline at end of file +21613 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 082d59d10..7384bd47e 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -186775 \ No newline at end of file +186751 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index e7fddfb22..3df6e7307 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -123143 \ No newline at end of file +123119 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 8b983838d..554992b95 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -119930 \ No newline at end of file +119906 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 4fc9f9feb..cfa5f12ae 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -116827 \ No newline at end of file +116752 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 7a20c6b3d..b0a0d70f5 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -131988 \ No newline at end of file +131913 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index 27c7f51ac..92f13e023 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -183126 \ No newline at end of file +183051 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 8550686dc..26c9d246f 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -112548 \ No newline at end of file +112498 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 14ee97dfc..fbec2b9ca 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -123891 \ No newline at end of file +123841 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index b19efd36d..992bd7d4d 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -135923 \ No newline at end of file +135875 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index dc06b6c6a..ba6c6461f 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -125140 \ No newline at end of file +125115 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index fa5caa1ba..48d68f256 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -147180 \ No newline at end of file +147156 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 1acf3c68f..cd818f9cc 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -163842 \ No newline at end of file +163767 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index b2d6ca0cd..968e09dbd 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -222199 \ No newline at end of file +222074 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 96c9f4d55..8513cf6e8 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -148043 \ No newline at end of file +147968 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 897a376b0..21873b84d 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -123903 \ No newline at end of file +123853 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index 9efd3b6c3..9d51e2b79 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -180223 \ No newline at end of file +180173 \ No newline at end of file diff --git a/.forge-snapshots/swap with return dynamic fee.snap b/.forge-snapshots/swap with return dynamic fee.snap index cb2abe163..39f947dfe 100644 --- a/.forge-snapshots/swap with return dynamic fee.snap +++ b/.forge-snapshots/swap with return dynamic fee.snap @@ -1 +1 @@ -155902 \ No newline at end of file +155827 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 265212952..62de932d4 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -158505 \ No newline at end of file +158430 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 71262ffa2..57506fa9e 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -11,6 +11,7 @@ import {FixedPoint96} from "./FixedPoint96.sol"; /// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas library SqrtPriceMath { using SafeCast for uint256; + using UnsafeMath for *; error InvalidPriceOrLiquidity(); error InvalidPrice(); @@ -142,6 +143,16 @@ library SqrtPriceMath { : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); } + /// @notice Sorts two `uint160`s and returns them in ascending order + function sort2(uint160 a, uint160 b) internal pure returns (uint160, uint160) { + assembly { + let diff := mul(xor(a, b), lt(b, a)) + a := xor(a, diff) + b := xor(b, diff) + } + return (a, b); + } + /// @notice Gets the amount0 delta between two prices /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) @@ -156,16 +167,35 @@ library SqrtPriceMath { returns (uint256 amount0) { unchecked { - if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + (sqrtPriceAX96, sqrtPriceBX96) = sort2(sqrtPriceAX96, sqrtPriceBX96); + + /// @solidity memory-safe-assembly + assembly { + if iszero(sqrtPriceAX96) { + mstore(0, 0x00bfc921) // selector for InvalidPrice() + revert(0x1c, 0x04) + } + } - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - uint256 numerator2 = sqrtPriceBX96 - sqrtPriceAX96; + uint256 _sqrtPriceAX96; + uint256 _sqrtPriceBX96; + assembly { + // avoid implicit upcast + _sqrtPriceAX96 := sqrtPriceAX96 + _sqrtPriceBX96 := sqrtPriceBX96 + } - if (sqrtPriceAX96 == 0) revert InvalidPrice(); + uint256 numerator1; + assembly { + numerator1 := shl(96, liquidity) + } + uint256 numerator2 = _sqrtPriceBX96 - _sqrtPriceAX96; return roundUp - ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtPriceBX96), sqrtPriceAX96) - : FullMath.mulDiv(numerator1, numerator2, sqrtPriceBX96) / sqrtPriceAX96; + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, _sqrtPriceBX96), _sqrtPriceAX96 + ) + : UnsafeMath.div(FullMath.mulDiv(numerator1, numerator2, _sqrtPriceBX96), _sqrtPriceAX96); } } diff --git a/src/libraries/UnsafeMath.sol b/src/libraries/UnsafeMath.sol index 690ee7dfe..9758986df 100644 --- a/src/libraries/UnsafeMath.sol +++ b/src/libraries/UnsafeMath.sol @@ -4,6 +4,12 @@ pragma solidity ^0.8.20; /// @title Math functions that do not check inputs or outputs /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks library UnsafeMath { + function div(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := div(x, y) + } + } + /// @notice Returns ceil(x / y) /// @dev division by 0 has unspecified behavior, and must be checked externally /// @param x The dividend From 04d35ab7a0bfd3f4f8901e2a5f7c94ffedd59c18 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 19 May 2024 04:31:55 -0400 Subject: [PATCH 21/25] Add `assign` function for direct uint conversion in `SqrtPriceMath` This commit adds a new function named 'assign' in SqrtPriceMath.sol. This function allows for direct conversion from two `uint160`s to two `uint256`s without implicit upcasting. Additionally, this newly introduced function has been utilized in line 185 where previously the conversion was done via assembly, simplifying the code and maintaining consistency. --- src/libraries/SqrtPriceMath.sol | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 57506fa9e..9a00b1dd8 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -153,6 +153,14 @@ library SqrtPriceMath { return (a, b); } + /// @notice Directly returns two `uint256`s from two `uint160`s without implicit upcasting + function assign(uint160 a, uint160 b) internal pure returns (uint256 _a, uint256 _b) { + assembly { + _a := a + _b := b + } + } + /// @notice Gets the amount0 delta between two prices /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) @@ -177,13 +185,7 @@ library SqrtPriceMath { } } - uint256 _sqrtPriceAX96; - uint256 _sqrtPriceBX96; - assembly { - // avoid implicit upcast - _sqrtPriceAX96 := sqrtPriceAX96 - _sqrtPriceBX96 := sqrtPriceBX96 - } + (uint256 _sqrtPriceAX96, uint256 _sqrtPriceBX96) = assign(sqrtPriceAX96, sqrtPriceBX96); uint256 numerator1; assembly { From 789b88debd0c0bed0c6a4a23a1649d36726bf533 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 19 May 2024 04:56:41 -0400 Subject: [PATCH 22/25] Add natspec on `UnsafeMath.div` --- src/libraries/SqrtPriceMath.sol | 1 - src/libraries/UnsafeMath.sol | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 9a00b1dd8..25c3bda19 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -11,7 +11,6 @@ import {FixedPoint96} from "./FixedPoint96.sol"; /// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas library SqrtPriceMath { using SafeCast for uint256; - using UnsafeMath for *; error InvalidPriceOrLiquidity(); error InvalidPrice(); diff --git a/src/libraries/UnsafeMath.sol b/src/libraries/UnsafeMath.sol index 9758986df..44ec6156a 100644 --- a/src/libraries/UnsafeMath.sol +++ b/src/libraries/UnsafeMath.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.20; /// @title Math functions that do not check inputs or outputs /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks library UnsafeMath { + /// @notice Returns x / y or 0 if y == 0 + /// @dev saves gas by skipping the zero divisor check + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, x / y function div(uint256 x, uint256 y) internal pure returns (uint256 z) { assembly { z := div(x, y) From a2c4b880d1c3969689ee4ad0b83e37ec98380ce8 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 19 May 2024 06:00:38 -0400 Subject: [PATCH 23/25] Optimize signed variants of `getAmount0Delta` and `getAmount1Delta` The code in `SqrtPriceMath` has been refactored to optimize the functions `getAmount0Delta` and `getAmount1Delta`. The changes replaced previous return statements with inline assembly to handle operations on `int256` more efficiently. This has resulted in reduction in bytecode size and runtime gas. --- ...o already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- ...new liquidity to a position with salt.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- ...dLiquidity second addition same range.snap | 2 +- .forge-snapshots/simple addLiquidity.snap | 2 +- ...emoveLiquidity some liquidity remains.snap | 2 +- .forge-snapshots/simple removeLiquidity.snap | 2 +- src/libraries/SqrtPriceMath.sol | 56 ++++++++++++++++--- 14 files changed, 61 insertions(+), 21 deletions(-) diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index ff76e7f86..1434cfdf8 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -153643 \ No newline at end of file +153569 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index b4678d19b..ab6c6cbb0 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -331309 \ No newline at end of file +331235 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index c59df0298..1bdb8b929 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -286302 \ No newline at end of file +286228 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index c5bed833e..4201eadcf 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -143853 \ No newline at end of file +143779 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index cbb3de708..eb1755871 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -301821 \ No newline at end of file +301747 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 81ca44cf1..ac47b22a0 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -21709 \ No newline at end of file +21684 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 082d59d10..80367b2f3 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -186775 \ No newline at end of file +186725 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index e7fddfb22..003ba99af 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -123143 \ No newline at end of file +123093 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 8b983838d..1d2f70439 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -119930 \ No newline at end of file +119880 \ No newline at end of file diff --git a/.forge-snapshots/simple addLiquidity second addition same range.snap b/.forge-snapshots/simple addLiquidity second addition same range.snap index ccf9f3450..14be4dac5 100644 --- a/.forge-snapshots/simple addLiquidity second addition same range.snap +++ b/.forge-snapshots/simple addLiquidity second addition same range.snap @@ -1 +1 @@ -104932 \ No newline at end of file +104895 \ No newline at end of file diff --git a/.forge-snapshots/simple addLiquidity.snap b/.forge-snapshots/simple addLiquidity.snap index 8f3accdaa..0d4d720e6 100644 --- a/.forge-snapshots/simple addLiquidity.snap +++ b/.forge-snapshots/simple addLiquidity.snap @@ -1 +1 @@ -167424 \ No newline at end of file +167387 \ No newline at end of file diff --git a/.forge-snapshots/simple removeLiquidity some liquidity remains.snap b/.forge-snapshots/simple removeLiquidity some liquidity remains.snap index fc72404ca..064c0fdab 100644 --- a/.forge-snapshots/simple removeLiquidity some liquidity remains.snap +++ b/.forge-snapshots/simple removeLiquidity some liquidity remains.snap @@ -1 +1 @@ -98529 \ No newline at end of file +98504 \ No newline at end of file diff --git a/.forge-snapshots/simple removeLiquidity.snap b/.forge-snapshots/simple removeLiquidity.snap index 0eb1ac8de..2ed62b07f 100644 --- a/.forge-snapshots/simple removeLiquidity.snap +++ b/.forge-snapshots/simple removeLiquidity.snap @@ -1 +1 @@ -90569 \ No newline at end of file +90544 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 71262ffa2..7d71c52d2 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -198,10 +198,30 @@ library SqrtPriceMath { pure returns (int256 amount0) { - unchecked { - return liquidity < 0 - ? getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() - : -getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + /** + * Equivalent to: + * amount0 = liquidity < 0 + * ? getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + * : -getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + */ + bool roundUp; + uint128 liquidityAbs; + assembly { + // mask = 0 if liquidity >= 0 else -1 + let mask := sar(255, liquidity) + // roundUp = 1 if liquidity >= 0 else 0 + roundUp := iszero(mask) + liquidityAbs := xor(mask, add(mask, liquidity)) + } + // amount0Abs = liquidity / sqrt(lower) - liquidity / sqrt(upper) < type(uint224).max + // always fits in 224 bits, no need for toInt256() + uint256 amount0Abs = getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, liquidityAbs, roundUp); + assembly { + // mask = -1 if liquidity >= 0 else 0 + let mask := sub(0, roundUp) + // If liquidity < 0, amount0 = |amount0| = 0 ^ |amount0| + // If liquidity >= 0, amount0 = -|amount0| = ~|amount0| + 1 = (-1) ^ |amount0| - (-1) + amount0 := sub(xor(amount0Abs, mask), mask) } } @@ -215,10 +235,30 @@ library SqrtPriceMath { pure returns (int256 amount1) { - unchecked { - return liquidity < 0 - ? getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() - : -getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + /** + * Equivalent to: + * amount1 = liquidity < 0 + * ? getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + * : -getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + */ + bool roundUp; + uint128 liquidityAbs; + assembly { + // mask = 0 if liquidity >= 0 else -1 + let mask := sar(255, liquidity) + // roundUp = 1 if liquidity >= 0 else 0 + roundUp := iszero(mask) + liquidityAbs := xor(mask, add(mask, liquidity)) + } + // amount1Abs = liquidity * (sqrt(upper) - sqrt(lower)) < type(uint192).max + // always fits in 192 bits, no need for toInt256() + uint256 amount1Abs = getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, liquidityAbs, roundUp); + assembly { + // mask = -1 if liquidity >= 0 else 0 + let mask := sub(0, roundUp) + // If liquidity < 0, amount1 = |amount1| = 0 ^ |amount1| + // If liquidity >= 0, amount1 = -|amount1| = ~|amount1| + 1 = (-1) ^ |amount1| - (-1) + amount1 := sub(xor(amount1Abs, mask), mask) } } } From 99104cbba249b9a8d1326173f6a58ba0923420e0 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 23 May 2024 00:46:32 -0400 Subject: [PATCH 24/25] Cast variables in assembly and use `UnsafeMath.div` --- .../SwapMath_oneForZero_exactInPartial.snap | 2 +- .../SwapMath_oneForZero_exactOutPartial.snap | 2 +- .../SwapMath_zeroForOne_exactInPartial.snap | 2 +- .../SwapMath_zeroForOne_exactOutPartial.snap | 2 +- ...iceFromInput_zeroForOneEqualsFalseGas.snap | 2 +- ...riceFromInput_zeroForOneEqualsTrueGas.snap | 2 +- ...ceFromOutput_zeroForOneEqualsFalseGas.snap | 2 +- ...iceFromOutput_zeroForOneEqualsTrueGas.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap CA fee on unspecified.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../swap with return dynamic fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 59 ++++++++++--------- 25 files changed, 56 insertions(+), 51 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index decfddd46..cd9fe7809 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2390 \ No newline at end of file +2299 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index c173e591f..737a0ba18 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2826 \ No newline at end of file +2757 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index e4810b130..04721971d 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2886 \ No newline at end of file +2817 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index 80ef2483b..9f9ab44e6 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2493 \ No newline at end of file +2429 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap index 23c5f49dc..6efca19ea 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -572 \ No newline at end of file +481 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap index 19e03cffa..e8f7ef6cf 100644 --- a/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -776 \ No newline at end of file +707 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap index c5befbc75..e1233a640 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap @@ -1 +1 @@ -856 \ No newline at end of file +787 \ No newline at end of file diff --git a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap index 4c9bbbfa9..57db2e978 100644 --- a/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap +++ b/.forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap @@ -1 +1 @@ -468 \ No newline at end of file +404 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 35c6b247a..f0ba69b55 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -21239 \ No newline at end of file +21035 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 28298d6d7..efe090bdb 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -115750 \ No newline at end of file +115693 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 6a7052bf5..26097def0 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -130911 \ No newline at end of file +130854 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index 80dbc371f..55f3fb0d6 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -182049 \ No newline at end of file +181992 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 504a8b3ee..07a391265 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -111904 \ No newline at end of file +111847 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 099add65d..9aa1453ae 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -123247 \ No newline at end of file +123190 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 7c88179cb..a2ec237a6 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -135114 \ No newline at end of file +135057 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 9a4bf5373..20f68ff61 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -124384 \ No newline at end of file +124332 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index ea2e69269..403c4b54f 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -146125 \ No newline at end of file +146046 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 21e99bb0e..2bddba49d 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -162765 \ No newline at end of file +162708 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index bb078085e..c452d1c08 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -220478 \ No newline at end of file +220364 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 3b3295dab..9b951ac51 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -146966 \ No newline at end of file +146909 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 696195ddf..b9483c0d6 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -123259 \ No newline at end of file +123202 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index e7dd6e42e..fd4853ada 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -179028 \ No newline at end of file +178976 \ No newline at end of file diff --git a/.forge-snapshots/swap with return dynamic fee.snap b/.forge-snapshots/swap with return dynamic fee.snap index 51d897d60..7da1ee629 100644 --- a/.forge-snapshots/swap with return dynamic fee.snap +++ b/.forge-snapshots/swap with return dynamic fee.snap @@ -1 +1 @@ -154825 \ No newline at end of file +154768 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index e0b1c857d..f16c33c6e 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -157428 \ No newline at end of file +157371 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 093e024b4..7153fc628 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -35,29 +35,34 @@ library SqrtPriceMath { { // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price if (amount == 0) return sqrtPX96; - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator1; + uint256 _sqrtPX96; + assembly { + numerator1 := shl(96, liquidity) + _sqrtPX96 := sqrtPX96 + } if (add) { unchecked { - uint256 product = amount * sqrtPX96; - if (product / amount == sqrtPX96) { + uint256 product = amount * _sqrtPX96; + if (UnsafeMath.div(product, amount) == _sqrtPX96) { uint256 denominator = numerator1 + product; if (denominator >= numerator1) { // always fits in 160 bits - return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + return uint160(FullMath.mulDivRoundingUp(numerator1, _sqrtPX96, denominator)); } } } // denominator is checked for overflow - return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount)); + return uint160(UnsafeMath.divRoundingUp(numerator1, UnsafeMath.div(numerator1, _sqrtPX96) + amount)); } else { unchecked { - uint256 product = amount * sqrtPX96; + uint256 product = amount * _sqrtPX96; // if the product overflows, we know the denominator underflows // in addition, we must check that the denominator does not underflow - if (product / amount != sqrtPX96 || numerator1 <= product) revert PriceOverflow(); + if (UnsafeMath.div(product, amount) != _sqrtPX96 || numerator1 <= product) revert PriceOverflow(); uint256 denominator = numerator1 - product; - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + return FullMath.mulDivRoundingUp(numerator1, _sqrtPX96, denominator).toUint160(); } } } @@ -77,27 +82,33 @@ library SqrtPriceMath { pure returns (uint160) { + uint256 _liquidity; + uint256 _sqrtPX96; + assembly { + _liquidity := liquidity + _sqrtPX96 := sqrtPX96 + } // if we're adding (subtracting), rounding down requires rounding the quotient down (up) // in both cases, avoid a mulDiv for most inputs if (add) { uint256 quotient = ( - amount <= type(uint160).max - ? (amount << FixedPoint96.RESOLUTION) / liquidity - : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + amount >> 160 == 0 + ? UnsafeMath.div((amount << FixedPoint96.RESOLUTION), _liquidity) + : FullMath.mulDiv(amount, FixedPoint96.Q96, _liquidity) ); - return (uint256(sqrtPX96) + quotient).toUint160(); + return (_sqrtPX96 + quotient).toUint160(); } else { uint256 quotient = ( - amount <= type(uint160).max - ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) - : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + amount >> 160 == 0 + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, _liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, _liquidity) ); - if (sqrtPX96 <= quotient) revert NotEnoughLiquidity(); + if (_sqrtPX96 <= quotient) revert NotEnoughLiquidity(); // always fits 160 bits unchecked { - return uint160(sqrtPX96 - quotient); + return uint160(_sqrtPX96 - quotient); } } } @@ -152,14 +163,6 @@ library SqrtPriceMath { return (a, b); } - /// @notice Directly returns two `uint256`s from two `uint160`s without implicit upcasting - function assign(uint160 a, uint160 b) internal pure returns (uint256 _a, uint256 _b) { - assembly { - _a := a - _b := b - } - } - /// @notice Gets the amount0 delta between two prices /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) @@ -184,11 +187,13 @@ library SqrtPriceMath { } } - (uint256 _sqrtPriceAX96, uint256 _sqrtPriceBX96) = assign(sqrtPriceAX96, sqrtPriceBX96); - uint256 numerator1; + uint256 _sqrtPriceAX96; + uint256 _sqrtPriceBX96; assembly { numerator1 := shl(96, liquidity) + _sqrtPriceAX96 := sqrtPriceAX96 + _sqrtPriceBX96 := sqrtPriceBX96 } uint256 numerator2 = _sqrtPriceBX96 - _sqrtPriceAX96; From a8c1cc2dd4605da8e3676ab77c617824bee5d8e2 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 26 May 2024 00:15:25 -0400 Subject: [PATCH 25/25] Use `FullMath.mulDivQ96` in `SqrtPriceMath` This commit changes the FullMath.mulDiv function call in SqrtPriceMath to FullMath.mulDivQ96. This change simplifies the code and makes it more readable, without impacting the correctness or efficiency of the operation. --- .forge-snapshots/SwapMath_oneForZero_exactInCapped.snap | 2 +- .forge-snapshots/SwapMath_oneForZero_exactInPartial.snap | 2 +- .forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap | 2 +- .forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap | 2 +- .forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap | 2 +- .forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap | 2 +- .forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap | 2 +- .forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap | 2 +- .../add liquidity to already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .forge-snapshots/addLiquidity with empty hook.snap | 2 +- .forge-snapshots/addLiquidity with native token.snap | 2 +- .../create new liquidity to a position with salt.snap | 2 +- .../getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap | 2 +- .../getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .forge-snapshots/removeLiquidity with empty hook.snap | 2 +- .forge-snapshots/removeLiquidity with native token.snap | 2 +- .../simple addLiquidity second addition same range.snap | 2 +- .forge-snapshots/simple addLiquidity.snap | 2 +- .../simple removeLiquidity some liquidity remains.snap | 2 +- .forge-snapshots/simple removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .forge-snapshots/swap CA fee on unspecified.snap | 2 +- .forge-snapshots/swap against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .forge-snapshots/swap burn 6909 for input.snap | 2 +- .forge-snapshots/swap burn native 6909 for input.snap | 2 +- .forge-snapshots/swap mint native output as 6909.snap | 2 +- .forge-snapshots/swap mint output as 6909.snap | 2 +- .forge-snapshots/swap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .forge-snapshots/swap with lp fee and protocol fee.snap | 2 +- .forge-snapshots/swap with return dynamic fee.snap | 2 +- .forge-snapshots/update dynamic fee in before swap.snap | 2 +- src/libraries/SqrtPriceMath.sol | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap index a13ad25b8..3a657e2f7 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInCapped.snap @@ -1 +1 @@ -1883 \ No newline at end of file +1853 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap index 98665bfc6..d64b2abbe 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactInPartial.snap @@ -1 +1 @@ -2259 \ No newline at end of file +2199 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap index 5b208ae28..f07ae304b 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap @@ -1 +1 @@ -1622 \ No newline at end of file +1592 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap index 159091839..a1ba83bfb 100644 --- a/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap @@ -1 +1 @@ -2666 \ No newline at end of file +2636 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap index d8f809921..465e07681 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap @@ -1 +1 @@ -1994 \ No newline at end of file +1964 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap index 2840ffd6b..8bcd06e49 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap @@ -1 +1 @@ -2754 \ No newline at end of file +2724 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap index 26cbce261..9b21444cd 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap @@ -1 +1 @@ -1733 \ No newline at end of file +1703 \ No newline at end of file diff --git a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap index 96a7bd4c8..2da3b10eb 100644 --- a/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap +++ b/.forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap @@ -1 +1 @@ -2371 \ No newline at end of file +2311 \ No newline at end of file diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index a92a5b3eb..f4c923cb5 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -149238 \ No newline at end of file +149208 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index b83e806be..7400fea6a 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -325942 \ No newline at end of file +325912 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 73d4fd34b..a635bf0dc 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -280056 \ No newline at end of file +280026 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 4e737669e..f914aa977 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -139444 \ No newline at end of file +139414 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index ac99265ac..8770a8e00 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -297416 \ No newline at end of file +297386 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index 8c19d14d4..832f62a3c 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -369 \ No newline at end of file +339 \ No newline at end of file diff --git a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index 6c8526a81..91a3d4243 100644 --- a/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -370 \ No newline at end of file +340 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 28162fd4d..402f6f0db 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -19701 \ No newline at end of file +19749 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 444553d43..4114623b7 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -181584 \ No newline at end of file +181554 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 534a0f75e..e3def1b8a 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -135873 \ No newline at end of file +135843 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index f3f4839d1..60ee7d6b3 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -115701 \ No newline at end of file +115671 \ No newline at end of file diff --git a/.forge-snapshots/simple addLiquidity second addition same range.snap b/.forge-snapshots/simple addLiquidity second addition same range.snap index 101210d25..c5313e866 100644 --- a/.forge-snapshots/simple addLiquidity second addition same range.snap +++ b/.forge-snapshots/simple addLiquidity second addition same range.snap @@ -1 +1 @@ -102728 \ No newline at end of file +102698 \ No newline at end of file diff --git a/.forge-snapshots/simple addLiquidity.snap b/.forge-snapshots/simple addLiquidity.snap index 8f89147f3..f09b3147d 100644 --- a/.forge-snapshots/simple addLiquidity.snap +++ b/.forge-snapshots/simple addLiquidity.snap @@ -1 +1 @@ -165220 \ No newline at end of file +165190 \ No newline at end of file diff --git a/.forge-snapshots/simple removeLiquidity some liquidity remains.snap b/.forge-snapshots/simple removeLiquidity some liquidity remains.snap index 8e4b9b53d..9b57c884a 100644 --- a/.forge-snapshots/simple removeLiquidity some liquidity remains.snap +++ b/.forge-snapshots/simple removeLiquidity some liquidity remains.snap @@ -1 +1 @@ -96479 \ No newline at end of file +96449 \ No newline at end of file diff --git a/.forge-snapshots/simple removeLiquidity.snap b/.forge-snapshots/simple removeLiquidity.snap index 6bf06eec7..7dc1bd736 100644 --- a/.forge-snapshots/simple removeLiquidity.snap +++ b/.forge-snapshots/simple removeLiquidity.snap @@ -1 +1 @@ -88519 \ No newline at end of file +88489 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index d03905e6c..9475c7855 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -114834 \ No newline at end of file +114774 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index eff7344f1..857c298a8 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -129999 \ No newline at end of file +129939 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index 1d56a2b32..764696bb8 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -179437 \ No newline at end of file +179377 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 65c7afb44..87e4b5104 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -110200 \ No newline at end of file +110170 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 19994500c..292cd08c1 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -121547 \ No newline at end of file +121517 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index f2a37ca04..09385471a 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -133398 \ No newline at end of file +133368 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index f0ffb8d7f..fc07957c6 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -122698 \ No newline at end of file +122638 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 4ed36d70c..053fd559f 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -144442 \ No newline at end of file +144382 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index e3fa1df5c..e6cf6aa48 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -161071 \ No newline at end of file +161011 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 3c9f6fb0a..c698170e3 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -217402 \ No newline at end of file +217312 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index 804fadbbe..5f16b0345 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -145272 \ No newline at end of file +145212 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index e5adcf119..89226a288 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -138547 \ No newline at end of file +138517 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index e30fc66ad..656bcbcb3 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -176568 \ No newline at end of file +176478 \ No newline at end of file diff --git a/.forge-snapshots/swap with return dynamic fee.snap b/.forge-snapshots/swap with return dynamic fee.snap index fc98d5b1c..290f2fe90 100644 --- a/.forge-snapshots/swap with return dynamic fee.snap +++ b/.forge-snapshots/swap with return dynamic fee.snap @@ -1 +1 @@ -152308 \ No newline at end of file +152248 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index dcc1fee30..706de29e7 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -154950 \ No newline at end of file +154890 \ No newline at end of file diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 21a29872d..e60eef9bd 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -273,7 +273,7 @@ library SqrtPriceMath { * : FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96); * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. */ - amount1 = FullMath.mulDiv(_liquidity, numerator, denominator); + amount1 = FullMath.mulDivQ96(_liquidity, numerator); assembly { amount1 := add(amount1, and(gt(mulmod(_liquidity, numerator, denominator), 0), roundUp)) }