Skip to content

Commit

Permalink
test: add sepolia mixed quoter test against newly deployed pool manager
Browse files Browse the repository at this point in the history
  • Loading branch information
jsy1218 committed Sep 13, 2024
1 parent efebe80 commit 9a05c38
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 10 deletions.
3 changes: 1 addition & 2 deletions src/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 4 additions & 5 deletions src/libraries/Path.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down
5 changes: 4 additions & 1 deletion test/MixedRouteQuoterV2OnMainnet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
109 changes: 109 additions & 0 deletions test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 9a05c38

Please sign in to comment.