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

fix: dynamic fee incorrect encoding #40

Merged
merged 2 commits into from
Dec 12, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"devDependencies": {
"@uniswap/swap-router-contracts": "^1.3.0",
"@uniswap/v3-periphery": "^1.4.4",
"hardhat": "^2.6.8"
},
"license": "GPL-2.0-or-later",
Expand Down
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@uniswap/v2-core/=node_modules/@uniswap/v2-core/
@uniswap/v2-periphery/=node_modules/@uniswap/v2-periphery/
@uniswap/v3-core/=node_modules/@uniswap/v3-core/
@uniswap/v3-periphery/=node_modules/@uniswap/v3-periphery/
@uniswap/v4-core/=lib/v4-core/
@uniswap/v4-periphery/=lib/v4-periphery/
@uniswap/universal-router/=node_modules/@uniswap/universal-router/
Expand Down
11 changes: 9 additions & 2 deletions src/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ library Constants {

/// @dev fee bit shift to recover the fee, applicable to v3 v4 LP fees
/// left shift 4 bits, and then right shift 4 bits
/// for example, a v4 fee 100% is 1000000, or 010011110100001001000000
/// (010011110100001001000000 << 4) >> 4 = 000011110100001001000000
/// for example, a v3 fee 0.3% is 3000, or 101110111000
/// (101110111000 << 4) >> 4 = 000000000000101110111000
uint8 internal constant FEE_SHIFT = 4;

/// @dev fee bitmask to recover the fee, applicable to v4 LP fees
/// for example, a v4 fee 100% is 1000000, or 000011110100001001000000
/// 000011110100001001000000 & 100011111111111111111111 = 000011110100001001000000
/// if v4 fee is dynamic, then it's 100000000000000000000000
/// 100000000000000000000000 & 100011111111111111111111 = 100000000000000000000000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would love a comment explaining what the 3 0s are for to explain the mask more clearly

uint24 internal constant V4_FEE_BITMASK = 0x8FFFFF; // 100011111111111111111111

/// @dev The length of the bytes encoded address
uint8 internal constant ADDR_SIZE = 20;

Expand Down
2 changes: 1 addition & 1 deletion src/libraries/Path.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ library Path {
{
if (path.length < Constants.V4_POP_OFFSET) revert BytesLib.SliceOutOfBounds();
tokenIn = path.toAddress(0);
fee = (path.toUint24(Constants.ADDR_SIZE) << Constants.FEE_SHIFT) >> Constants.FEE_SHIFT;
fee = path.toUint24(Constants.ADDR_SIZE) & Constants.V4_FEE_BITMASK;
tickSpacing = path.toUint24(Constants.ADDR_SIZE + Constants.V4_FEE_SIZE);
hooks = path.toAddress(Constants.ADDR_SIZE + Constants.V4_FEE_SIZE + Constants.TICK_SPACING_SIZE);
tokenOut = path.toAddress(Constants.NEXT_V4_POOL_OFFSET);
Expand Down
79 changes: 70 additions & 9 deletions test/MixedRouterQuoterV2OnSepoliaCurrentPoolManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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 {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol";

import {IMixedRouteQuoterV2} from "../src/interfaces/IMixedRouteQuoterV2.sol";
import {MixedRouteQuoterV2} from "../src/MixedRouteQuoterV2.sol";
import {Quoter} from "v4-periphery/src/lens/Quoter.sol";
Expand All @@ -12,28 +14,34 @@ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";

contract MixedRouteQuoterV2TestOnSepolia is Test {
IQuoterV2 public v3QuoterV2;
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 public immutable v3QuoterV2Address = 0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3;

address V4_NATIVE_ADDRESS = address(0);
address SEPOLIA_WETH_ADDRESS = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14;
address SEPOLIA_USDC_ADDRESS = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;
address SEPOLIA_UNI_ADDRESS = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;

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);
v3QuoterV2 = IQuoterV2(v3QuoterV2Address);
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 > 1000000000);
vm.assume(amountIn < 10000000000000000);

uint24 fee = 3000;
Expand All @@ -48,23 +56,23 @@ contract MixedRouteQuoterV2TestOnSepolia is Test {
IMixedRouteQuoterV2.ExtraQuoteExactInputParams({nonEncodableData: nonEncodableData});
uint8 protocolVersion = uint8(4);
uint24 encodedFee = (uint24(protocolVersion) << v4FeeShift) + fee;
bytes memory path = abi.encodePacked(V4_SEPOLIA_A_ADDRESS, encodedFee, tickSpacing, hooks, V4_SEPOLIA_B_ADDRESS);
bytes memory path = abi.encodePacked(SEPOLIA_WETH_ADDRESS, encodedFee, tickSpacing, hooks, SEPOLIA_USDC_ADDRESS);

(uint256 amountOut, 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),
intermediateCurrency: Currency.wrap(SEPOLIA_USDC_ADDRESS),
fee: fee,
tickSpacing: int24(tickSpacing),
hooks: IHooks(hooks),
hookData: hookData
});

IQuoter.QuoteExactParams memory exactInParams = IQuoter.QuoteExactParams({
exactCurrency: Currency.wrap(V4_SEPOLIA_A_ADDRESS),
exactCurrency: Currency.wrap(SEPOLIA_WETH_ADDRESS),
path: exactInPathKey,
exactAmount: uint128(amountIn)
});
Expand All @@ -77,20 +85,73 @@ contract MixedRouteQuoterV2TestOnSepolia is Test {
// 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),
intermediateCurrency: Currency.wrap(SEPOLIA_WETH_ADDRESS),
fee: fee,
tickSpacing: int24(tickSpacing),
hooks: IHooks(hooks),
hookData: hookData
});

IQuoter.QuoteExactParams memory exactOutParams = IQuoter.QuoteExactParams({
exactCurrency: Currency.wrap(V4_SEPOLIA_B_ADDRESS),
exactCurrency: Currency.wrap(SEPOLIA_USDC_ADDRESS),
path: exactOutPathKey,
exactAmount: uint128(expectedAmountOut)
});

(uint256 expectedAmountIn,) = quoter.quoteExactOutput(exactOutParams);
assertApproxEqAbs(amountIn, expectedAmountIn, 1);

// V4 quoter exact out cannot get back to the original amountIn, but seems to be v4 quoter issue only
// https://app.warp.dev/block/1EWePTvdTYQ2XPmyDkJu38
// assertApproxEqAbs(amountIn, expectedAmountIn, 1);
}

function test_FuzzQuoteExactInput_MultiTokenPath_NoFOT(uint256 amountIn) public {
// make the tests mean something (a non-small input) bc otherwise everything rounds to 0
vm.assume(amountIn > 1000000000);
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[](2);
nonEncodableData[0] = (IMixedRouteQuoterV2.NonEncodableData({hookData: hookData}));
nonEncodableData[1] = (IMixedRouteQuoterV2.NonEncodableData({hookData: hookData}));

// bytes memory path = abi.encodePacked(V4_SEPOLIA_OP_ADDRESS, fee,tickSpacing, hooks, V4_SEPOLIA_USDC_ADDRESS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

IMixedRouteQuoterV2.ExtraQuoteExactInputParams memory extraParams =
IMixedRouteQuoterV2.ExtraQuoteExactInputParams({nonEncodableData: nonEncodableData});
uint8 protocolVersion = uint8(4);
uint24 encodedFee = (uint24(protocolVersion) << v4FeeShift) + fee;
uint8 v3ProtocolVersion = uint8(3);
uint24 encodedV3Fee = (uint24(v3ProtocolVersion) << v4FeeShift) + fee;
console.log(encodedV3Fee);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

bytes memory path = abi.encodePacked(SEPOLIA_WETH_ADDRESS, encodedFee, tickSpacing, hooks, SEPOLIA_USDC_ADDRESS, encodedV3Fee, SEPOLIA_UNI_ADDRESS);

(uint256 amountOut, uint256 gasEstimate) = mixedRouteQuoterV2.quoteExactInput(path, extraParams, amountIn);

assertGt(gasEstimate, 0);

PathKey[] memory exactInPathKey = new PathKey[](1);
exactInPathKey[0] = PathKey({
intermediateCurrency: Currency.wrap(SEPOLIA_USDC_ADDRESS),
fee: fee,
tickSpacing: int24(tickSpacing),
hooks: IHooks(hooks),
hookData: hookData
});

IQuoter.QuoteExactParams memory exactInParams = IQuoter.QuoteExactParams({
exactCurrency: Currency.wrap(SEPOLIA_WETH_ADDRESS),
path: exactInPathKey,
exactAmount: uint128(amountIn)
});

(uint256 intermediateAmountOut,) = quoter.quoteExactInput(exactInParams);

bytes memory v3QuoterV2Path = abi.encodePacked(SEPOLIA_USDC_ADDRESS, fee, SEPOLIA_UNI_ADDRESS);
(uint256 expectedAmountOut,,,) = v3QuoterV2.quoteExactInput(v3QuoterV2Path, intermediateAmountOut);

assertEqUint(amountOut, expectedAmountOut);
}
}