From 7bb3dc4889ad61d9a5db2ad35bcf112ac1a87f69 Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Tue, 16 Sep 2025 18:07:17 +0200 Subject: [PATCH 1/9] [WIP] add L4 -> ETH test swaps with and without fees --- .../src/uniswap/quote/getUniswapRoute.test.ts | 139 +++++++++++++++++- .../src/uniswap/routerCommands.ts | 34 +++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts index 3dccff58..667a1a44 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts @@ -10,7 +10,7 @@ import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; import { getUniswapV4Address } from "../../currency/currency.js"; import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; -import { CommandType, RoutePlanner } from "../routerCommands.js"; +import { CommandType, getCommandName, RoutePlanner } from "../routerCommands.js"; import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; @@ -414,6 +414,143 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount }); + + test.only("L4 -> ETH", async () => { + const currencyIn = tokenL4; + const currencyOut = zeroAddress; // ETH + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost + }); + + test("L4 -> ETH with Veraswap and referral fee", async () => { + const currencyIn = tokenL4; + const currencyOut = zeroAddress; // ETH + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 0n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount * 2n; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + + const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; + const referralFeeRecipient = { address: referralAddress, bips: feeBips }; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + referralFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); }); describe("V3 Single", () => { diff --git a/packages/veraswap-sdk/src/uniswap/routerCommands.ts b/packages/veraswap-sdk/src/uniswap/routerCommands.ts index 880fbda0..153017d4 100644 --- a/packages/veraswap-sdk/src/uniswap/routerCommands.ts +++ b/packages/veraswap-sdk/src/uniswap/routerCommands.ts @@ -326,3 +326,37 @@ export function createCommand( return { type, encodedInput: parameters[0] as any }; } } + +const commandTypeString = { + [CommandType.V3_SWAP_EXACT_IN]: "V3_SWAP_EXACT_IN", + [CommandType.V3_SWAP_EXACT_OUT]: "V3_SWAP_EXACT_OUT", + [CommandType.PERMIT2_TRANSFER_FROM]: "PERMIT2_TRANSFER_FROM", + [CommandType.PERMIT2_PERMIT_BATCH]: "PERMIT2_PERMIT_BATCH", + [CommandType.SWEEP]: "SWEEP", + [CommandType.TRANSFER]: "TRANSFER", + [CommandType.PAY_PORTION]: "PAY_PORTION", + + [CommandType.V2_SWAP_EXACT_IN]: "V2_SWAP_EXACT_IN", + [CommandType.V2_SWAP_EXACT_OUT]: "V2_SWAP_EXACT_OUT", + [CommandType.PERMIT2_PERMIT]: "PERMIT2_PERMIT", + [CommandType.WRAP_ETH]: "WRAP_ETH", + [CommandType.UNWRAP_WETH]: "UNWRAP_WETH", + [CommandType.PERMIT2_TRANSFER_FROM_BATCH]: "PERMIT2_TRANSFER_FROM_BATCH", + [CommandType.BALANCE_CHECK_ERC20]: "BALANCE_CHECK_ERC20", + + [CommandType.V4_SWAP]: "V4_SWAP", + [CommandType.V3_POSITION_MANAGER_PERMIT]: "V3_POSITION_MANAGER_PERMIT", + [CommandType.V3_POSITION_MANAGER_CALL]: "V3_POSITION_MANAGER_CALL", + [CommandType.V4_INITIALIZE_POOL]: "V4_INITIALIZE_POOL", + [CommandType.V4_POSITION_MANAGER_CALL]: "V4_POSITION_MANAGER_CALL", + + [CommandType.EXECUTE_SUB_PLAN]: "EXECUTE_SUB_PLAN", + + // Custom Commands + [CommandType.CALL_TARGET]: "CALL_TARGET", +}; + +// First part of the keys is the numbered indices, second part is the names +export function getCommandName(command: CommandType): string { + return commandTypeString[command]; +} From 2686a16066e4bd6f54c095779afaba4a314c2aaa Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Thu, 18 Sep 2025 19:24:30 +0200 Subject: [PATCH 2/9] Formatting CommandsBuilderV4Test.s.sol --- .../CommandsBuilderV4Test.s.sol | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/veraswap-sdk/test/CommandsBuilder/CommandsBuilderV4Test.s.sol b/packages/veraswap-sdk/test/CommandsBuilder/CommandsBuilderV4Test.s.sol index 5ad7afa6..8849b802 100644 --- a/packages/veraswap-sdk/test/CommandsBuilder/CommandsBuilderV4Test.s.sol +++ b/packages/veraswap-sdk/test/CommandsBuilder/CommandsBuilderV4Test.s.sol @@ -23,7 +23,6 @@ contract CommandsBuilderV4Test is UniswapBaseTest { super.setUp(); } - /***** V4 *****/ // A -> B function test_V4_A_B() public { PoolUtils.createV4Pool(tokenA, tokenB, v4PositionManager, 10 ether); @@ -145,13 +144,8 @@ contract CommandsBuilderV4Test is UniswapBaseTest { // Currency (Currency currencyIn, Currency currencyOut) = (tokenA, weth9); PathKey[] memory path = new PathKey[](1); - path[0] = PathKey({ - intermediateCurrency: eth, - fee: 3000, - tickSpacing: 60, - hooks: IHooks(address(0)), - hookData: "" - }); + path[0] = + PathKey({intermediateCurrency: eth, fee: 3000, tickSpacing: 60, hooks: IHooks(address(0)), hookData: ""}); (bytes memory commands, bytes[] memory commandInputs) = CommandsBuilderLibrary.getSwapExactInCommands( weth9, @@ -225,13 +219,8 @@ contract CommandsBuilderV4Test is UniswapBaseTest { // Currency (Currency currencyIn, Currency currencyOut) = (tokenA, tokenB); PathKey[] memory path = new PathKey[](2); - path[0] = PathKey({ - intermediateCurrency: liq4, - fee: 3000, - tickSpacing: 60, - hooks: IHooks(address(0)), - hookData: "" - }); + path[0] = + PathKey({intermediateCurrency: liq4, fee: 3000, tickSpacing: 60, hooks: IHooks(address(0)), hookData: ""}); path[1] = PathKey({ intermediateCurrency: currencyOut, fee: 3000, From f7a81a8e03882e50a26e802bfac15a70fb21e2cf Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Thu, 18 Sep 2025 19:24:53 +0200 Subject: [PATCH 3/9] [WIP] move out V4 router commands test, continue testing eth / weth swap --- .../src/uniswap/quote/getUniswapRoute.test.ts | 519 +----------- .../uniswap/quote/getUniswapRouteV4.test.ts | 775 ++++++++++++++++++ 2 files changed, 795 insertions(+), 499 deletions(-) create mode 100644 packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts index 667a1a44..2aec64b3 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts @@ -10,7 +10,7 @@ import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; import { getUniswapV4Address } from "../../currency/currency.js"; import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; -import { CommandType, getCommandName, RoutePlanner } from "../routerCommands.js"; +import { RoutePlanner } from "../routerCommands.js"; import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; @@ -52,10 +52,10 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { // Helper function to get balance of a token const getBalance = (token: Address) => getBalanceForAddress(token, anvilClientL1.account.address); - describe("V4 Single", () => { - test("A -> L4", async () => { + describe("V3 Single", () => { + test("A -> L3", async () => { const currencyIn = tokenA; - const currencyOut = tokenL4; + const currencyOut = tokenL3; const currencyHops: Address[] = []; const contracts = { @@ -70,7 +70,10 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { currencyOut, currencyHops, amountIn, - contracts, + contracts: { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }, }); expect(route).toBeDefined(); const { quote, amountOut, value } = route!; @@ -98,324 +101,7 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount }); - test("PERMIT2_TRANSFER_FROM command", async () => { - const currencyIn = tokenA; - - const veraswapFeeRecipient = "0x000000000000000000000000000000000000beef" as Address; - const feeRecipientBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapFeeRecipient); - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - - const feeAmount = 1000n; - - const routePlanner = new RoutePlanner(); - routePlanner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [currencyIn, veraswapFeeRecipient, feeAmount]); - - //Execute - const value = 0n; - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const feeRecipientBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapFeeRecipient); - - // Input balance decreased by exact amount - expect(currencyInBalanceBeforeSwap - currencyInBalanceAfterSwap).toBe(feeAmount); - // Input balance increased by exact amount - expect(feeRecipientBalanceAfterSwap - feeRecipientBalanceBeforeSwap).toBe(feeAmount); - }); - - test("A -> L4 with Veraswap fee", async () => { - const currencyIn = tokenA; - const currencyOut = tokenL4; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - const feeBips = 100n; // 1% referral fee - const bipsDenominator = 10000n; // 100% = 10000 bips - const feeAmount = (amountIn * feeBips) / bipsDenominator; - const amountInWithoutFee = amountIn - feeAmount; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn: amountInWithoutFee, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; - const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - veraswapFeeRecipient, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - }); - - test("A -> L4 with Veraswap and referral fee", async () => { - const currencyIn = tokenA; - const currencyOut = tokenL4; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - const feeBips = 100n; // 1% referral fee - const bipsDenominator = 10000n; // 100% = 10000 bips - const feeAmount = (amountIn * feeBips) / bipsDenominator; - const amountInWithoutFee = amountIn - feeAmount * 2n; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn: amountInWithoutFee, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; - const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; - - const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; - const referralFeeRecipient = { address: referralAddress, bips: feeBips }; - - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - veraswapFeeRecipient, - referralFeeRecipient, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - }); - - test("ETH -> L4 with Veraswap fee", async () => { - const currencyIn = zeroAddress; // ETH - const currencyOut = tokenL4; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - const feeBips = 100n; // 1% referral fee - const bipsDenominator = 10000n; // 100% = 10000 bips - const feeAmount = (amountIn * feeBips) / bipsDenominator; - const amountInWithoutFee = amountIn - feeAmount; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn: amountInWithoutFee, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut } = route!; - - const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; - const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - veraswapFeeRecipient, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, // need to pass full amount in for ETH, value is amount after fees - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCost = gasUsed * effectiveGasPrice; - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn - gasCost); // Input balance decreased by exact amount and gas cost - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - }); - - test("ETH -> L4 with Veraswap and referral fee", async () => { - const currencyIn = zeroAddress; // ETH - const currencyOut = tokenL4; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - const feeBips = 100n; // 1% referral fee - const bipsDenominator = 10000n; // 100% = 10000 bips - const feeAmount = (amountIn * feeBips) / bipsDenominator; - const amountInWithoutFee = amountIn - feeAmount * 2n; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn: amountInWithoutFee, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut } = route!; - - const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; - const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; - - const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; - const referralFeeRecipient = { address: referralAddress, bips: feeBips }; - - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - veraswapFeeRecipient, - referralFeeRecipient, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); - - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCost = gasUsed * effectiveGasPrice; - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn - gasCost); // Input balance decreased by exact amount and gas cost - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - }); - - test.only("L4 -> ETH", async () => { + test.skip("L4 -> ETH", async () => { const currencyIn = tokenL4; const currencyOut = zeroAddress; // ETH const currencyHops: Address[] = []; @@ -448,84 +134,9 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { const routePlanner = new RoutePlanner(); addCommandsToRoutePlanner(routePlanner, commands); - console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCost = gasUsed * effectiveGasPrice; - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost - }); - - test("L4 -> ETH with Veraswap and referral fee", async () => { - const currencyIn = tokenL4; - const currencyOut = zeroAddress; // ETH - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - const feeBips = 0n; // 1% referral fee - const bipsDenominator = 10000n; // 100% = 10000 bips - const feeAmount = (amountIn * feeBips) / bipsDenominator; - const amountInWithoutFee = amountIn - feeAmount * 2n; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn: amountInWithoutFee, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut } = route!; - - const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; - const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; - - const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; - const referralFeeRecipient = { address: referralAddress, bips: feeBips }; - - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - veraswapFeeRecipient, - referralFeeRecipient, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - //Execute const currencyInBalanceBeforeSwap = await getBalance(currencyIn); const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); const hash = await anvilClientL1.writeContract({ @@ -536,11 +147,19 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { args: [routePlanner.commands, routePlanner.inputs, deadline], }); const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + // console.log( + // receipt.logs.map((l) => ({ + // address: l.address, + // ...decodeEventLog({ + // abi: events, + // data: l.data, + // topics: l.topics, + // }), + // })), + // ); const currencyInBalanceAfterSwap = await getBalance(currencyIn); const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); - const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); const gasUsed = receipt.gasUsed; const effectiveGasPrice = receipt.effectiveGasPrice; @@ -548,105 +167,7 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost - expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount - }); - }); - - describe("V3 Single", () => { - test("A -> L3", async () => { - const currencyIn = tokenA; - const currencyOut = tokenL3; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn, - contracts: { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - }); - }); - - describe("V4 Multihop", () => { - test("A -> L4 -> B", async () => { - const currencyIn = tokenA; - const currencyOut = tokenB; - const currencyHops = [tokenL4]; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost }); }); diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts new file mode 100644 index 00000000..9f375617 --- /dev/null +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts @@ -0,0 +1,775 @@ +import { QueryClient } from "@tanstack/react-query"; +import { Actions, V4Planner } from "@uniswap/v4-sdk"; +import { Address, decodeEventLog, parseAbi, parseUnits, zeroAddress } from "viem"; +import { describe, expect, test } from "vitest"; + +import { events } from "../../artifacts/events.js"; +import { IERC20 } from "../../artifacts/IERC20.js"; +import { IUniversalRouter } from "../../artifacts/IUniversalRouter.js"; +import { opChainL1, opChainL1Client } from "../../chains/supersim.js"; +import { LOCAL_CURRENCIES } from "../../constants/tokens.js"; +import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; +import { getUniswapV4Address } from "../../currency/currency.js"; +import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; +import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; +import { + ACTION_CONSTANTS, + CommandType, + CreateCommandParamsGeneric, + getCommandName, + RoutePlanner, +} from "../routerCommands.js"; + +import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; + +//TODO: Add deeper tests with ETH wrap/unwrap +describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { + const queryClient = new QueryClient(); + // Uniswap Error Abi + const UniswapErrorAbi = parseAbi([ + "error DeltaNotNegative(address)", + "error DeltaNotPositive(address)", + "error CurrencyNotSettled()", + "error PoolNotInitialized()", + "error V3TooLittleReceived()", + "error SwapAmountCannotBeZero()", + ]); + + const amountIn = parseUnits("0.01", 18); + + // A/ETH, B/ETH Pools Exist + // A/B Pool Does Not Exist + const tokenA = getUniswapV4Address(LOCAL_CURRENCIES[0]); // 2 A tokens after this (Hyperlane) + const tokenB = getUniswapV4Address(LOCAL_CURRENCIES[3]); // 2 B tokens after this (Hyperlane) + const tokenL3 = getUniswapV4Address(LOCAL_CURRENCIES[7]); + const tokenL4 = getUniswapV4Address(LOCAL_CURRENCIES[8]); + + const getBalanceForAddress = function (token: Address, address: Address) { + if (token === zeroAddress) return opChainL1Client.getBalance({ address }); + + return opChainL1Client.readContract({ + address: token, + abi: IERC20.abi, + functionName: "balanceOf", + args: [address], + }); + }; + + // Helper function to get balance of a token + const getBalance = (token: Address) => getBalanceForAddress(token, anvilClientL1.account.address); + + test("A -> L4", async () => { + const currencyIn = tokenA; + const currencyOut = tokenL4; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + }); + + test("PERMIT2_TRANSFER_FROM command", async () => { + const currencyIn = tokenA; + + const veraswapFeeRecipient = "0x000000000000000000000000000000000000beef" as Address; + const feeRecipientBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapFeeRecipient); + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + + const feeAmount = 1000n; + + const routePlanner = new RoutePlanner(); + routePlanner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [currencyIn, veraswapFeeRecipient, feeAmount]); + + //Execute + const value = 0n; + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const feeRecipientBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapFeeRecipient); + + // Input balance decreased by exact amount + expect(currencyInBalanceBeforeSwap - currencyInBalanceAfterSwap).toBe(feeAmount); + // Input balance increased by exact amount + expect(feeRecipientBalanceAfterSwap - feeRecipientBalanceBeforeSwap).toBe(feeAmount); + }); + + test("A -> L4 with Veraswap fee", async () => { + const currencyIn = tokenA; + const currencyOut = tokenL4; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 100n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test("A -> L4 with Veraswap and referral fee", async () => { + const currencyIn = tokenA; + const currencyOut = tokenL4; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 100n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount * 2n; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + + const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; + const referralFeeRecipient = { address: referralAddress, bips: feeBips }; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + referralFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test("ETH -> L3 with Veraswap fee", async () => { + const currencyIn = zeroAddress; // ETH + const currencyOut = tokenL3; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 100n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, // need to pass full amount in for ETH, value is amount after fees + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn - gasCost); // Input balance decreased by exact amount and gas cost + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test("ETH -> L3 with Veraswap and referral fee", async () => { + const currencyIn = zeroAddress; // ETH + const currencyOut = tokenL3; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 100n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount * 2n; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + + const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; + const referralFeeRecipient = { address: referralAddress, bips: feeBips }; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + referralFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn - gasCost); // Input balance decreased by exact amount and gas cost + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test.only("L3 -> WETH", async () => { + const currencyIn = tokenL3; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const currencyOut = contracts.weth9; + const currencyHops: Address[] = []; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + // const commandsFromFn = getRouterCommandsForQuote({ + // currencyIn, + // currencyOut, + // amountIn, + // contracts, + // ...quote, + // }); + const v4TradePlan = new V4Planner(); + v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); + v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ + { + poolKey: quote.bestQuoteSingle.poolKey, + zeroForOne: quote.bestQuoteSingle.zeroForOne, + amountIn, + amountOutMinimum: quote.bestQuoteSingle.variableAmount, + hookData: quote.bestQuoteSingle.hookData, + }, + ]); + v4TradePlan.addAction(Actions.TAKE, [ + // NOTE: for WETH output, we get an error if we just use currencyOut here + // currencyOut, + zeroAddress, + ACTION_CONSTANTS.MSG_SENDER, + quote.bestQuoteSingle.variableAmount, + ]); + const commandInput = v4TradePlan.finalize(); + const commands = [ + [CommandType.V4_SWAP, [commandInput]], + [CommandType.WRAP_ETH, [ACTION_CONSTANTS.MSG_SENDER, quote.bestQuoteSingle.variableAmount]], + ] as unknown as CreateCommandParamsGeneric[]; + + // expect(commands[0][1][0]).toEqual(commandsFromFn[0][1][0]); + // console.log(commands[0][1][0]); + // console.log(commandsFromFn[0][1][0]); + + const routePlanner = new RoutePlanner(); + // NOTE: With this, the output amount is not right + // addCommandsToRoutePlanner(routePlanner, commandsFromFn); + addCommandsToRoutePlanner(routePlanner, commands); + + console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + // const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const currencyOutBalanceBeforeSwap = await getBalance(contracts.weth9); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + console.log( + receipt.logs.map((l) => ({ + address: l.address, + ...decodeEventLog({ + abi: events, + data: l.data, + topics: l.topics, + }), + })), + ); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + // const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const currencyOutBalanceAfterSwap = await getBalance(contracts.weth9); + console.log({ currencyOutBalanceAfterSwap, currencyOutBalanceBeforeSwap, amountOut }); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + console.log({ + currencyOutBalanceAfterSwap, + currencyOutBalanceBeforeSwap, + amountOut, + gasCost, + beforeAndAfter: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, + // Should equal amountOut + beforeAndAfterPlusGas: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost, + shouldBeZero: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost - amountOut, + diff: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap + gasCost, + }); + console.log({ amountIn }); + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost + // expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); // Output balance increased by variable amount minus gas cost + // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost + }); + + test.only("L3 -> ETH", async () => { + const currencyIn = tokenL3; + const currencyOut = zeroAddress; // ETH + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const currencyHops: Address[] = []; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + // const commandsFromFn = getRouterCommandsForQuote({ + // currencyIn, + // currencyOut, + // amountIn, + // contracts, + // ...quote, + // }); + const v4TradePlan = new V4Planner(); + v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); + v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ + { + poolKey: quote.bestQuoteSingle.poolKey, + zeroForOne: quote.bestQuoteSingle.zeroForOne, + amountIn, + amountOutMinimum: quote.bestQuoteSingle.variableAmount, + hookData: quote.bestQuoteSingle.hookData, + }, + ]); + v4TradePlan.addAction(Actions.TAKE, [ + currencyOut, + ACTION_CONSTANTS.MSG_SENDER, + quote.bestQuoteSingle.variableAmount, + ]); + const commandInput = v4TradePlan.finalize(); + const commands = [ + [CommandType.V4_SWAP, [commandInput]], + [CommandType.WRAP_ETH, [ACTION_CONSTANTS.MSG_SENDER, quote.bestQuoteSingle.variableAmount]], + ] as unknown as CreateCommandParamsGeneric[]; + + // expect(commands[0][1][0]).toEqual(commandsFromFn[0][1][0]); + // console.log(commands[0][1][0]); + // console.log(commandsFromFn[0][1][0]); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const wethBalanceBeforeSwap = await getBalance(contracts.weth9); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + console.log( + receipt.logs.map((l) => ({ + address: l.address, + ...decodeEventLog({ + abi: events, + data: l.data, + topics: l.topics, + }), + })), + ); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const wethBalanceAfterSwap = await getBalance(contracts.weth9); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + console.log({ + currencyOutBalanceAfterSwap, + currencyOutBalanceBeforeSwap, + amountOut, + gasCost, + beforeAndAfter: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, + // Should equal amountOut + beforeAndAfterPlusGas: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost, + shouldBeZero: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost - amountOut, + diff: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap + gasCost, + }); + + expect(wethBalanceAfterSwap).toBe(wethBalanceBeforeSwap); // WETH balance should not change + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost + // expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); // Output balance increased by variable amount minus gas cost + // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost + }); + + test("L3 -> ETH with Veraswap and referral fee", async () => { + const currencyIn = tokenL3; + const currencyOut = zeroAddress; // ETH + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 0n; // 1% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount * 2n; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + + const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; + const referralFeeRecipient = { address: referralAddress, bips: feeBips }; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + referralFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value: amountIn, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test("A -> L4 -> B", async () => { + const currencyIn = tokenA; + const currencyOut = tokenB; + const currencyHops = [tokenL4]; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + }); +}); From 1c53aa05dff3e113f6b3733021438e4640181d55 Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Fri, 19 Sep 2025 16:01:47 +0200 Subject: [PATCH 4/9] [WIP] add more details to test --- .../uniswap/quote/getUniswapRouteV4.test.ts | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts index 9f375617..2cad1acb 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts @@ -420,7 +420,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount }); - test.only("L3 -> WETH", async () => { + test("L3 -> WETH", async () => { const currencyIn = tokenL3; const contracts = { @@ -549,6 +549,21 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const currencyHops: Address[] = []; + // Same as calling getUniswapRouteExactIn + // const metaQuote = await opChainL1Client.readContract({ + // address: contracts.metaQuoter, + // abi: [metaQuoteExactInputSingle], + // functionName: "metaQuoteExactInputSingle", + // args: [ + // { + // exactCurrency: currencyIn, + // variableCurrency: currencyOut, + // exactAmount: amountIn, + // poolKeyOptions: Object.values(DEFAULT_POOL_PARAMS), + // }, + // ], + // }); + // Route const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { chainId: opChainL1.id, @@ -561,13 +576,15 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { expect(route).toBeDefined(); const { quote, amountOut } = route!; - // const commandsFromFn = getRouterCommandsForQuote({ - // currencyIn, - // currencyOut, - // amountIn, - // contracts, - // ...quote, - // }); + expect(quote.bestQuoteSingle.poolKey.hooks).toBe(zeroAddress); + + const commandsFromFn = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + ...quote, + }); const v4TradePlan = new V4Planner(); v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ @@ -575,29 +592,22 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { poolKey: quote.bestQuoteSingle.poolKey, zeroForOne: quote.bestQuoteSingle.zeroForOne, amountIn, - amountOutMinimum: quote.bestQuoteSingle.variableAmount, + amountOutMinimum: amountOut, hookData: quote.bestQuoteSingle.hookData, }, ]); - v4TradePlan.addAction(Actions.TAKE, [ - currencyOut, - ACTION_CONSTANTS.MSG_SENDER, - quote.bestQuoteSingle.variableAmount, - ]); + v4TradePlan.addAction(Actions.TAKE, [currencyOut, ACTION_CONSTANTS.MSG_SENDER, amountOut]); const commandInput = v4TradePlan.finalize(); - const commands = [ - [CommandType.V4_SWAP, [commandInput]], - [CommandType.WRAP_ETH, [ACTION_CONSTANTS.MSG_SENDER, quote.bestQuoteSingle.variableAmount]], - ] as unknown as CreateCommandParamsGeneric[]; - - // expect(commands[0][1][0]).toEqual(commandsFromFn[0][1][0]); - // console.log(commands[0][1][0]); - // console.log(commandsFromFn[0][1][0]); + const commands = [[CommandType.V4_SWAP, [commandInput]]] as unknown as CreateCommandParamsGeneric[]; const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); + // addCommandsToRoutePlanner(routePlanner, commands); + addCommandsToRoutePlanner(routePlanner, commandsFromFn); - console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + // Commpare V4_SWAP command inputs + expect(commands[0][1][0]).toBe(commandsFromFn[0][1][0]); + + // console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); //Execute const currencyInBalanceBeforeSwap = await getBalance(currencyIn); @@ -635,20 +645,22 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { console.log({ currencyOutBalanceAfterSwap, currencyOutBalanceBeforeSwap, - amountOut, gasCost, beforeAndAfter: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, // Should equal amountOut beforeAndAfterPlusGas: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost, - shouldBeZero: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost - amountOut, - diff: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap + gasCost, }); expect(wethBalanceAfterSwap).toBe(wethBalanceBeforeSwap); // WETH balance should not change expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost + console.log({ + amountIn, + amountOut, + // Should be zero + diff: currencyOutBalanceAfterSwap - (currencyOutBalanceBeforeSwap + amountOut - gasCost), + }); // expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); // Output balance increased by variable amount minus gas cost - // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost }); test("L3 -> ETH with Veraswap and referral fee", async () => { From d6718eeb04e114efda8305674eba536cfd1a4b4b Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Fri, 19 Sep 2025 18:53:57 +0200 Subject: [PATCH 5/9] [WIP] add more details to the test --- .../uniswap/quote/getUniswapRouteV4.test.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts index 2cad1acb..d3c874fb 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts @@ -1,10 +1,11 @@ import { QueryClient } from "@tanstack/react-query"; import { Actions, V4Planner } from "@uniswap/v4-sdk"; -import { Address, decodeEventLog, parseAbi, parseUnits, zeroAddress } from "viem"; +import { Address, decodeEventLog, parseAbi, parseEventLogs, parseUnits, zeroAddress } from "viem"; import { describe, expect, test } from "vitest"; import { events } from "../../artifacts/events.js"; import { IERC20 } from "../../artifacts/IERC20.js"; +import { Swap } from "../../artifacts/IPoolManager.js"; import { IUniversalRouter } from "../../artifacts/IUniversalRouter.js"; import { opChainL1, opChainL1Client } from "../../chains/supersim.js"; import { LOCAL_CURRENCIES } from "../../constants/tokens.js"; @@ -613,6 +614,10 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const currencyInBalanceBeforeSwap = await getBalance(currencyIn); const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); const wethBalanceBeforeSwap = await getBalance(contracts.weth9); + const poolManagerCurrencyOutBalanceBeforeSwap = await getBalanceForAddress( + currencyOut, + LOCAL_UNISWAP_CONTRACTS.v4PoolManager, + ); const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); const hash = await anvilClientL1.writeContract({ @@ -623,6 +628,10 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { args: [routePlanner.commands, routePlanner.inputs, deadline], }); const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + // Can be used for debugging with if `cast run ${hash} --rpc-url http://127.0.0.1:8547 -vvv` + // Make sure to be running `pnpm test:watch src/uniswap/quote/getUniswapRouteV4.test.ts` + console.log({ hash: receipt.transactionHash }); console.log( receipt.logs.map((l) => ({ address: l.address, @@ -634,9 +643,20 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { })), ); + const swapEvent = parseEventLogs({ abi: [Swap], eventName: "Swap", logs: receipt.logs, strict: true })[0]; + const swapEventAmountOut = swapEvent.args.amount0; + expect(swapEventAmountOut).toBe(amountOut); + const currencyInBalanceAfterSwap = await getBalance(currencyIn); const currencyOutBalanceAfterSwap = await getBalance(currencyOut); const wethBalanceAfterSwap = await getBalance(contracts.weth9); + const poolManagerCurrencyOutBalanceAfterSwap = await getBalanceForAddress( + currencyOut, + LOCAL_UNISWAP_CONTRACTS.v4PoolManager, + ); + console.log({ poolManagerCurrencyOutBalanceBeforeSwap, poolManagerCurrencyOutBalanceAfterSwap }); + // Swap done with the pool manager. Ensure its ETH balance decreases exactly by amountOut + expect(poolManagerCurrencyOutBalanceAfterSwap).toBe(poolManagerCurrencyOutBalanceBeforeSwap - amountOut); const gasUsed = receipt.gasUsed; const effectiveGasPrice = receipt.effectiveGasPrice; @@ -659,8 +679,8 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { // Should be zero diff: currencyOutBalanceAfterSwap - (currencyOutBalanceBeforeSwap + amountOut - gasCost), }); - // expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); // Output balance increased by variable amount minus gas cost - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost + // FIXME: currently the balance after is lower than expected: 10000000000000000 wei (0.01 ether) diff + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost }); test("L3 -> ETH with Veraswap and referral fee", async () => { From b9be81cddea1be8bbfdd9c7aeb8c636586b29feb Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Mon, 22 Sep 2025 16:02:00 +0200 Subject: [PATCH 6/9] Move v3 pool tests, fix erc20 - eth tests --- .../src/uniswap/quote/getUniswapRoute.test.ts | 169 +--------- .../uniswap/quote/getUniswapRouteV3.test.ts | 291 ++++++++++++++++++ .../uniswap/quote/getUniswapRouteV4.test.ts | 81 ++--- 3 files changed, 317 insertions(+), 224 deletions(-) create mode 100644 packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV3.test.ts diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts index 2aec64b3..c3ae0361 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts @@ -32,7 +32,6 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { // A/ETH, B/ETH Pools Exist // A/B Pool Does Not Exist const tokenA = getUniswapV4Address(LOCAL_CURRENCIES[0]); // 2 A tokens after this (Hyperlane) - const tokenB = getUniswapV4Address(LOCAL_CURRENCIES[3]); // 2 B tokens after this (Hyperlane) const tokenL34 = getUniswapV4Address(LOCAL_CURRENCIES[6]); const tokenL3 = getUniswapV4Address(LOCAL_CURRENCIES[7]); const tokenL4 = getUniswapV4Address(LOCAL_CURRENCIES[8]); @@ -52,172 +51,6 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { // Helper function to get balance of a token const getBalance = (token: Address) => getBalanceForAddress(token, anvilClientL1.account.address); - describe("V3 Single", () => { - test("A -> L3", async () => { - const currencyIn = tokenA; - const currencyOut = tokenL3; - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn, - contracts: { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - }); - - test.skip("L4 -> ETH", async () => { - const currencyIn = tokenL4; - const currencyOut = zeroAddress; // ETH - const currencyHops: Address[] = []; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut } = route!; - - const commands = getRouterCommandsForQuote({ - currencyIn, - currencyOut, - amountIn, - contracts, - ...quote, - }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); - // console.log( - // receipt.logs.map((l) => ({ - // address: l.address, - // ...decodeEventLog({ - // abi: events, - // data: l.data, - // topics: l.topics, - // }), - // })), - // ); - - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCost = gasUsed * effectiveGasPrice; - - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost - // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost - }); - }); - - describe("V3 Multihop", () => { - test("A -> L3 -> B", async () => { - const currencyIn = tokenA; - const currencyOut = tokenB; - const currencyHops = [tokenL3]; - - const contracts = { - weth9: LOCAL_UNISWAP_CONTRACTS.weth9, - metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, - }; - - // Route - const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { - chainId: opChainL1.id, - currencyIn, - currencyOut, - currencyHops, - amountIn, - contracts, - }); - expect(route).toBeDefined(); - const { quote, amountOut, value } = route!; - - const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); - - const routePlanner = new RoutePlanner(); - addCommandsToRoutePlanner(routePlanner, commands); - - //Execute - const currencyInBalanceBeforeSwap = await getBalance(currencyIn); - const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); - const hash = await anvilClientL1.writeContract({ - abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], - address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value, - functionName: "execute", - args: [routePlanner.commands, routePlanner.inputs, deadline], - }); - await opChainL1Client.waitForTransactionReceipt({ hash }); - const currencyInBalanceAfterSwap = await getBalance(currencyIn); - const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount - }); - }); - describe("Mixed V4 -> V3", () => { test("L4 -> L34 -> L3", async () => { const currencyIn = tokenL4; @@ -264,7 +97,7 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount }); - test("A -> ETH -> Z", async () => { + test.skip("A -> ETH -> Z", async () => { const currencyIn = tokenA; const currencyOut = tokenZ; const hopCurrencies = [zeroAddress]; diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV3.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV3.test.ts new file mode 100644 index 00000000..07a5b378 --- /dev/null +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV3.test.ts @@ -0,0 +1,291 @@ +import { QueryClient } from "@tanstack/react-query"; +import { Address, parseAbi, parseUnits, zeroAddress } from "viem"; +import { describe, expect, test } from "vitest"; + +import { IERC20 } from "../../artifacts/IERC20.js"; +import { IUniversalRouter } from "../../artifacts/IUniversalRouter.js"; +import { opChainL1, opChainL1Client } from "../../chains/supersim.js"; +import { LOCAL_CURRENCIES } from "../../constants/tokens.js"; +import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; +import { getUniswapV4Address } from "../../currency/currency.js"; +import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; +import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; +import { RoutePlanner } from "../routerCommands.js"; + +import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; + +//TODO: Add deeper tests with ETH wrap/unwrap +describe("uniswap/quote/getUniswapRoute.test.ts", function () { + const queryClient = new QueryClient(); + // Uniswap Error Abi + const UniswapErrorAbi = parseAbi([ + "error DeltaNotNegative(address)", + "error DeltaNotPositive(address)", + "error CurrencyNotSettled()", + "error PoolNotInitialized()", + "error V3TooLittleReceived()", + "error SwapAmountCannotBeZero()", + ]); + + const amountIn = parseUnits("0.01", 18); + + // A/ETH, B/ETH Pools Exist + // A/B Pool Does Not Exist + const tokenA = getUniswapV4Address(LOCAL_CURRENCIES[0]); // 2 A tokens after this (Hyperlane) + const tokenB = getUniswapV4Address(LOCAL_CURRENCIES[3]); // 2 B tokens after this (Hyperlane) + const tokenL3 = getUniswapV4Address(LOCAL_CURRENCIES[7]); + const tokenL4 = getUniswapV4Address(LOCAL_CURRENCIES[8]); + + const getBalanceForAddress = function (token: Address, address: Address) { + if (token === zeroAddress) return opChainL1Client.getBalance({ address }); + + return opChainL1Client.readContract({ + address: token, + abi: IERC20.abi, + functionName: "balanceOf", + args: [address], + }); + }; + + // Helper function to get balance of a token + const getBalance = (token: Address) => getBalanceForAddress(token, anvilClientL1.account.address); + + test("A -> L3", async () => { + const currencyIn = tokenA; + const currencyOut = tokenL3; + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts: { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + }); + + test("L4 -> ETH", async () => { + const currencyIn = tokenL4; + const currencyOut = zeroAddress; // ETH + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + // console.log( + // receipt.logs.map((l) => ({ + // address: l.address, + // ...decodeEventLog({ + // abi: events, + // data: l.data, + // topics: l.topics, + // }), + // })), + // ); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost + // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost + }); + + test("L4 -> ETH with Veraswap and referral fee", async () => { + const currencyIn = tokenL4; + const currencyOut = zeroAddress; // ETH + const currencyHops: Address[] = []; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + const feeBips = 25n; // 0.25% referral fee + const bipsDenominator = 10000n; // 100% = 10000 bips + const feeAmount = (amountIn * feeBips) / bipsDenominator; + const amountInWithoutFee = amountIn - feeAmount * 2n; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn: amountInWithoutFee, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; + const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; + + const referralAddress = "0x00000000000000000000000000000000000beef2" as Address; + const referralFeeRecipient = { address: referralAddress, bips: feeBips }; + + const commands = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + veraswapFeeRecipient, + referralFeeRecipient, + ...quote, + }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); + + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress); + const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress); + + const gasUsed = receipt.gasUsed; + const effectiveGasPrice = receipt.effectiveGasPrice; + const gasCost = gasUsed * effectiveGasPrice; + + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost + expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount + }); + + test("A -> L3 -> B", async () => { + const currencyIn = tokenA; + const currencyOut = tokenB; + const currencyHops = [tokenL3]; + + const contracts = { + weth9: LOCAL_UNISWAP_CONTRACTS.weth9, + metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, + }; + + // Route + const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, { + chainId: opChainL1.id, + currencyIn, + currencyOut, + currencyHops, + amountIn, + contracts, + }); + expect(route).toBeDefined(); + const { quote, amountOut, value } = route!; + + const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); + + const routePlanner = new RoutePlanner(); + addCommandsToRoutePlanner(routePlanner, commands); + + //Execute + const currencyInBalanceBeforeSwap = await getBalance(currencyIn); + const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); + const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); + const hash = await anvilClientL1.writeContract({ + abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], + address: LOCAL_UNISWAP_CONTRACTS.universalRouter, + value, + functionName: "execute", + args: [routePlanner.commands, routePlanner.inputs, deadline], + }); + await opChainL1Client.waitForTransactionReceipt({ hash }); + const currencyInBalanceAfterSwap = await getBalance(currencyIn); + const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount + expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount + }); +}); diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts index d3c874fb..4ca9c595 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts @@ -539,7 +539,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost }); - test.only("L3 -> ETH", async () => { + test("L3 -> ETH", async () => { const currencyIn = tokenL3; const currencyOut = zeroAddress; // ETH @@ -579,41 +579,38 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { expect(quote.bestQuoteSingle.poolKey.hooks).toBe(zeroAddress); - const commandsFromFn = getRouterCommandsForQuote({ + const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote, }); - const v4TradePlan = new V4Planner(); - v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); - v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ - { - poolKey: quote.bestQuoteSingle.poolKey, - zeroForOne: quote.bestQuoteSingle.zeroForOne, - amountIn, - amountOutMinimum: amountOut, - hookData: quote.bestQuoteSingle.hookData, - }, - ]); - v4TradePlan.addAction(Actions.TAKE, [currencyOut, ACTION_CONSTANTS.MSG_SENDER, amountOut]); - const commandInput = v4TradePlan.finalize(); - const commands = [[CommandType.V4_SWAP, [commandInput]]] as unknown as CreateCommandParamsGeneric[]; + // const v4TradePlan = new V4Planner(); + // v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); + // v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ + // { + // poolKey: quote.bestQuoteSingle.poolKey, + // zeroForOne: quote.bestQuoteSingle.zeroForOne, + // amountIn, + // amountOutMinimum: amountOut, + // hookData: quote.bestQuoteSingle.hookData, + // }, + // ]); + // v4TradePlan.addAction(Actions.TAKE, [currencyOut, ACTION_CONSTANTS.MSG_SENDER, amountOut]); + // const commandInput = v4TradePlan.finalize(); + // const commandsFromPlan = [[CommandType.V4_SWAP, [commandInput]]] as unknown as CreateCommandParamsGeneric[]; const routePlanner = new RoutePlanner(); - // addCommandsToRoutePlanner(routePlanner, commands); - addCommandsToRoutePlanner(routePlanner, commandsFromFn); + addCommandsToRoutePlanner(routePlanner, commands); + // addCommandsToRoutePlanner(routePlanner, commandsFromPlan); // Commpare V4_SWAP command inputs - expect(commands[0][1][0]).toBe(commandsFromFn[0][1][0]); - - // console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + // expect(commands[0][1][0]).toBe(commandsFromPlan[0][1][0]); //Execute const currencyInBalanceBeforeSwap = await getBalance(currencyIn); const currencyOutBalanceBeforeSwap = await getBalance(currencyOut); - const wethBalanceBeforeSwap = await getBalance(contracts.weth9); const poolManagerCurrencyOutBalanceBeforeSwap = await getBalanceForAddress( currencyOut, LOCAL_UNISWAP_CONTRACTS.v4PoolManager, @@ -623,7 +620,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const hash = await anvilClientL1.writeContract({ abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, + value: 0n, functionName: "execute", args: [routePlanner.commands, routePlanner.inputs, deadline], }); @@ -631,17 +628,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { // Can be used for debugging with if `cast run ${hash} --rpc-url http://127.0.0.1:8547 -vvv` // Make sure to be running `pnpm test:watch src/uniswap/quote/getUniswapRouteV4.test.ts` - console.log({ hash: receipt.transactionHash }); - console.log( - receipt.logs.map((l) => ({ - address: l.address, - ...decodeEventLog({ - abi: events, - data: l.data, - topics: l.topics, - }), - })), - ); + // console.log({ hash: receipt.transactionHash }); const swapEvent = parseEventLogs({ abi: [Swap], eventName: "Swap", logs: receipt.logs, strict: true })[0]; const swapEventAmountOut = swapEvent.args.amount0; @@ -649,12 +636,11 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const currencyInBalanceAfterSwap = await getBalance(currencyIn); const currencyOutBalanceAfterSwap = await getBalance(currencyOut); - const wethBalanceAfterSwap = await getBalance(contracts.weth9); const poolManagerCurrencyOutBalanceAfterSwap = await getBalanceForAddress( currencyOut, LOCAL_UNISWAP_CONTRACTS.v4PoolManager, ); - console.log({ poolManagerCurrencyOutBalanceBeforeSwap, poolManagerCurrencyOutBalanceAfterSwap }); + // Swap done with the pool manager. Ensure its ETH balance decreases exactly by amountOut expect(poolManagerCurrencyOutBalanceAfterSwap).toBe(poolManagerCurrencyOutBalanceBeforeSwap - amountOut); @@ -662,24 +648,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const effectiveGasPrice = receipt.effectiveGasPrice; const gasCost = gasUsed * effectiveGasPrice; - console.log({ - currencyOutBalanceAfterSwap, - currencyOutBalanceBeforeSwap, - gasCost, - beforeAndAfter: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, - // Should equal amountOut - beforeAndAfterPlusGas: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost, - }); - - expect(wethBalanceAfterSwap).toBe(wethBalanceBeforeSwap); // WETH balance should not change expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount - console.log({ - amountIn, - amountOut, - // Should be zero - diff: currencyOutBalanceAfterSwap - (currencyOutBalanceBeforeSwap + amountOut - gasCost), - }); - // FIXME: currently the balance after is lower than expected: 10000000000000000 wei (0.01 ether) diff expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost }); @@ -693,7 +662,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter, }; - const feeBips = 0n; // 1% referral fee + const feeBips = 25n; // 0.25% referral fee const bipsDenominator = 10000n; // 100% = 10000 bips const feeAmount = (amountIn * feeBips) / bipsDenominator; const amountInWithoutFee = amountIn - feeAmount * 2n; @@ -708,7 +677,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { contracts, }); expect(route).toBeDefined(); - const { quote, amountOut } = route!; + const { quote, amountOut, value } = route!; const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address; const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips }; @@ -739,7 +708,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const hash = await anvilClientL1.writeContract({ abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, + value, functionName: "execute", args: [routePlanner.commands, routePlanner.inputs, deadline], }); From 46c83c9cd3ecdafa34832512e114dea88efed901 Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Mon, 22 Sep 2025 16:58:57 +0200 Subject: [PATCH 7/9] Fix and clean up tests --- .../src/uniswap/quote/getUniswapRoute.test.ts | 15 ++- .../uniswap/quote/getUniswapRouteV4.test.ts | 96 +++---------------- 2 files changed, 28 insertions(+), 83 deletions(-) diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts index c3ae0361..1e2053e6 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts @@ -10,7 +10,7 @@ import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; import { getUniswapV4Address } from "../../currency/currency.js"; import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; -import { RoutePlanner } from "../routerCommands.js"; +import { getCommandName, RoutePlanner } from "../routerCommands.js"; import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; @@ -97,7 +97,7 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount }); - test.skip("A -> ETH -> Z", async () => { + test("A -> ETH -> Z", async () => { const currencyIn = tokenA; const currencyOut = tokenZ; const hopCurrencies = [zeroAddress]; @@ -118,8 +118,11 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { }); expect(route).toBeDefined(); const { quote, amountOut, value } = route!; + console.log({ tokenA, tokenZ, weth: LOCAL_UNISWAP_CONTRACTS.weth9 }); + console.log({ path: quote.bestQuoteMultihop.path }); const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote }); + console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); const routePlanner = new RoutePlanner(); addCommandsToRoutePlanner(routePlanner, commands); @@ -135,9 +138,17 @@ describe("uniswap/quote/getUniswapRoute.test.ts", function () { functionName: "execute", args: [routePlanner.commands, routePlanner.inputs, deadline], }); + console.log({ hash }); await opChainL1Client.waitForTransactionReceipt({ hash }); const currencyInBalanceAfterSwap = await getBalance(currencyIn); const currencyOutBalanceAfterSwap = await getBalance(currencyOut); + console.log({ + currencyOutBalanceBeforeSwap, + currencyOutBalanceAfterSwap, + currencyOut, + amountOut, + diff: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, + }); expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount }); diff --git a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts index 4ca9c595..a8d6191c 100644 --- a/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts +++ b/packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV4.test.ts @@ -1,9 +1,7 @@ import { QueryClient } from "@tanstack/react-query"; -import { Actions, V4Planner } from "@uniswap/v4-sdk"; -import { Address, decodeEventLog, parseAbi, parseEventLogs, parseUnits, zeroAddress } from "viem"; +import { Address, parseAbi, parseEventLogs, parseUnits, zeroAddress } from "viem"; import { describe, expect, test } from "vitest"; -import { events } from "../../artifacts/events.js"; import { IERC20 } from "../../artifacts/IERC20.js"; import { Swap } from "../../artifacts/IPoolManager.js"; import { IUniversalRouter } from "../../artifacts/IUniversalRouter.js"; @@ -13,13 +11,7 @@ import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js"; import { getUniswapV4Address } from "../../currency/currency.js"; import { anvilClientL1, wagmiConfig } from "../../test/constants.js"; import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js"; -import { - ACTION_CONSTANTS, - CommandType, - CreateCommandParamsGeneric, - getCommandName, - RoutePlanner, -} from "../routerCommands.js"; +import { CommandType, RoutePlanner } from "../routerCommands.js"; import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js"; @@ -421,7 +413,7 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount }); - test("L3 -> WETH", async () => { + test.only("L3 -> WETH", async () => { const currencyIn = tokenL3; const contracts = { @@ -442,49 +434,19 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { contracts, }); expect(route).toBeDefined(); - const { quote, amountOut } = route!; + const { quote, amountOut, value } = route!; - // const commandsFromFn = getRouterCommandsForQuote({ - // currencyIn, - // currencyOut, - // amountIn, - // contracts, - // ...quote, - // }); - const v4TradePlan = new V4Planner(); - v4TradePlan.addAction(Actions.SETTLE, [currencyIn, amountIn, true]); - v4TradePlan.addAction(Actions.SWAP_EXACT_IN_SINGLE, [ - { - poolKey: quote.bestQuoteSingle.poolKey, - zeroForOne: quote.bestQuoteSingle.zeroForOne, - amountIn, - amountOutMinimum: quote.bestQuoteSingle.variableAmount, - hookData: quote.bestQuoteSingle.hookData, - }, - ]); - v4TradePlan.addAction(Actions.TAKE, [ - // NOTE: for WETH output, we get an error if we just use currencyOut here - // currencyOut, - zeroAddress, - ACTION_CONSTANTS.MSG_SENDER, - quote.bestQuoteSingle.variableAmount, - ]); - const commandInput = v4TradePlan.finalize(); - const commands = [ - [CommandType.V4_SWAP, [commandInput]], - [CommandType.WRAP_ETH, [ACTION_CONSTANTS.MSG_SENDER, quote.bestQuoteSingle.variableAmount]], - ] as unknown as CreateCommandParamsGeneric[]; - - // expect(commands[0][1][0]).toEqual(commandsFromFn[0][1][0]); - // console.log(commands[0][1][0]); - // console.log(commandsFromFn[0][1][0]); + const commandsFromFn = getRouterCommandsForQuote({ + currencyIn, + currencyOut, + amountIn, + contracts, + ...quote, + }); const routePlanner = new RoutePlanner(); // NOTE: With this, the output amount is not right - // addCommandsToRoutePlanner(routePlanner, commandsFromFn); - addCommandsToRoutePlanner(routePlanner, commands); - - console.log({ commandsKeys: commands.map((c) => [getCommandName(c[0]), c[1].toString()]) }); + addCommandsToRoutePlanner(routePlanner, commandsFromFn); //Execute const currencyInBalanceBeforeSwap = await getBalance(currencyIn); @@ -495,48 +457,20 @@ describe("uniswap/quote/getUniswapRouteV4.test.ts", function () { const hash = await anvilClientL1.writeContract({ abi: [...IUniversalRouter.abi, ...UniswapErrorAbi], address: LOCAL_UNISWAP_CONTRACTS.universalRouter, - value: amountIn, + value, functionName: "execute", args: [routePlanner.commands, routePlanner.inputs, deadline], }); - const receipt = await opChainL1Client.waitForTransactionReceipt({ hash }); - console.log( - receipt.logs.map((l) => ({ - address: l.address, - ...decodeEventLog({ - abi: events, - data: l.data, - topics: l.topics, - }), - })), - ); + + await opChainL1Client.waitForTransactionReceipt({ hash }); const currencyInBalanceAfterSwap = await getBalance(currencyIn); // const currencyOutBalanceAfterSwap = await getBalance(currencyOut); const currencyOutBalanceAfterSwap = await getBalance(contracts.weth9); - console.log({ currencyOutBalanceAfterSwap, currencyOutBalanceBeforeSwap, amountOut }); - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCost = gasUsed * effectiveGasPrice; - - console.log({ - currencyOutBalanceAfterSwap, - currencyOutBalanceBeforeSwap, - amountOut, - gasCost, - beforeAndAfter: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap, - // Should equal amountOut - beforeAndAfterPlusGas: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost, - shouldBeZero: currencyOutBalanceBeforeSwap - currencyOutBalanceAfterSwap - gasCost - amountOut, - diff: currencyOutBalanceAfterSwap - currencyOutBalanceBeforeSwap + gasCost, - }); - console.log({ amountIn }); expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount minus gas cost - // expect(currencyOutBalanceAfterSwap).toBeGreaterThan(currencyOutBalanceBeforeSwap); // Output balance increased by variable amount minus gas cost - // expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost }); test("L3 -> ETH", async () => { From 05f917dd3e97d63b6d8502fa69024ee8f95041b4 Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Wed, 24 Sep 2025 15:48:52 +0200 Subject: [PATCH 8/9] Update rpc urls for arbitrum and base, remove TODOs --- packages/veraswap-sdk/src/chains/arbitrum.ts | 4 ++-- packages/veraswap-sdk/src/chains/base.ts | 7 +++++-- packages/veraswap-sdk/src/constants/uniswap.ts | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/veraswap-sdk/src/chains/arbitrum.ts b/packages/veraswap-sdk/src/chains/arbitrum.ts index f88a79dc..c5849477 100644 --- a/packages/veraswap-sdk/src/chains/arbitrum.ts +++ b/packages/veraswap-sdk/src/chains/arbitrum.ts @@ -6,8 +6,8 @@ export const arbitrum = { ...arbitrumChain, rpcUrls: { default: { - http: ["https://lb.drpc.org/ogrpc?network=arbitrum&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"], - webSocket: ["wss://lb.drpc.org/ogws?network=arbitrum&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"], + http: ["https://arbitrum-mainnet.core.chainstack.com/db36c07f913d8dbb8cdf6a8831c1bd23"], + webSocket: ["wss://arbitrum-mainnet.core.chainstack.com/db36c07f913d8dbb8cdf6a8831c1bd23"], }, }, custom: { diff --git a/packages/veraswap-sdk/src/chains/base.ts b/packages/veraswap-sdk/src/chains/base.ts index 13abb1e8..97f5082e 100644 --- a/packages/veraswap-sdk/src/chains/base.ts +++ b/packages/veraswap-sdk/src/chains/base.ts @@ -6,8 +6,11 @@ export const base = { ...baseChain, rpcUrls: { default: { - http: ["https://lb.drpc.org/ogrpc?network=base&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"], - webSocket: ["wss://lb.drpc.org/ogws?network=base&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"], + // TODO:investigate issue with Chainstack causing a revert error when fetching quote + // http: ["https://base-mainnet.core.chainstack.com/a3cbc100238b9972f7d4f44c446a2c16"], + // webSocket: ["wss://base-mainnet.core.chainstack.com/a3cbc100238b9972f7d4f44c446a2c16"], + http: ["https://base-mainnet.g.alchemy.com/v2/VFO0zUhBMB8WDuge_ro4KzKRkvwyrYas"], + webSocket: ["wss://base-mainnet.g.alchemy.com/v2/VFO0zUhBMB8WDuge_ro4KzKRkvwyrYas"], }, }, custom: { diff --git a/packages/veraswap-sdk/src/constants/uniswap.ts b/packages/veraswap-sdk/src/constants/uniswap.ts index b8e0c7ba..9c0e0bdf 100644 --- a/packages/veraswap-sdk/src/constants/uniswap.ts +++ b/packages/veraswap-sdk/src/constants/uniswap.ts @@ -161,7 +161,6 @@ export function getUniswapContracts(params?: { owner?: Address }) { /** Uniswap contracts for local deployment via CREATE2 & salt = bytes32(0) */ export const LOCAL_UNISWAP_CONTRACTS = getUniswapContracts(); -//TODO: Add metaquoter address (compute using poolManager address) export interface UniswapContracts { weth9: Address; v2Factory?: Address; @@ -176,7 +175,6 @@ export interface UniswapContracts { universalRouter: Address; } -// TODO: FIXME fix all weth9 addresses /** Uniswap contracts by chain */ export const UNISWAP_CONTRACTS: Record = { [opChainL1.id]: LOCAL_UNISWAP_CONTRACTS, From c8bc08c59e58ad993fc6b815c67f149ffc38615c Mon Sep 17 00:00:00 2001 From: Oscar Baracos Date: Wed, 24 Sep 2025 15:52:08 +0200 Subject: [PATCH 9/9] Change default slippage to 2.5% --- packages/veraswap-app/src/atoms/atoms.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/veraswap-app/src/atoms/atoms.ts b/packages/veraswap-app/src/atoms/atoms.ts index 4040b503..5a69817a 100644 --- a/packages/veraswap-app/src/atoms/atoms.ts +++ b/packages/veraswap-app/src/atoms/atoms.ts @@ -239,8 +239,9 @@ export const resetTransactionStateAtom = atom(null, (_, set) => { export const slippageAtom = atom<"auto" | number>("auto"); +/** Slippage tolerance in percentage. If slippageAtom is "auto", defaults to 2.5% */ export const slippageToleranceAtom = atom((get) => { const slippage = get(slippageAtom); - if (slippage === "auto") return 0.5; + if (slippage === "auto") return 2.5; // default to 2.5% return slippage; });