From 9a05c380135afd0e3d1b94535335bb49e82dd2af Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:11:13 -0700 Subject: [PATCH] test: add sepolia mixed quoter test against newly deployed pool manager --- src/libraries/Constants.sol | 3 +- src/libraries/Path.sol | 9 +- test/MixedRouteQuoterV2OnMainnet.t.sol | 5 +- ...outeQuoterV2OnSepoliaOldPoolManager.t.sol} | 6 +- ...rQuoterV2OnSepoliaCurrentPoolManager.t.sol | 109 ++++++++++++++++++ 5 files changed, 122 insertions(+), 10 deletions(-) rename test/{MixedRouteQuoterV2OnSepolia.t.sol => MixedRouteQuoterV2OnSepoliaOldPoolManager.t.sol} (97%) create mode 100644 test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index ded3c16..0e7622a 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -57,8 +57,7 @@ library Constants { uint8 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE; /// @dev The offset of a single token address (20) and pool fee (3) + tick spacing (3) + hooks address (20) = 46 - uint8 internal constant NEXT_V4_POOL_OFFSET = - ADDR_SIZE + V4_FEE_SIZE + TICK_SPACING_SIZE + ADDR_SIZE; + uint8 internal constant NEXT_V4_POOL_OFFSET = ADDR_SIZE + V4_FEE_SIZE + TICK_SPACING_SIZE + ADDR_SIZE; /// @dev The offset of a single token address (20) and pool fee (3) + tick spacing (3) + hooks address (20) + token address (20) = 66 uint8 internal constant V4_POP_OFFSET = NEXT_V4_POOL_OFFSET + ADDR_SIZE; diff --git a/src/libraries/Path.sol b/src/libraries/Path.sol index ba02f60..569cfa9 100644 --- a/src/libraries/Path.sol +++ b/src/libraries/Path.sol @@ -10,13 +10,15 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; /// @title Functions for manipulating path data for multihop swaps library Path { using BytesLib for bytes; + error InvalidPoolVersion(uint24 poolVersion); /// @notice Decodes the first pool in path /// @param path The bytes encoded swap path function decodePoolVersion(bytes memory path) internal pure returns (uint8 poolVersion) { if (path.length < Constants.ADDR_SIZE) revert BytesLib.SliceOutOfBounds(); - poolVersion = (toUint8(path, Constants.ADDR_SIZE) & Constants.POOL_VERSION_BITMASK) >> Constants.POOL_VERSION_BITMASK_SHIFT; + poolVersion = (toUint8(path, Constants.ADDR_SIZE) & Constants.POOL_VERSION_BITMASK) + >> Constants.POOL_VERSION_BITMASK_SHIFT; } /// @notice Decodes the first pool in path @@ -57,10 +59,7 @@ library Path { tokenIn = toAddress(path, 0); fee = (toUint24(path, Constants.ADDR_SIZE) << Constants.FEE_SHIFT) >> Constants.FEE_SHIFT; tickSpacing = toUint24(path, Constants.ADDR_SIZE + Constants.V4_FEE_SIZE); - hooks = toAddress( - path, - Constants.ADDR_SIZE + Constants.V4_FEE_SIZE + Constants.TICK_SPACING_SIZE - ); + hooks = toAddress(path, Constants.ADDR_SIZE + Constants.V4_FEE_SIZE + Constants.TICK_SPACING_SIZE); tokenOut = toAddress(path, Constants.NEXT_V4_POOL_OFFSET); } diff --git a/test/MixedRouteQuoterV2OnMainnet.t.sol b/test/MixedRouteQuoterV2OnMainnet.t.sol index b638f3d..b1517f4 100644 --- a/test/MixedRouteQuoterV2OnMainnet.t.sol +++ b/test/MixedRouteQuoterV2OnMainnet.t.sol @@ -78,6 +78,9 @@ contract MixedRouteQuoterV2TestOnMainnet is Test { // Due to mixed quoter v2 having more compact pool version + fee tier encoding for v2, // Overall gas cost of mixed quoter v2 should always be less than mixed quoter v1 assertLt(swapGasEstimateV2, v3SwapGasEstimate); - assertLt(gasBeforeQuoteMixedQuoterV2 - gasAfterQuoteMixedQuoterV2, gasBeforeQuoteMixedQuoterV1 - gasAfterQuoteMixedQuoterV1); + assertLt( + gasBeforeQuoteMixedQuoterV2 - gasAfterQuoteMixedQuoterV2, + gasBeforeQuoteMixedQuoterV1 - gasAfterQuoteMixedQuoterV1 + ); } } diff --git a/test/MixedRouteQuoterV2OnSepolia.t.sol b/test/MixedRouteQuoterV2OnSepoliaOldPoolManager.t.sol similarity index 97% rename from test/MixedRouteQuoterV2OnSepolia.t.sol rename to test/MixedRouteQuoterV2OnSepoliaOldPoolManager.t.sol index d45b42f..906fa20 100644 --- a/test/MixedRouteQuoterV2OnSepolia.t.sol +++ b/test/MixedRouteQuoterV2OnSepoliaOldPoolManager.t.sol @@ -13,7 +13,9 @@ import {Constants} from "../src/libraries/Constants.sol"; import {Quoter} from "v4-periphery/src/lens/Quoter.sol"; import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol"; -contract MixedRouteQuoterV2TestOnSepolia is Test { +// Feel free to delete this old manager manager test file, if we were to go ahead with the quoter refactoring https://github.com/Uniswap/v4-periphery/pull/349 +// The amount of syntax change is not worth the effort to keep this test file. And we already thoroughly tested. +contract MixedRouteQuoterV2TestOnSepoliaOldPoolMananger is Test { IMixedRouteQuoterV2 public mixedRouteQuoterV2; IQuoter public quoter; IPoolManager public poolManager; @@ -257,7 +259,7 @@ contract MixedRouteQuoterV2TestOnSepolia is Test { uint8 GRT_USDC_poolVersions = uint8(4); uint24 GRT_USDC_fee = 500; uint24 GRT_USDC_encodedFee = (uint24(GRT_USDC_poolVersions) << v4FeeShift) + GRT_USDC_fee; - uint24 GRT_USDC_tickspacing = 10; + uint24 GRT_USDC_tickspacing = 10; address GRT_USDC_hooks = address(0); bytes memory GRT_USDC_hookData = "0x"; IMixedRouteQuoterV2.NonEncodableData[] memory nonEncodableData = new IMixedRouteQuoterV2.NonEncodableData[](2); diff --git a/test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol b/test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol new file mode 100644 index 0000000..87cdfd6 --- /dev/null +++ b/test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IQuoter} from "@uniswap/v4-periphery/src/interfaces/IQuoter.sol"; +import {IMixedRouteQuoterV2} from "../src/interfaces/IMixedRouteQuoterV2.sol"; +import {MixedRouteQuoterV2} from "../src/MixedRouteQuoterV2.sol"; +import {Quoter} from "v4-periphery/src/lens/Quoter.sol"; +import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; + +contract MixedRouteQuoterV2TestOnSepolia is Test { + IMixedRouteQuoterV2 public mixedRouteQuoterV2; + IQuoter public quoter; + IPoolManager public poolManager; + + address public immutable uniswapV4PoolManager = 0xE8E23e97Fa135823143d6b9Cba9c699040D51F70; + address public immutable uniswapV3PoolFactory = 0x0227628f3F023bb0B980b67D528571c95c6DaC1c; + address public immutable uniswapV2PoolFactory = 0xB7f907f7A9eBC822a80BD25E224be42Ce0A698A0; + + address V4_SEPOLIA_A_ADDRESS = 0x0275C79896215a790dD57F436E1103D4179213be; + address V4_SEPOLIA_B_ADDRESS = 0x1a6990C77cFBBA398BeB230Dd918e28AAb71EEC2; + uint8 public v4FeeShift = 20; + + function setUp() public { + vm.createSelectFork(vm.envString("SEPOLIA_RPC_URL")); + poolManager = IPoolManager(uniswapV4PoolManager); + mixedRouteQuoterV2 = new MixedRouteQuoterV2(poolManager, uniswapV3PoolFactory, uniswapV2PoolFactory); + quoter = new Quoter(poolManager); + } + + function test_FuzzQuoteExactInput_ZeroForOneTrue(uint256 amountIn) public { + // make the tests mean something (a non-small input) bc otherwise everything rounds to 0 + vm.assume(amountIn > 10000); + vm.assume(amountIn < 10000000000000000); + + uint24 fee = 3000; + uint24 tickSpacing = 60; + address hooks = address(0); + bytes memory hookData = "0x"; + IMixedRouteQuoterV2.NonEncodableData[] memory nonEncodableData = new IMixedRouteQuoterV2.NonEncodableData[](1); + nonEncodableData[0] = (IMixedRouteQuoterV2.NonEncodableData({hookData: hookData})); + + // bytes memory path = abi.encodePacked(V4_SEPOLIA_OP_ADDRESS, fee,tickSpacing, hooks, V4_SEPOLIA_USDC_ADDRESS); + IMixedRouteQuoterV2.ExtraQuoteExactInputParams memory extraParams = + IMixedRouteQuoterV2.ExtraQuoteExactInputParams({nonEncodableData: nonEncodableData}); + uint8 poolVersions = uint8(4); + uint24 encodedFee = (uint24(poolVersions) << v4FeeShift) + fee; + bytes memory path = abi.encodePacked(V4_SEPOLIA_A_ADDRESS, encodedFee, tickSpacing, hooks, V4_SEPOLIA_B_ADDRESS); + + ( + uint256 amountOut, + uint160[] memory sqrtPriceX96After, + uint32[] memory initializedTicksLoaded, + uint256 gasEstimate + ) = mixedRouteQuoterV2.quoteExactInput(path, extraParams, amountIn); + + assertGt(gasEstimate, 0); + + PathKey[] memory exactInPathKey = new PathKey[](1); + exactInPathKey[0] = PathKey({ + intermediateCurrency: Currency.wrap(V4_SEPOLIA_B_ADDRESS), + fee: fee, + tickSpacing: int24(tickSpacing), + hooks: IHooks(hooks), + hookData: hookData + }); + + IQuoter.QuoteExactParams memory exactInParams = IQuoter.QuoteExactParams({ + exactCurrency: Currency.wrap(V4_SEPOLIA_A_ADDRESS), + path: exactInPathKey, + exactAmount: uint128(amountIn) + }); + + ( + int128[] memory expectedDeltaAmounts, + uint160[] memory expectedSqrtPriceX96After, + uint32[] memory expectedInitializedTicksLoaded + ) = quoter.quoteExactInput(exactInParams); + + uint256 expectedAmountOut = uint256(uint128(-expectedDeltaAmounts[exactInPathKey.length])); // negate the final delta amount out + assertEqUint(amountOut, expectedAmountOut); + assertEqUint(sqrtPriceX96After[0], expectedSqrtPriceX96After[0]); + assertEqUint(initializedTicksLoaded[0], expectedInitializedTicksLoaded[0]); + + // mixed quoter doesn't support exact out by design, but we can cross check the final amount in will equate the original input amount in, + // if we call the v4 quoter for the exact out quote. v3 and v4 quoter support exact out quote + PathKey[] memory exactOutPathKey = new PathKey[](1); + exactOutPathKey[0] = PathKey({ + intermediateCurrency: Currency.wrap(V4_SEPOLIA_A_ADDRESS), + fee: fee, + tickSpacing: int24(tickSpacing), + hooks: IHooks(hooks), + hookData: hookData + }); + + IQuoter.QuoteExactParams memory exactOutParams = IQuoter.QuoteExactParams({ + exactCurrency: Currency.wrap(V4_SEPOLIA_B_ADDRESS), + path: exactOutPathKey, + exactAmount: uint128(expectedAmountOut) + }); + + (int128[] memory expectedDeltaAmountsIn,,) = quoter.quoteExactOutput(exactOutParams); + uint256 expectedAmountIn = uint256(uint128(expectedDeltaAmountsIn[0])); // final delta amount in is the positive amount in the first array element + assertApproxEqAbs(amountIn, expectedAmountIn, 1); + } +}