Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

test: add sepolia mixed quoter test against newly deployed pool manager #36

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}