From 56b728a54313b2a7a021207f0beaf92091e45c8e Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 19 Mar 2024 12:20:49 -0400 Subject: [PATCH 01/27] initial commit - copy from external api --- src/utils/routerTradeAdapter.ts | 274 ++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 src/utils/routerTradeAdapter.ts diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts new file mode 100644 index 0000000..167f048 --- /dev/null +++ b/src/utils/routerTradeAdapter.ts @@ -0,0 +1,274 @@ +import { MixedRouteSDK, Trade as RouterTrade } from '@uniswap/router-sdk' +import { + Currency, + CurrencyAmount, + Ether, + NativeCurrency as SdkNativeCurrency, + Token, + TradeType, +} from '@uniswap/sdk-core' +import { Pair, Route as V2Route } from '@uniswap/v2-sdk' +import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' +import { BigNumber } from 'ethers' +import { ETH_ADDRESS, WETH_ADDRESS } from './constants' + +export enum ChainId { + MAINNET = 1, + OPTIMISM = 10, + POLYGON = 137, + ARBITRUM = 42161, + BNB = 56, + BASE = 8453, +} + +export const WRAPPED_NATIVE_CURRENCY = { + [ChainId.MAINNET]: new Token( + ChainId.MAINNET, + '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.POLYGON]: new Token( + ChainId.POLYGON, + '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + 18, + 'WMATIC', + 'Wrapped MATIC' + ), + [ChainId.OPTIMISM]: new Token( + ChainId.OPTIMISM, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ARBITRUM]: new Token( + ChainId.ARBITRUM, + '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.BNB]: new Token(ChainId.BNB, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB', 'Wrapped BNB'), + [ChainId.BASE]: new Token(ChainId.BASE, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), +} + +export type ClassicInput = { + readonly token: string + readonly amount: string +} + +export type ClassicOutput = { + readonly token: string + readonly amount: string + readonly recipient: string +} + +export type TokenInRoute = { + address: string + chainId: number + symbol: string + decimals: string + name?: string + buyFeeBps?: string + sellFeeBps?: string +} + +export enum PoolType { + V2Pool = 'v2-pool', + V3Pool = 'v3-pool', +} + +export type V2Reserve = { + token: TokenInRoute + quotient: string +} + +export type V2PoolInRoute = { + type: PoolType.V2Pool + address?: string + tokenIn: TokenInRoute + tokenOut: TokenInRoute + reserve0: V2Reserve + reserve1: V2Reserve + amountIn?: string + amountOut?: string +} + +export type V3PoolInRoute = { + type: PoolType.V3Pool + address?: string + tokenIn: TokenInRoute + tokenOut: TokenInRoute + sqrtRatioX96: string + liquidity: string + tickCurrent: string + fee: string + amountIn?: string + amountOut?: string +} + +export type PartialClassicQuote = { + chainId: number + swapper: string + input: ClassicInput + output: ClassicOutput + slippage: number + tradeType: TradeType + route: Array<(V3PoolInRoute | V2PoolInRoute)[]> +} + +interface RouteResult { + routev3: V3Route | null + routev2: V2Route | null + mixedRoute: MixedRouteSDK | null + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount +} + +export const isNativeCurrency = (address: string, chainId: number) => + address.toLowerCase() === WETH_ADDRESS(chainId).toLowerCase() || address.toLowerCase() === ETH_ADDRESS.toLowerCase() + +export class RouterTradeAdapter { + static fromClassicQuote(quote: PartialClassicQuote) { + const { route, input, output, chainId } = quote + + const tokenIn = route[0]?.[0]?.tokenIn + const tokenOut = route[0]?.[route[0]?.length - 1]?.tokenOut + if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present') + + const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(input.token, chainId), tokenIn) + const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(output.token, chainId), tokenOut) + + const typedRoutes: RouteResult[] = route.map((route) => { + if (route.length === 0) { + throw new Error('Expected route to have at least one pair or pool') + } + const rawAmountIn = route[0].amountIn + const rawAmountOut = route[route.length - 1].amountOut + + if (!rawAmountIn || !rawAmountOut) { + throw new Error('Expected both raw amountIn and raw amountOut to be present') + } + + const inputAmount = CurrencyAmount.fromRawAmount(parsedCurrencyIn, rawAmountIn) + const outputAmount = CurrencyAmount.fromRawAmount(parsedCurrencyOut, rawAmountOut) + + const isOnlyV2 = RouterTradeAdapter.isVersionedRoute(PoolType.V2Pool, route) + const isOnlyV3 = RouterTradeAdapter.isVersionedRoute(PoolType.V3Pool, route) + + return { + routev3: isOnlyV3 + ? new V3Route(route.map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) + : null, + routev2: isOnlyV2 + ? new V2Route(route.map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) + : null, + mixedRoute: + !isOnlyV3 && !isOnlyV2 + ? new MixedRouteSDK(route.map(RouterTradeAdapter.toPoolOrPair), parsedCurrencyIn, parsedCurrencyOut) + : null, + inputAmount, + outputAmount, + } + }) + + return new RouterTrade({ + v2Routes: typedRoutes + .filter((route) => route.routev2) + .map((route) => ({ + routev2: route.routev2 as V2Route, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + v3Routes: typedRoutes + .filter((route) => route.routev3) + .map((route) => ({ + routev3: route.routev3 as V3Route, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + mixedRoutes: typedRoutes + .filter((route) => route.mixedRoute) + .map((route) => ({ + mixedRoute: route.mixedRoute as MixedRouteSDK, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + tradeType: quote.tradeType, + }) + } + + private static toCurrency(isNative: boolean, token: TokenInRoute): Currency { + if (isNative) { + return new NativeCurrency(token.chainId, parseInt(token.decimals)) + } + return this.toToken(token) + } + + private static toPoolOrPair = (pool: V3PoolInRoute | V2PoolInRoute): Pool | Pair => { + return pool.type === PoolType.V3Pool ? RouterTradeAdapter.toPool(pool) : RouterTradeAdapter.toPair(pool) + } + + private static toToken(token: TokenInRoute): Token { + const { chainId, address, decimals, symbol, buyFeeBps, sellFeeBps } = token + return new Token( + chainId, + address, + parseInt(decimals.toString()), + symbol, + /* name */ undefined, + false, + buyFeeBps ? BigNumber.from(buyFeeBps) : undefined, + sellFeeBps ? BigNumber.from(sellFeeBps) : undefined + ) + } + + private static toPool({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOut }: V3PoolInRoute): Pool { + return new Pool( + RouterTradeAdapter.toToken(tokenIn), + RouterTradeAdapter.toToken(tokenOut), + parseInt(fee) as FeeAmount, + sqrtRatioX96, + liquidity, + parseInt(tickCurrent) + ) + } + + private static toPair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair => { + return new Pair( + CurrencyAmount.fromRawAmount(RouterTradeAdapter.toToken(reserve0.token), reserve0.quotient), + CurrencyAmount.fromRawAmount(RouterTradeAdapter.toToken(reserve1.token), reserve1.quotient) + ) + } + + private static isVersionedRoute( + type: PoolType, + route: (V3PoolInRoute | V2PoolInRoute)[] + ): route is T[] { + return route.every((pool) => pool.type === type) + } +} + +export class NativeCurrency extends SdkNativeCurrency { + constructor(chainId: number, decimals: number, symbol?: string, name?: string) { + if (chainId === ChainId.MAINNET) { + return Ether.onChain(chainId) + } + super(chainId, decimals, symbol, name) + } + + equals(currency: Currency): boolean { + return currency.isNative && currency.chainId === this.chainId + } + + get wrapped(): Token { + const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId as ChainId] + if (!wrapped) { + throw new Error('unsupported chain') + } + + return wrapped + } +} From 4450fbe0d4104943039dadf1e9ad35274f0d3d6c Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 19 Mar 2024 15:55:38 -0400 Subject: [PATCH 02/27] Add constants and wire up test --- src/utils/constants.ts | 169 ++++++++++++++++++++++++++++++++ src/utils/routerTradeAdapter.ts | 90 ++++------------- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 82 +++++++++++++++- 4 files changed, 269 insertions(+), 74 deletions(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index ee20415..eaa69b7 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,3 +1,4 @@ +import { ChainId, Token } from '@uniswap/sdk-core' import { BigNumber } from 'ethers' type ChainConfig = { @@ -106,6 +107,174 @@ const CHAIN_CONFIGS: { [key: number]: ChainConfig } = { }, } +export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { + [ChainId.MAINNET]: new Token( + 1, + '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.GOERLI]: new Token( + 5, + '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.SEPOLIA]: new Token( + 11155111, + '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.BNB]: new Token( + 56, + '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', + 18, + 'WBNB', + 'Wrapped BNB' + ), + [ChainId.OPTIMISM]: new Token( + ChainId.OPTIMISM, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.OPTIMISM_GOERLI]: new Token( + ChainId.OPTIMISM_GOERLI, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.OPTIMISM_SEPOLIA]: new Token( + ChainId.OPTIMISM_SEPOLIA, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ARBITRUM_ONE]: new Token( + ChainId.ARBITRUM_ONE, + '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ARBITRUM_GOERLI]: new Token( + ChainId.ARBITRUM_GOERLI, + '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ARBITRUM_SEPOLIA]: new Token( + ChainId.ARBITRUM_SEPOLIA, + '0xc556bAe1e86B2aE9c22eA5E036b07E55E7596074', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.POLYGON]: new Token( + ChainId.POLYGON, + '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + 18, + 'WMATIC', + 'Wrapped MATIC' + ), + [ChainId.POLYGON_MUMBAI]: new Token( + ChainId.POLYGON_MUMBAI, + '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889', + 18, + 'WMATIC', + 'Wrapped MATIC' + ), + + // The Celo native currency 'CELO' implements the erc-20 token standard + [ChainId.CELO]: new Token( + ChainId.CELO, + '0x471EcE3750Da237f93B8E339c536989b8978a438', + 18, + 'CELO', + 'Celo native asset' + ), + [ChainId.CELO_ALFAJORES]: new Token( + ChainId.CELO_ALFAJORES, + '0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9', + 18, + 'CELO', + 'Celo native asset' + ), + [ChainId.GNOSIS]: new Token( + ChainId.GNOSIS, + '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', + 18, + 'WXDAI', + 'Wrapped XDAI on Gnosis' + ), + [ChainId.MOONBEAM]: new Token( + ChainId.MOONBEAM, + '0xAcc15dC74880C9944775448304B263D191c6077F', + 18, + 'WGLMR', + 'Wrapped GLMR' + ), + [ChainId.AVALANCHE]: new Token( + ChainId.AVALANCHE, + '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', + 18, + 'WAVAX', + 'Wrapped AVAX' + ), + [ChainId.BASE]: new Token( + ChainId.BASE, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.BASE_GOERLI]: new Token( + ChainId.BASE_GOERLI, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ROOTSTOCK]: new Token( + ChainId.ROOTSTOCK, + '0x542fDA317318eBF1d3DEAf76E0b632741A7e677d', + 18, + 'WRBTC', + 'Wrapped BTC' + ), + [ChainId.ZORA]: new Token( + ChainId.ZORA, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.ZORA_SEPOLIA]: new Token( + ChainId.ZORA_SEPOLIA, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether' + ), + [ChainId.BLAST]: new Token( + ChainId.BLAST, + '0x4300000000000000000000000000000000000004', + 18, + 'WETH', + 'Wrapped Ether' + ), +}; + +export const SUPPORTED_CHAINS = Object.keys(CHAIN_CONFIGS).map((chainId) => parseInt(chainId)) + export const UNIVERSAL_ROUTER_ADDRESS = (chainId: number): string => { if (!(chainId in CHAIN_CONFIGS)) throw new Error(`Universal Router not deployed on chain ${chainId}`) return CHAIN_CONFIGS[chainId].router diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 167f048..3cea10c 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -1,5 +1,6 @@ import { MixedRouteSDK, Trade as RouterTrade } from '@uniswap/router-sdk' import { + ChainId, Currency, CurrencyAmount, Ether, @@ -10,60 +11,7 @@ import { import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' import { BigNumber } from 'ethers' -import { ETH_ADDRESS, WETH_ADDRESS } from './constants' - -export enum ChainId { - MAINNET = 1, - OPTIMISM = 10, - POLYGON = 137, - ARBITRUM = 42161, - BNB = 56, - BASE = 8453, -} - -export const WRAPPED_NATIVE_CURRENCY = { - [ChainId.MAINNET]: new Token( - ChainId.MAINNET, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.POLYGON]: new Token( - ChainId.POLYGON, - '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', - 18, - 'WMATIC', - 'Wrapped MATIC' - ), - [ChainId.OPTIMISM]: new Token( - ChainId.OPTIMISM, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.ARBITRUM]: new Token( - ChainId.ARBITRUM, - '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.BNB]: new Token(ChainId.BNB, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB', 'Wrapped BNB'), - [ChainId.BASE]: new Token(ChainId.BASE, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), -} - -export type ClassicInput = { - readonly token: string - readonly amount: string -} - -export type ClassicOutput = { - readonly token: string - readonly amount: string - readonly recipient: string -} +import { ETH_ADDRESS, WETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' export type TokenInRoute = { address: string @@ -110,11 +58,6 @@ export type V3PoolInRoute = { } export type PartialClassicQuote = { - chainId: number - swapper: string - input: ClassicInput - output: ClassicOutput - slippage: number tradeType: TradeType route: Array<(V3PoolInRoute | V2PoolInRoute)[]> } @@ -130,23 +73,26 @@ interface RouteResult { export const isNativeCurrency = (address: string, chainId: number) => address.toLowerCase() === WETH_ADDRESS(chainId).toLowerCase() || address.toLowerCase() === ETH_ADDRESS.toLowerCase() +// Helper class to convert routing-specific quote entities to RouterTrade entities +// the returned RouterTrade can then be used to build the UniswapTrade entity in this package export class RouterTradeAdapter { static fromClassicQuote(quote: PartialClassicQuote) { - const { route, input, output, chainId } = quote + const { route } = quote const tokenIn = route[0]?.[0]?.tokenIn const tokenOut = route[0]?.[route[0]?.length - 1]?.tokenOut if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present') + if (tokenIn.chainId !== tokenOut.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId') - const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(input.token, chainId), tokenIn) - const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(output.token, chainId), tokenOut) + const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenIn.address, tokenIn.chainId), tokenIn) + const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenOut.address, tokenIn.chainId), tokenOut) - const typedRoutes: RouteResult[] = route.map((route) => { - if (route.length === 0) { + const typedRoutes: RouteResult[] = route.map((subRoute) => { + if (subRoute.length === 0) { throw new Error('Expected route to have at least one pair or pool') } - const rawAmountIn = route[0].amountIn - const rawAmountOut = route[route.length - 1].amountOut + const rawAmountIn = subRoute[0].amountIn + const rawAmountOut = subRoute[subRoute.length - 1].amountOut if (!rawAmountIn || !rawAmountOut) { throw new Error('Expected both raw amountIn and raw amountOut to be present') @@ -155,19 +101,19 @@ export class RouterTradeAdapter { const inputAmount = CurrencyAmount.fromRawAmount(parsedCurrencyIn, rawAmountIn) const outputAmount = CurrencyAmount.fromRawAmount(parsedCurrencyOut, rawAmountOut) - const isOnlyV2 = RouterTradeAdapter.isVersionedRoute(PoolType.V2Pool, route) - const isOnlyV3 = RouterTradeAdapter.isVersionedRoute(PoolType.V3Pool, route) + const isOnlyV2 = RouterTradeAdapter.isVersionedRoute(PoolType.V2Pool, subRoute) + const isOnlyV3 = RouterTradeAdapter.isVersionedRoute(PoolType.V3Pool, subRoute) return { routev3: isOnlyV3 - ? new V3Route(route.map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) + ? new V3Route(subRoute.map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) : null, routev2: isOnlyV2 - ? new V2Route(route.map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) + ? new V2Route(subRoute.map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) : null, mixedRoute: !isOnlyV3 && !isOnlyV2 - ? new MixedRouteSDK(route.map(RouterTradeAdapter.toPoolOrPair), parsedCurrencyIn, parsedCurrencyOut) + ? new MixedRouteSDK(subRoute.map(RouterTradeAdapter.toPoolOrPair), parsedCurrencyIn, parsedCurrencyOut) : null, inputAmount, outputAmount, @@ -252,7 +198,7 @@ export class RouterTradeAdapter { } export class NativeCurrency extends SdkNativeCurrency { - constructor(chainId: number, decimals: number, symbol?: string, name?: string) { + constructor(chainId: ChainId, decimals: number, symbol?: string, name?: string) { if (chainId === ChainId.MAINNET) { return Ether.onChain(chainId) } diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index 2ac15bd..5a08265 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -9,9 +9,10 @@ import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions } from '@uniswap/v import { generatePermitSignature, toInputPermit, makePermit, generateEip2098PermitSignature } from './utils/permit2' import { CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { registerFixture } from './forge/writeInterop' -import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC } from './utils/uniswapData' +import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC, WETH } from './utils/uniswapData' import { hexToDecimalString } from './utils/hexToDecimalString' import { FORGE_PERMIT2_ADDRESS, FORGE_ROUTER_ADDRESS, TEST_FEE_RECIPIENT_ADDRESS } from './utils/addresses' +import { PartialClassicQuote, PoolType, RouterTradeAdapter } from '../src/utils/routerTradeAdapter' const FORK_BLOCK = 16075500 @@ -693,4 +694,83 @@ describe('Uniswap', () => { }).to.throw('Flat fee amount greater than minimumAmountOut') }) }) + + describe.only("RouterTradeAdapter", () => { + it("encoded UR parameters are the same from ClassicQuote and equivalent SDK", async () => { + const inputAmount = utils.parseEther('1000').toString() + const outputAmount = utils.parseUnits('1000', 6).toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount); + const rawOutputAmount = CurrencyAmount.fromRawAmount(USDC, outputAmount); + const classicQuote: PartialClassicQuote = { + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + { + type: PoolType.V2Pool, + tokenIn: { + address: DAI.address, + chainId: 1, + symbol: DAI.symbol!, + decimals: String(DAI.decimals), + }, + tokenOut: { + address: USDC.address, + chainId: 1, + symbol: USDC.symbol!, + decimals: String(USDC.decimals), + }, + reserve0: { + token: { + address: DAI.address, + chainId: 1, + symbol: DAI.symbol!, + decimals: String(DAI.decimals), + }, + quotient: USDC_DAI_V2.reserve0.quotient.toString(), + }, + reserve1: { + token: { + address: USDC.address, + chainId: 1, + symbol: USDC.symbol!, + decimals: String(USDC.decimals), + }, + quotient: USDC_DAI_V2.reserve1.quotient.toString(), + }, + amountIn: inputAmount, + amountOut: outputAmount + } + ] + ] + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote); + const opts = swapOptions({ }) + const uniswapTrade = new UniswapTrade(routerTrade, opts); + // ensure values are correct in UniswapTrade entity + expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()); + expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()); + // check route + expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1); + expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(DAI.address); + expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(USDC.address); + // check swap + expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(DAI.address); + expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(USDC.address); + // there will be some slippage on the output amount + expect(uniswapTrade.trade.minimumAmountOut(opts.slippageTolerance, rawOutputAmount).lessThan(uniswapTrade.trade.outputAmount)).to.be.true; + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)); + expect(hexToDecimalString(methodParameters.value)).to.eq('0'); + + const trade = new V2Trade( + new RouteV2([USDC_DAI_V2], DAI, USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const methodParametersV2 = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(methodParametersV2.calldata) + }) + }) }) From 21887917d5e51860303a077a74e11a850e13739d Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 19 Mar 2024 15:56:24 -0400 Subject: [PATCH 03/27] fix tests --- test/uniswapTrades.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index 5a08265..bdbeb5c 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -695,8 +695,8 @@ describe('Uniswap', () => { }) }) - describe.only("RouterTradeAdapter", () => { - it("encoded UR parameters are the same from ClassicQuote and equivalent SDK", async () => { + describe("RouterTradeAdapter", () => { + it("encoded UR parameters are the same using fromClassicQuote and raw SDK", async () => { const inputAmount = utils.parseEther('1000').toString() const outputAmount = utils.parseUnits('1000', 6).toString() const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount); From 9ee7f740031ca55bbf927698ef7eb3dabf315d80 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 19 Mar 2024 17:47:56 -0400 Subject: [PATCH 04/27] Add v2 tests --- src/utils/constants.ts | 60 +------ src/utils/routerTradeAdapter.ts | 26 +-- test/uniswapTrades.test.ts | 289 +++++++++++++++++++++++++------- 3 files changed, 248 insertions(+), 127 deletions(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index eaa69b7..0c10fbc 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -108,34 +108,10 @@ const CHAIN_CONFIGS: { [key: number]: ChainConfig } = { } export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { - [ChainId.MAINNET]: new Token( - 1, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.GOERLI]: new Token( - 5, - '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.SEPOLIA]: new Token( - 11155111, - '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.BNB]: new Token( - 56, - '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', - 18, - 'WBNB', - 'Wrapped BNB' - ), + [ChainId.MAINNET]: new Token(1, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'Wrapped Ether'), + [ChainId.GOERLI]: new Token(5, '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 18, 'WETH', 'Wrapped Ether'), + [ChainId.SEPOLIA]: new Token(11155111, '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14', 18, 'WETH', 'Wrapped Ether'), + [ChainId.BNB]: new Token(56, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB', 'Wrapped BNB'), [ChainId.OPTIMISM]: new Token( ChainId.OPTIMISM, '0x4200000000000000000000000000000000000006', @@ -229,13 +205,7 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { 'WAVAX', 'Wrapped AVAX' ), - [ChainId.BASE]: new Token( - ChainId.BASE, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), + [ChainId.BASE]: new Token(ChainId.BASE, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), [ChainId.BASE_GOERLI]: new Token( ChainId.BASE_GOERLI, '0x4200000000000000000000000000000000000006', @@ -250,13 +220,7 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { 'WRBTC', 'Wrapped BTC' ), - [ChainId.ZORA]: new Token( - ChainId.ZORA, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), + [ChainId.ZORA]: new Token(ChainId.ZORA, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), [ChainId.ZORA_SEPOLIA]: new Token( ChainId.ZORA_SEPOLIA, '0x4200000000000000000000000000000000000006', @@ -264,16 +228,8 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { 'WETH', 'Wrapped Ether' ), - [ChainId.BLAST]: new Token( - ChainId.BLAST, - '0x4300000000000000000000000000000000000004', - 18, - 'WETH', - 'Wrapped Ether' - ), -}; - -export const SUPPORTED_CHAINS = Object.keys(CHAIN_CONFIGS).map((chainId) => parseInt(chainId)) + [ChainId.BLAST]: new Token(ChainId.BLAST, '0x4300000000000000000000000000000000000004', 18, 'WETH', 'Wrapped Ether'), +} export const UNIVERSAL_ROUTER_ADDRESS = (chainId: number): string => { if (!(chainId in CHAIN_CONFIGS)) throw new Error(`Universal Router not deployed on chain ${chainId}`) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 3cea10c..1005b74 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -11,7 +11,7 @@ import { import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' import { BigNumber } from 'ethers' -import { ETH_ADDRESS, WETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' +import { ETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' export type TokenInRoute = { address: string @@ -58,6 +58,9 @@ export type V3PoolInRoute = { } export type PartialClassicQuote = { + // We still need tokenIn/Out to support native currency + tokenIn: string + tokenOut: string tradeType: TradeType route: Array<(V3PoolInRoute | V2PoolInRoute)[]> } @@ -70,22 +73,25 @@ interface RouteResult { outputAmount: CurrencyAmount } -export const isNativeCurrency = (address: string, chainId: number) => - address.toLowerCase() === WETH_ADDRESS(chainId).toLowerCase() || address.toLowerCase() === ETH_ADDRESS.toLowerCase() +export const isNativeCurrency = (address: string) => + address.toLowerCase() === ETH_ADDRESS.toLowerCase() // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the UniswapTrade entity in this package export class RouterTradeAdapter { static fromClassicQuote(quote: PartialClassicQuote) { - const { route } = quote + const { route, tokenIn, tokenOut } = quote - const tokenIn = route[0]?.[0]?.tokenIn - const tokenOut = route[0]?.[route[0]?.length - 1]?.tokenOut - if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present') - if (tokenIn.chainId !== tokenOut.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId') + const tokenInData = route[0]?.[0]?.tokenIn + const tokenOutData = route[0]?.[route[0]?.length - 1]?.tokenOut + if (!tokenInData || !tokenOutData) throw new Error('Expected both tokenIn and tokenOut to be present') + if (tokenInData.chainId !== tokenOutData.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId') - const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenIn.address, tokenIn.chainId), tokenIn) - const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenOut.address, tokenIn.chainId), tokenOut) + const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenIn), tokenInData) + const parsedCurrencyOut = RouterTradeAdapter.toCurrency( + isNativeCurrency(tokenOut), + tokenOutData + ) const typedRoutes: RouteResult[] = route.map((subRoute) => { if (subRoute.length === 0) { diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index bdbeb5c..cb643fe 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -7,12 +7,13 @@ import { MixedRouteTrade, MixedRouteSDK } from '@uniswap/router-sdk' import { Trade as V2Trade, Pair, Route as RouteV2 } from '@uniswap/v2-sdk' import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions } from '@uniswap/v3-sdk' import { generatePermitSignature, toInputPermit, makePermit, generateEip2098PermitSignature } from './utils/permit2' -import { CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' +import { CurrencyAmount, Ether, Percent, Token, TradeType } from '@uniswap/sdk-core' import { registerFixture } from './forge/writeInterop' import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC, WETH } from './utils/uniswapData' import { hexToDecimalString } from './utils/hexToDecimalString' import { FORGE_PERMIT2_ADDRESS, FORGE_ROUTER_ADDRESS, TEST_FEE_RECIPIENT_ADDRESS } from './utils/addresses' -import { PartialClassicQuote, PoolType, RouterTradeAdapter } from '../src/utils/routerTradeAdapter' +import { PartialClassicQuote, PoolType, RouterTradeAdapter, V2PoolInRoute } from '../src/utils/routerTradeAdapter' +import { ETH_ADDRESS } from '../src/utils/constants' const FORK_BLOCK = 16075500 @@ -695,82 +696,240 @@ describe('Uniswap', () => { }) }) - describe("RouterTradeAdapter", () => { - it("encoded UR parameters are the same using fromClassicQuote and raw SDK", async () => { + describe.only('RouterTradeAdapter', () => { + const mockV2PoolInRoute = (pair: Pair, tokenIn: Token, tokenOut: Token, amountIn: string, amountOut: string): V2PoolInRoute => { + // get token0 and token1 + const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut + const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn + + return { + type: PoolType.V2Pool, + tokenIn: { + address: tokenIn.address, + chainId: 1, + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), + }, + tokenOut: { + address: tokenOut.address, + chainId: 1, + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), + }, + reserve0: { + token: { + address: token0.address, + chainId: 1, + symbol: token0.symbol!, + decimals: String(token0.decimals), + }, + quotient: pair.reserve0.quotient.toString(), + }, + reserve1: { + token: { + address: token1.address, + chainId: 1, + symbol: token1.symbol!, + decimals: String(token1.decimals), + }, + quotient: pair.reserve1.quotient.toString(), + }, + amountIn, + amountOut + } + } + + it('v2 - encoded UR parameters are the same using fromClassicQuote and raw SDK', async () => { const inputAmount = utils.parseEther('1000').toString() - const outputAmount = utils.parseUnits('1000', 6).toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount); - const rawOutputAmount = CurrencyAmount.fromRawAmount(USDC, outputAmount); + const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([USDC_DAI_V2], DAI, USDC), rawInputAmount, TradeType.EXACT_INPUT) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, tradeType: TradeType.EXACT_INPUT, route: [ [ - { - type: PoolType.V2Pool, - tokenIn: { - address: DAI.address, - chainId: 1, - symbol: DAI.symbol!, - decimals: String(DAI.decimals), - }, - tokenOut: { - address: USDC.address, - chainId: 1, - symbol: USDC.symbol!, - decimals: String(USDC.decimals), - }, - reserve0: { - token: { - address: DAI.address, - chainId: 1, - symbol: DAI.symbol!, - decimals: String(DAI.decimals), - }, - quotient: USDC_DAI_V2.reserve0.quotient.toString(), - }, - reserve1: { - token: { - address: USDC.address, - chainId: 1, - symbol: USDC.symbol!, - decimals: String(USDC.decimals), - }, - quotient: USDC_DAI_V2.reserve1.quotient.toString(), - }, - amountIn: inputAmount, - amountOut: outputAmount - } - ] - ] + mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, inputAmount, outputAmount) + ], + ], } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote); - const opts = swapOptions({ }) - const uniswapTrade = new UniswapTrade(routerTrade, opts); + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) // ensure values are correct in UniswapTrade entity - expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()); - expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()); + expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) + expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) // check route - expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1); - expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(DAI.address); - expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(USDC.address); + expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) + expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(DAI.address) + expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(USDC.address) // check swap - expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(DAI.address); - expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(USDC.address); + expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(DAI.address) + expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(USDC.address) // there will be some slippage on the output amount - expect(uniswapTrade.trade.minimumAmountOut(opts.slippageTolerance, rawOutputAmount).lessThan(uniswapTrade.trade.outputAmount)).to.be.true; - + expect( + uniswapTrade.trade + .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) + .lessThan(uniswapTrade.trade.outputAmount) + ).to.be.true + // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)); - expect(hexToDecimalString(methodParameters.value)).to.eq('0'); + const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') - const trade = new V2Trade( - new RouteV2([USDC_DAI_V2], DAI, USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const methodParametersV2 = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(methodParametersV2.calldata) + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles weth properly', async () => { + const inputAmount = utils.parseEther('1').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], WETH, USDC), rawInputAmount, TradeType.EXACT_INPUT) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount) + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + // ensure values are correct in UniswapTrade entity + expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) + expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) + // check route + expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) + expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) + // check swap + expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) + // there will be some slippage on the output amount + expect( + uniswapTrade.trade + .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) + .lessThan(uniswapTrade.trade.outputAmount) + ).to.be.true + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth input properly', async () => { + const inputAmount = utils.parseEther('1').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, TradeType.EXACT_INPUT) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: ETH_ADDRESS, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount) + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + // ensure values are correct in UniswapTrade entity + expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) + expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) + // check route + expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) + expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) + // check swap + expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) + // there will be some slippage on the output amount + expect( + uniswapTrade.trade + .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) + .lessThan(uniswapTrade.trade.outputAmount) + ).to.be.true + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth output properly', async () => { + const inputAmount = utils.parseUnits('1000', 6).toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(USDC, inputAmount) + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], USDC, Ether.onChain(1)), rawInputAmount, TradeType.EXACT_INPUT) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseEther(rawOutputAmount.toExact()).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: USDC.address, + tokenOut: ETH_ADDRESS, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, inputAmount, outputAmount) + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + // ensure values are correct in UniswapTrade entity + expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) + expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) + // check route + expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) + expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) + // check swap + expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) + expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) + // there will be some slippage on the output amount + expect( + uniswapTrade.trade + .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) + .lessThan(uniswapTrade.trade.outputAmount) + ).to.be.true + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) }) }) }) From 2b4ffea5c92781424a2c1f124dd08c0757a687af Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 19 Mar 2024 18:40:30 -0400 Subject: [PATCH 05/27] yarn prettier --- src/utils/routerTradeAdapter.ts | 11 +- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 287 ++++++++++++++++++++++---------- 3 files changed, 206 insertions(+), 94 deletions(-) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 1005b74..b24f40e 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -73,8 +73,7 @@ interface RouteResult { outputAmount: CurrencyAmount } -export const isNativeCurrency = (address: string) => - address.toLowerCase() === ETH_ADDRESS.toLowerCase() +export const isNativeCurrency = (address: string) => address.toLowerCase() === ETH_ADDRESS.toLowerCase() // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the UniswapTrade entity in this package @@ -85,13 +84,11 @@ export class RouterTradeAdapter { const tokenInData = route[0]?.[0]?.tokenIn const tokenOutData = route[0]?.[route[0]?.length - 1]?.tokenOut if (!tokenInData || !tokenOutData) throw new Error('Expected both tokenIn and tokenOut to be present') - if (tokenInData.chainId !== tokenOutData.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId') + if (tokenInData.chainId !== tokenOutData.chainId) + throw new Error('Expected tokenIn and tokenOut to be have same chainId') const parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenIn), tokenInData) - const parsedCurrencyOut = RouterTradeAdapter.toCurrency( - isNativeCurrency(tokenOut), - tokenOutData - ) + const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenOut), tokenOutData) const typedRoutes: RouteResult[] = route.map((subRoute) => { if (subRoute.length === 0) { diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +} diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index cb643fe..e9de154 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -12,7 +12,13 @@ import { registerFixture } from './forge/writeInterop' import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC, WETH } from './utils/uniswapData' import { hexToDecimalString } from './utils/hexToDecimalString' import { FORGE_PERMIT2_ADDRESS, FORGE_ROUTER_ADDRESS, TEST_FEE_RECIPIENT_ADDRESS } from './utils/addresses' -import { PartialClassicQuote, PoolType, RouterTradeAdapter, V2PoolInRoute } from '../src/utils/routerTradeAdapter' +import { + PartialClassicQuote, + PoolType, + RouterTradeAdapter, + V2PoolInRoute, + V3PoolInRoute, +} from '../src/utils/routerTradeAdapter' import { ETH_ADDRESS } from '../src/utils/constants' const FORK_BLOCK = 16075500 @@ -697,7 +703,13 @@ describe('Uniswap', () => { }) describe.only('RouterTradeAdapter', () => { - const mockV2PoolInRoute = (pair: Pair, tokenIn: Token, tokenOut: Token, amountIn: string, amountOut: string): V2PoolInRoute => { + const mockV2PoolInRoute = ( + pair: Pair, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V2PoolInRoute => { // get token0 and token1 const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn @@ -735,11 +747,41 @@ describe('Uniswap', () => { quotient: pair.reserve1.quotient.toString(), }, amountIn, - amountOut + amountOut, } } - it('v2 - encoded UR parameters are the same using fromClassicQuote and raw SDK', async () => { + const mockV3PoolInRoute = ( + pool: Pool, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V3PoolInRoute => { + return { + type: PoolType.V3Pool, + tokenIn: { + address: tokenIn.address, + chainId: 1, + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), + }, + tokenOut: { + address: tokenOut.address, + chainId: 1, + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), + }, + sqrtRatioX96: pool.sqrtRatioX96.toString(), + liquidity: pool.liquidity.toString(), + tickCurrent: pool.tickCurrent.toString(), + fee: pool.fee.toString(), + amountIn, + amountOut, + } + } + + it('v2 - erc20 <> erc20', async () => { const inputAmount = utils.parseEther('1000').toString() const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount) @@ -754,40 +796,52 @@ describe('Uniswap', () => { tokenIn: DAI.address, tokenOut: USDC.address, tradeType: TradeType.EXACT_INPUT, - route: [ - [ - mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, inputAmount, outputAmount) - ], - ], + route: [[mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, inputAmount, outputAmount)]], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure values are correct in UniswapTrade entity - expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) - expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) - // check route - expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) - expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(DAI.address) - expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(USDC.address) - // check swap - expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(DAI.address) - expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(USDC.address) - // there will be some slippage on the output amount - expect( - uniswapTrade.trade - .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) - .lessThan(uniswapTrade.trade.outputAmount) - ).to.be.true // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) expect(hexToDecimalString(methodParameters.value)).to.eq('0') // ensure that the encoded call parameters are the same expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) }) - it('v2 - handles weth properly', async () => { + it('v3 - erc20 <> erc20', async () => { + const inputAmount = utils.parseEther('1000').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([USDC_DAI_V3], DAI, USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [[mockV3PoolInRoute(USDC_DAI_V3, DAI, USDC, inputAmount, outputAmount)]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles weth input properly', async () => { const inputAmount = utils.parseEther('1').toString() const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) @@ -802,45 +856,98 @@ describe('Uniswap', () => { tokenIn: WETH.address, tokenOut: USDC.address, tradeType: TradeType.EXACT_INPUT, + route: [[mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount)]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles weth input properly', async () => { + const inputAmount = utils.parseEther('1').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], WETH, USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [[mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, inputAmount, outputAmount)]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth input properly', async () => { + const inputAmount = utils.parseEther('1').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) + + const opts = swapOptions({}) + const trade = new V2Trade( + new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: ETH_ADDRESS, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, route: [ [ - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount) + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount), ], ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure values are correct in UniswapTrade entity - expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) - expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) - // check route - expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) - expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) - // check swap - expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) - // there will be some slippage on the output amount - expect( - uniswapTrade.trade - .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) - .lessThan(uniswapTrade.trade.outputAmount) - ).to.be.true // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) // ensure that the encoded call parameters are the same expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) }) - it('v2 - handles eth input properly', async () => { + it('v3 - handles eth input properly', async () => { const inputAmount = utils.parseEther('1').toString() const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) const opts = swapOptions({}) - const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, TradeType.EXACT_INPUT) + const trade = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], Ether.onChain(1), USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const rawOutputAmount = trade.outputAmount @@ -852,32 +959,16 @@ describe('Uniswap', () => { tradeType: TradeType.EXACT_INPUT, route: [ [ - // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount) + // WETH here since all pools use WETH + mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, inputAmount, outputAmount), ], ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure values are correct in UniswapTrade entity - expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) - expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) - // check route - expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) - expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) - // check swap - expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) - // there will be some slippage on the output amount - expect( - uniswapTrade.trade - .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) - .lessThan(uniswapTrade.trade.outputAmount) - ).to.be.true // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) // ensure that the encoded call parameters are the same @@ -888,7 +979,47 @@ describe('Uniswap', () => { const inputAmount = utils.parseUnits('1000', 6).toString() const rawInputAmount = CurrencyAmount.fromRawAmount(USDC, inputAmount) const opts = swapOptions({}) - const trade = new V2Trade(new RouteV2([WETH_USDC_V2], USDC, Ether.onChain(1)), rawInputAmount, TradeType.EXACT_INPUT) + const trade = new V2Trade( + new RouteV2([WETH_USDC_V2], USDC, Ether.onChain(1)), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseEther(rawOutputAmount.toExact()).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: USDC.address, + tokenOut: ETH_ADDRESS, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, inputAmount, outputAmount), + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles eth output properly', async () => { + const inputAmount = utils.parseUnits('1000', 6).toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(USDC, inputAmount) + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], USDC, Ether.onChain(1)), + rawInputAmount, + TradeType.EXACT_INPUT + ) const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const rawOutputAmount = trade.outputAmount @@ -901,31 +1032,15 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, inputAmount, outputAmount) + mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, inputAmount, outputAmount), ], ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure values are correct in UniswapTrade entity - expect(uniswapTrade.trade.inputAmount.toExact()).to.eq(rawInputAmount.toExact()) - expect(uniswapTrade.trade.outputAmount.toExact()).to.eq(rawOutputAmount.toExact()) - // check route - expect(uniswapTrade.trade.routes[0].pools.length).to.eq(1) - expect(uniswapTrade.trade.routes[0].pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.routes[0].pools[0].token1.address).to.eq(WETH.address) - // check swap - expect(uniswapTrade.trade.swaps[0].route.pools[0].token0.address).to.eq(USDC.address) - expect(uniswapTrade.trade.swaps[0].route.pools[0].token1.address).to.eq(WETH.address) - // there will be some slippage on the output amount - expect( - uniswapTrade.trade - .minimumAmountOut(opts.slippageTolerance, rawOutputAmount) - .lessThan(uniswapTrade.trade.outputAmount) - ).to.be.true // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(routerTrade, opts)) + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) expect(hexToDecimalString(methodParameters.value)).to.eq('0') // ensure that the encoded call parameters are the same From 2c7987bb3f0ec581cea0f965d251bb0a6f20bba1 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 10:09:21 -0400 Subject: [PATCH 06/27] comments --- src/utils/routerTradeAdapter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index b24f40e..656a80d 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -58,7 +58,7 @@ export type V3PoolInRoute = { } export type PartialClassicQuote = { - // We still need tokenIn/Out to support native currency + // We need tokenIn/Out to support native currency tokenIn: string tokenOut: string tradeType: TradeType @@ -78,6 +78,7 @@ export const isNativeCurrency = (address: string) => address.toLowerCase() === E // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the UniswapTrade entity in this package export class RouterTradeAdapter { + // Generate a RouterTrade using fields from a classic quote response static fromClassicQuote(quote: PartialClassicQuote) { const { route, tokenIn, tokenOut } = quote From 55a164a7d5f21f63cfdba4038bff67980b31417a Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 10:15:57 -0400 Subject: [PATCH 07/27] .only --- test/uniswapTrades.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index e9de154..abf578d 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -702,7 +702,7 @@ describe('Uniswap', () => { }) }) - describe.only('RouterTradeAdapter', () => { + describe('RouterTradeAdapter', () => { const mockV2PoolInRoute = ( pair: Pair, tokenIn: Token, From 9d0d485de3f83f5dc1db0234e065cfce19dbecd9 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 11:34:30 -0400 Subject: [PATCH 08/27] Allow for payer as User as swapoptions --- src/entities/protocols/uniswap.ts | 33 ++++++++++++++++--------------- src/index.ts | 1 + src/utils/routerTradeAdapter.ts | 4 ++-- test/forge/interop.json | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index 9c05842..946f01a 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -31,6 +31,7 @@ export type FlatFeeOptions = { // so we extend swap options with the permit2 permit // when safe mode is enabled, the SDK will add an extra ETH sweep for security export type SwapOptions = Omit & { + payerIsUser?: boolean inputTokenPermit?: Permit2Permit flatFee?: FlatFeeOptions safeMode?: boolean @@ -53,7 +54,10 @@ export class UniswapTrade implements Command { } encode(planner: RoutePlanner, _config: TradeConfig): void { - let payerIsUser = true + if(this.options.payerIsUser === undefined) { + // default to user as payer + Object.assign(this.options, { payerIsUser: true }) + } // If the input currency is the native currency, we need to wrap it with the router as the recipient if (this.trade.inputAmount.currency.isNative) { @@ -63,7 +67,7 @@ export class UniswapTrade implements Command { this.trade.maximumAmountIn(this.options.slippageTolerance).quotient.toString(), ]) // since WETH is now owned by the router, the router pays for inputs - payerIsUser = false + Object.assign(this.options, { payerIsUser: false }) } // The overall recipient at the end of the trade, SENDER_AS_RECIPIENT uses the msg.sender this.options.recipient = this.options.recipient ?? SENDER_AS_RECIPIENT @@ -81,13 +85,13 @@ export class UniswapTrade implements Command { for (const swap of this.trade.swaps) { switch (swap.route.protocol) { case Protocol.V2: - addV2Swap(planner, swap, this.trade.tradeType, this.options, payerIsUser, routerMustCustody) + addV2Swap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) break case Protocol.V3: - addV3Swap(planner, swap, this.trade.tradeType, this.options, payerIsUser, routerMustCustody) + addV3Swap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) break case Protocol.MIXED: - addMixedSwap(planner, swap, this.trade.tradeType, this.options, payerIsUser, routerMustCustody) + addMixedSwap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) break default: throw new Error('UNSUPPORTED_TRADE_PROTOCOL') @@ -165,7 +169,6 @@ function addV2Swap( { route, inputAmount, outputAmount }: Swap, tradeType: TradeType, options: SwapOptions, - payerIsUser: boolean, routerMustCustody: boolean ): void { const trade = new V2Trade( @@ -181,7 +184,7 @@ function addV2Swap( trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), route.path.map((pool) => pool.address), - payerIsUser, + options.payerIsUser, ]) } else if (tradeType == TradeType.EXACT_OUTPUT) { planner.addCommand(CommandType.V2_SWAP_EXACT_OUT, [ @@ -189,7 +192,7 @@ function addV2Swap( trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), route.path.map((pool) => pool.address), - payerIsUser, + options.payerIsUser, ]) } } @@ -200,7 +203,6 @@ function addV3Swap( { route, inputAmount, outputAmount }: Swap, tradeType: TradeType, options: SwapOptions, - payerIsUser: boolean, routerMustCustody: boolean ): void { const trade = V3Trade.createUncheckedTrade({ @@ -217,7 +219,7 @@ function addV3Swap( trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), path, - payerIsUser, + options.payerIsUser, ]) } else if (tradeType == TradeType.EXACT_OUTPUT) { planner.addCommand(CommandType.V3_SWAP_EXACT_OUT, [ @@ -225,7 +227,7 @@ function addV3Swap( trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), path, - payerIsUser, + options.payerIsUser, ]) } } @@ -236,7 +238,6 @@ function addMixedSwap( swap: Swap, tradeType: TradeType, options: SwapOptions, - payerIsUser: boolean, routerMustCustody: boolean ): void { const { route, inputAmount, outputAmount } = swap @@ -245,9 +246,9 @@ function addMixedSwap( // single hop, so it can be reduced to plain v2 or v3 swap logic if (route.pools.length === 1) { if (route.pools[0] instanceof Pool) { - return addV3Swap(planner, swap, tradeType, options, payerIsUser, routerMustCustody) + return addV3Swap(planner, swap, tradeType, options, routerMustCustody) } else if (route.pools[0] instanceof Pair) { - return addV2Swap(planner, swap, tradeType, options, payerIsUser, routerMustCustody) + return addV2Swap(planner, swap, tradeType, options, routerMustCustody) } else { throw new Error('Invalid route type') } @@ -302,7 +303,7 @@ function addMixedSwap( i == 0 ? amountIn : CONTRACT_BALANCE, // amountIn !isLastSectionInRoute(i) ? 0 : amountOut, // amountOut path, // path - payerIsUser && i === 0, // payerIsUser + options.payerIsUser && i === 0, // payerIsUser ]) } else { planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ @@ -310,7 +311,7 @@ function addMixedSwap( i === 0 ? amountIn : CONTRACT_BALANCE, // amountIn !isLastSectionInRoute(i) ? 0 : amountOut, // amountOutMin newRoute.path.map((pool) => pool.address), // path - payerIsUser && i === 0, + options.payerIsUser && i === 0, ]) } } diff --git a/src/index.ts b/src/index.ts index b966818..4c57682 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { SwapRouter } from './swapRouter' export * from './entities' +export * from './utils/routerTradeAdapter' export { RoutePlanner, CommandType } from './utils/routerCommands' export { UNIVERSAL_ROUTER_ADDRESS, diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 656a80d..fd5543a 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -110,10 +110,10 @@ export class RouterTradeAdapter { return { routev3: isOnlyV3 - ? new V3Route(subRoute.map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) + ? new V3Route((subRoute as V3PoolInRoute[]).map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) : null, routev2: isOnlyV2 - ? new V2Route(subRoute.map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) + ? new V2Route((subRoute as V2PoolInRoute[]).map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) : null, mixedRoute: !isOnlyV3 && !isOnlyV2 diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file From 31ef4c515475c6213fff61ca868c05a494a46d37 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 11:34:54 -0400 Subject: [PATCH 09/27] bump major --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f47528a..7fa9d35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/universal-router-sdk", - "version": "1.8.1", + "version": "2.0.0", "description": "sdk for integrating with the Universal Router contracts", "main": "dist/index.js", "typings": "dist/index.d.ts", From 265f327239b9bc9ec9fde95b6dbe9af4edacd6c5 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 13:14:43 -0400 Subject: [PATCH 10/27] prettier fix --- src/entities/protocols/uniswap.ts | 9 ++++++++- src/utils/routerTradeAdapter.ts | 12 ++++++++++-- test/forge/interop.json | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index 946f01a..f31a20e 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -54,7 +54,7 @@ export class UniswapTrade implements Command { } encode(planner: RoutePlanner, _config: TradeConfig): void { - if(this.options.payerIsUser === undefined) { + if (this.options.payerIsUser === undefined) { // default to user as payer Object.assign(this.options, { payerIsUser: true }) } @@ -177,6 +177,13 @@ function addV2Swap( tradeType ) + console.log( + 'ur-sdk', + options.slippageTolerance.toFixed(), + options.slippageTolerance.isPercent, + options.slippageTolerance + ) + if (tradeType == TradeType.EXACT_INPUT) { planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ // if native, we have to unwrap so keep in the router for now diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index fd5543a..935e789 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -110,10 +110,18 @@ export class RouterTradeAdapter { return { routev3: isOnlyV3 - ? new V3Route((subRoute as V3PoolInRoute[]).map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) + ? new V3Route( + (subRoute as V3PoolInRoute[]).map(RouterTradeAdapter.toPool), + parsedCurrencyIn, + parsedCurrencyOut + ) : null, routev2: isOnlyV2 - ? new V2Route((subRoute as V2PoolInRoute[]).map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) + ? new V2Route( + (subRoute as V2PoolInRoute[]).map(RouterTradeAdapter.toPair), + parsedCurrencyIn, + parsedCurrencyOut + ) : null, mixedRoute: !isOnlyV3 && !isOnlyV2 diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +} From 08d841a5e4792e7bf8619e1ce1c41ddeab534d3a Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 16:24:20 -0400 Subject: [PATCH 11/27] back down to jsbi 3.1.4 --- src/entities/protocols/uniswap.ts | 10 ++++++++-- yarn.lock | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index f31a20e..83db7bf 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -16,11 +16,12 @@ import { partitionMixedRouteByProtocol, } from '@uniswap/router-sdk' import { Permit2Permit } from '../../utils/inputTokens' -import { Currency, TradeType, CurrencyAmount, Percent } from '@uniswap/sdk-core' +import { Currency, TradeType, CurrencyAmount, Percent, Fraction } from '@uniswap/sdk-core' import { Command, RouterTradeType, TradeConfig } from '../Command' import { SENDER_AS_RECIPIENT, ROUTER_AS_RECIPIENT, CONTRACT_BALANCE, ETH_ADDRESS } from '../../utils/constants' import { encodeFeeBips } from '../../utils/numbers' import { BigNumber, BigNumberish } from 'ethers' +import JSBI from 'jsbi' export type FlatFeeOptions = { amount: BigNumberish @@ -177,11 +178,16 @@ function addV2Swap( tradeType ) + const ZERO = JSBI.BigInt(0); + console.log(typeof ZERO, ZERO instanceof JSBI) + console.log(new Fraction(ZERO) instanceof Fraction) + + console.log( 'ur-sdk', options.slippageTolerance.toFixed(), options.slippageTolerance.isPercent, - options.slippageTolerance + options.slippageTolerance.lessThan(new Fraction(ZERO)) ) if (tradeType == TradeType.EXACT_INPUT) { diff --git a/yarn.lock b/yarn.lock index 76a6047..8e80e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5728,9 +5728,9 @@ js-yaml@^3.13.1: esprima "^4.0.0" jsbi@^3.1.4: - version "3.2.5" - resolved "https://registry.npmjs.org/jsbi/-/jsbi-3.2.5.tgz" - integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + version "3.1.4" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" + integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== jsbn@~0.1.0: version "0.1.1" From 483a474789573727e341cd03c52c9f08decb3107 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 20 Mar 2024 16:49:41 -0400 Subject: [PATCH 12/27] v2-sdk to 4.3.0 --- package.json | 4 ++-- src/entities/protocols/uniswap.ts | 12 ------------ yarn.lock | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7fa9d35..590c2c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/universal-router-sdk", - "version": "2.0.0", + "version": "2.0.1", "description": "sdk for integrating with the Universal Router contracts", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -53,7 +53,7 @@ "@uniswap/router-sdk": "^1.9.0", "@uniswap/sdk-core": "^4.2.0", "@uniswap/universal-router": "1.6.0", - "@uniswap/v2-sdk": "^4.2.0", + "@uniswap/v2-sdk": "^4.3.0", "@uniswap/v3-sdk": "^3.11.0", "bignumber.js": "^9.0.2", "ethers": "^5.3.1" diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index 83db7bf..d78144a 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -178,18 +178,6 @@ function addV2Swap( tradeType ) - const ZERO = JSBI.BigInt(0); - console.log(typeof ZERO, ZERO instanceof JSBI) - console.log(new Fraction(ZERO) instanceof Fraction) - - - console.log( - 'ur-sdk', - options.slippageTolerance.toFixed(), - options.slippageTolerance.isPercent, - options.slippageTolerance.lessThan(new Fraction(ZERO)) - ) - if (tradeType == TradeType.EXACT_INPUT) { planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ // if native, we have to unwrap so keep in the router for now diff --git a/yarn.lock b/yarn.lock index 8e80e7f..6f53434 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2253,7 +2253,7 @@ resolved "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== -"@uniswap/v2-sdk@^4.2.0", "@uniswap/v2-sdk@^4.3.0": +"@uniswap/v2-sdk@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@uniswap/v2-sdk/-/v2-sdk-4.3.0.tgz#621dcf4ea236448a0a49cd901932b3a466c8ae02" integrity sha512-FUKkgo/1TQc/HuWWgsoy1FIcsLkKwm3Nnor88yfn2NH8ER5RK/wDF9UzDDilYh3yyf2mAnaY89CKFhcIl+lbBQ== From f3bf8edb121fec02d44f46c1cab71ca8e0cbeb50 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 22 Mar 2024 10:22:07 -0400 Subject: [PATCH 13/27] Fix version to major and fix imorts --- package.json | 2 +- src/entities/protocols/uniswap.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 590c2c3..3f3718a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/universal-router-sdk", - "version": "2.0.1", + "version": "2.0.0", "description": "sdk for integrating with the Universal Router contracts", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index d78144a..4021432 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -16,12 +16,11 @@ import { partitionMixedRouteByProtocol, } from '@uniswap/router-sdk' import { Permit2Permit } from '../../utils/inputTokens' -import { Currency, TradeType, CurrencyAmount, Percent, Fraction } from '@uniswap/sdk-core' +import { Currency, TradeType, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Command, RouterTradeType, TradeConfig } from '../Command' import { SENDER_AS_RECIPIENT, ROUTER_AS_RECIPIENT, CONTRACT_BALANCE, ETH_ADDRESS } from '../../utils/constants' import { encodeFeeBips } from '../../utils/numbers' import { BigNumber, BigNumberish } from 'ethers' -import JSBI from 'jsbi' export type FlatFeeOptions = { amount: BigNumberish From 86bbe6e948d70407b83908ebd74540c84b35b14f Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 27 Mar 2024 17:39:46 -0400 Subject: [PATCH 14/27] Support 0xeee...eee for ether for trading api compatability --- src/utils/constants.ts | 1 + src/utils/routerTradeAdapter.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 036d76f..1e11665 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -253,6 +253,7 @@ export const PERMIT2_ADDRESS = '0x000000000022D473030F116dDEE9F6B43aC78BA3' export const CONTRACT_BALANCE = BigNumber.from(2).pow(255) export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000' +export const E_ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const MAX_UINT256 = BigNumber.from(2).pow(256).sub(1) export const MAX_UINT160 = BigNumber.from(2).pow(160).sub(1) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 935e789..197e256 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -11,7 +11,7 @@ import { import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' import { BigNumber } from 'ethers' -import { ETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' +import { ETH_ADDRESS, E_ETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' export type TokenInRoute = { address: string @@ -73,7 +73,7 @@ interface RouteResult { outputAmount: CurrencyAmount } -export const isNativeCurrency = (address: string) => address.toLowerCase() === ETH_ADDRESS.toLowerCase() +export const isNativeCurrency = (address: string) => address.toLowerCase() === ETH_ADDRESS.toLowerCase() || address.toLowerCase() === E_ETH_ADDRESS.toLowerCase(); // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the UniswapTrade entity in this package From 85205084da3d302d3fee728fc785f9c7d31a2603 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 27 Mar 2024 17:52:43 -0400 Subject: [PATCH 15/27] Add split route test --- test/uniswapTrades.test.ts | 80 +++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index abf578d..c75ca27 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -19,7 +19,7 @@ import { V2PoolInRoute, V3PoolInRoute, } from '../src/utils/routerTradeAdapter' -import { ETH_ADDRESS } from '../src/utils/constants' +import { E_ETH_ADDRESS, ETH_ADDRESS } from '../src/utils/constants' const FORK_BLOCK = 16075500 @@ -938,6 +938,44 @@ describe('Uniswap', () => { expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) }) + + it('v2 - handles eth input properly - 0xeeee...eeee address', async () => { + const inputAmount = utils.parseEther('1').toString() + const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) + + const opts = swapOptions({}) + const trade = new V2Trade( + new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), + rawInputAmount, + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const rawOutputAmount = trade.outputAmount + const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() + + const classicQuote: PartialClassicQuote = { + tokenIn: E_ETH_ADDRESS, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount), + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + it('v3 - handles eth input properly', async () => { const inputAmount = utils.parseEther('1').toString() const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) @@ -1046,5 +1084,45 @@ describe('Uniswap', () => { // ensure that the encoded call parameters are the same expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) }) + + it('v3 - handles split routes properly', async () => { + const inputAmount = utils.parseEther('1').toString() + const splitRouteInputAmounts = [utils.parseEther('0.5').toString(), utils.parseEther('0.5').toString()] + const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) + + const opts = swapOptions({}) + const trade1 = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], WETH, USDC), + rawInputAmount.divide(2), + TradeType.EXACT_INPUT + ) + const trade2 = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3_LOW_FEE], WETH, USDC), + rawInputAmount.divide(2), + TradeType.EXACT_INPUT + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade1, trade2]), opts)) + + const splitRouteOutputAmounts = [utils.parseUnits(trade1.outputAmount.toExact(), USDC.decimals).toString(), utils.parseUnits(trade2.outputAmount.toExact(), USDC.decimals).toString()] + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, splitRouteInputAmounts[0], splitRouteOutputAmounts[0])], + [mockV3PoolInRoute(WETH_USDC_V3_LOW_FEE, WETH, USDC, splitRouteInputAmounts[1], splitRouteOutputAmounts[1])], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) }) }) From cae3f302c6c4d315f354d6808cf12bd2ce382e89 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 11:12:31 -0400 Subject: [PATCH 16/27] Address comments --- src/entities/protocols/uniswap.ts | 47 +++++++++++++++++-------------- src/utils/routerTradeAdapter.ts | 14 +++++++-- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index 4021432..c28dd25 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -31,7 +31,7 @@ export type FlatFeeOptions = { // so we extend swap options with the permit2 permit // when safe mode is enabled, the SDK will add an extra ETH sweep for security export type SwapOptions = Omit & { - payerIsUser?: boolean + directSend?: boolean inputTokenPermit?: Permit2Permit flatFee?: FlatFeeOptions safeMode?: boolean @@ -49,16 +49,21 @@ interface Swap { // also translates trade objects from previous (v2, v3) SDKs export class UniswapTrade implements Command { readonly tradeType: RouterTradeType = RouterTradeType.UniswapTrade + readonly payerIsUser: boolean; + constructor(public trade: RouterTrade, public options: SwapOptions) { if (!!options.fee && !!options.flatFee) throw new Error('Only one fee option permitted') + + if(this.inputRequiresWrap) this.payerIsUser = false; + else if (this.options.directSend) this.payerIsUser = false; + else this.payerIsUser = true } - encode(planner: RoutePlanner, _config: TradeConfig): void { - if (this.options.payerIsUser === undefined) { - // default to user as payer - Object.assign(this.options, { payerIsUser: true }) - } + get inputRequiresWrap(): boolean { + return this.trade.inputAmount.currency.isNative + } + encode(planner: RoutePlanner, _config: TradeConfig): void { // If the input currency is the native currency, we need to wrap it with the router as the recipient if (this.trade.inputAmount.currency.isNative) { // TODO: optimize if only one v2 pool we can directly send this to the pool @@ -66,8 +71,6 @@ export class UniswapTrade implements Command { ROUTER_AS_RECIPIENT, this.trade.maximumAmountIn(this.options.slippageTolerance).quotient.toString(), ]) - // since WETH is now owned by the router, the router pays for inputs - Object.assign(this.options, { payerIsUser: false }) } // The overall recipient at the end of the trade, SENDER_AS_RECIPIENT uses the msg.sender this.options.recipient = this.options.recipient ?? SENDER_AS_RECIPIENT @@ -79,19 +82,18 @@ export class UniswapTrade implements Command { const performAggregatedSlippageCheck = this.trade.tradeType === TradeType.EXACT_INPUT && this.trade.routes.length > 2 const outputIsNative = this.trade.outputAmount.currency.isNative - const inputIsNative = this.trade.inputAmount.currency.isNative const routerMustCustody = performAggregatedSlippageCheck || outputIsNative || hasFeeOption(this.options) for (const swap of this.trade.swaps) { switch (swap.route.protocol) { case Protocol.V2: - addV2Swap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) + addV2Swap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody) break case Protocol.V3: - addV3Swap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) + addV3Swap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody) break case Protocol.MIXED: - addMixedSwap(planner, swap, this.trade.tradeType, this.options, routerMustCustody) + addMixedSwap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody) break default: throw new Error('UNSUPPORTED_TRADE_PROTOCOL') @@ -153,7 +155,7 @@ export class UniswapTrade implements Command { } } - if (inputIsNative && (this.trade.tradeType === TradeType.EXACT_OUTPUT || riskOfPartialFill(this.trade))) { + if (this.inputRequiresWrap && (this.trade.tradeType === TradeType.EXACT_OUTPUT || riskOfPartialFill(this.trade))) { // for exactOutput swaps that take native currency as input // we need to send back the change to the user planner.addCommand(CommandType.UNWRAP_WETH, [this.options.recipient, 0]) @@ -169,6 +171,7 @@ function addV2Swap( { route, inputAmount, outputAmount }: Swap, tradeType: TradeType, options: SwapOptions, + payerIsUser: boolean, routerMustCustody: boolean ): void { const trade = new V2Trade( @@ -184,7 +187,7 @@ function addV2Swap( trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), route.path.map((pool) => pool.address), - options.payerIsUser, + payerIsUser, ]) } else if (tradeType == TradeType.EXACT_OUTPUT) { planner.addCommand(CommandType.V2_SWAP_EXACT_OUT, [ @@ -192,7 +195,7 @@ function addV2Swap( trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), route.path.map((pool) => pool.address), - options.payerIsUser, + payerIsUser, ]) } } @@ -203,6 +206,7 @@ function addV3Swap( { route, inputAmount, outputAmount }: Swap, tradeType: TradeType, options: SwapOptions, + payerIsUser: boolean, routerMustCustody: boolean ): void { const trade = V3Trade.createUncheckedTrade({ @@ -219,7 +223,7 @@ function addV3Swap( trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), path, - options.payerIsUser, + payerIsUser, ]) } else if (tradeType == TradeType.EXACT_OUTPUT) { planner.addCommand(CommandType.V3_SWAP_EXACT_OUT, [ @@ -227,7 +231,7 @@ function addV3Swap( trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), path, - options.payerIsUser, + payerIsUser, ]) } } @@ -238,6 +242,7 @@ function addMixedSwap( swap: Swap, tradeType: TradeType, options: SwapOptions, + payerIsUser: boolean, routerMustCustody: boolean ): void { const { route, inputAmount, outputAmount } = swap @@ -246,9 +251,9 @@ function addMixedSwap( // single hop, so it can be reduced to plain v2 or v3 swap logic if (route.pools.length === 1) { if (route.pools[0] instanceof Pool) { - return addV3Swap(planner, swap, tradeType, options, routerMustCustody) + return addV3Swap(planner, swap, tradeType, options, payerIsUser, routerMustCustody) } else if (route.pools[0] instanceof Pair) { - return addV2Swap(planner, swap, tradeType, options, routerMustCustody) + return addV2Swap(planner, swap, tradeType, options, payerIsUser, routerMustCustody) } else { throw new Error('Invalid route type') } @@ -303,7 +308,7 @@ function addMixedSwap( i == 0 ? amountIn : CONTRACT_BALANCE, // amountIn !isLastSectionInRoute(i) ? 0 : amountOut, // amountOut path, // path - options.payerIsUser && i === 0, // payerIsUser + payerIsUser && i === 0, // payerIsUser ]) } else { planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ @@ -311,7 +316,7 @@ function addMixedSwap( i === 0 ? amountIn : CONTRACT_BALANCE, // amountIn !isLastSectionInRoute(i) ? 0 : amountOut, // amountOutMin newRoute.path.map((pool) => pool.address), // path - options.payerIsUser && i === 0, + payerIsUser && i === 0, ]) } } diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 197e256..d575bd7 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -73,7 +73,8 @@ interface RouteResult { outputAmount: CurrencyAmount } -export const isNativeCurrency = (address: string) => address.toLowerCase() === ETH_ADDRESS.toLowerCase() || address.toLowerCase() === E_ETH_ADDRESS.toLowerCase(); +export const isNativeCurrency = (address: string) => + address.toLowerCase() === ETH_ADDRESS.toLowerCase() || address.toLowerCase() === E_ETH_ADDRESS.toLowerCase() // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the UniswapTrade entity in this package @@ -82,8 +83,15 @@ export class RouterTradeAdapter { static fromClassicQuote(quote: PartialClassicQuote) { const { route, tokenIn, tokenOut } = quote - const tokenInData = route[0]?.[0]?.tokenIn - const tokenOutData = route[0]?.[route[0]?.length - 1]?.tokenOut + if (!route) throw new Error('Expected route to be present') + if (!route.length) throw new Error('Expected there to be at least one route') + if(route.some((r) => !r.length)) throw new Error('Expected all routes to have at least one pool') + const firstRoute = route[0] + if(!firstRoute.length) throw new Error('Expected route to have at least one pool') + + const tokenInData = firstRoute[0].tokenIn + const tokenOutData = firstRoute[firstRoute.length - 1].tokenOut + if (!tokenInData || !tokenOutData) throw new Error('Expected both tokenIn and tokenOut to be present') if (tokenInData.chainId !== tokenOutData.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId') From 000fa6b55a8e62bc3a06f69a5079453ebe1211cc Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 17:25:19 -0400 Subject: [PATCH 17/27] Add exact out, multi hop, split route tests --- src/utils/constants.ts | 2 +- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 845 +++++++++++++++++++------------------ 3 files changed, 438 insertions(+), 411 deletions(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 1e11665..0448988 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -253,7 +253,7 @@ export const PERMIT2_ADDRESS = '0x000000000022D473030F116dDEE9F6B43aC78BA3' export const CONTRACT_BALANCE = BigNumber.from(2).pow(255) export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000' -export const E_ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +export const E_ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const MAX_UINT256 = BigNumber.from(2).pow(256).sub(1) export const MAX_UINT160 = BigNumber.from(2).pow(160).sub(1) diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index c75ca27..df1e6c6 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import JSBI from 'jsbi' -import { BigNumber, utils, Wallet } from 'ethers' +import { BigNumber, ethers, utils, Wallet } from 'ethers' import { expandTo18Decimals } from '../src/utils/numbers' import { SwapRouter, UniswapTrade, FlatFeeOptions } from '../src' import { MixedRouteTrade, MixedRouteSDK } from '@uniswap/router-sdk' @@ -13,6 +13,7 @@ import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC, WETH } from import { hexToDecimalString } from './utils/hexToDecimalString' import { FORGE_PERMIT2_ADDRESS, FORGE_ROUTER_ADDRESS, TEST_FEE_RECIPIENT_ADDRESS } from './utils/addresses' import { + NativeCurrency, PartialClassicQuote, PoolType, RouterTradeAdapter, @@ -228,6 +229,7 @@ describe('Uniswap', () => { CurrencyAmount.fromRawAmount(USDC, outputUSDC), TradeType.EXACT_OUTPUT ) + const routerTrade = buildTrade([trade]); const opts = swapOptions({}) const methodParameters = SwapRouter.swapERC20CallParameters(buildTrade([trade]), opts) const methodParametersV2 = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) @@ -702,427 +704,452 @@ describe('Uniswap', () => { }) }) - describe('RouterTradeAdapter', () => { - const mockV2PoolInRoute = ( - pair: Pair, - tokenIn: Token, - tokenOut: Token, - amountIn: string, - amountOut: string - ): V2PoolInRoute => { - // get token0 and token1 - const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut - const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn - - return { - type: PoolType.V2Pool, - tokenIn: { - address: tokenIn.address, - chainId: 1, - symbol: tokenIn.symbol!, - decimals: String(tokenIn.decimals), - }, - tokenOut: { - address: tokenOut.address, - chainId: 1, - symbol: tokenOut.symbol!, - decimals: String(tokenOut.decimals), - }, - reserve0: { - token: { - address: token0.address, + for (let tradeType of [TradeType.EXACT_INPUT, TradeType.EXACT_OUTPUT]) { + describe('RouterTradeAdapter ' + tradeType, () => { + const getAmountToken = (tokenIn: Token | NativeCurrency, tokenOut: Token | NativeCurrency, tradeType: TradeType): Token | NativeCurrency => { + return tradeType === TradeType.EXACT_INPUT ? tokenIn : tokenOut + } + const getAmount = (tokenIn: Token | NativeCurrency, tokenOut: Token | NativeCurrency, amount: string, tradeType: TradeType): CurrencyAmount => { + return tradeType === TradeType.EXACT_INPUT + ? CurrencyAmount.fromRawAmount(tokenIn, amount) + : CurrencyAmount.fromRawAmount(tokenOut, amount) + } + + const mockV2PoolInRoute = ( + pair: Pair, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V2PoolInRoute => { + // get token0 and token1 + const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut + const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn + + return { + type: PoolType.V2Pool, + tokenIn: { + address: tokenIn.address, chainId: 1, - symbol: token0.symbol!, - decimals: String(token0.decimals), + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), }, - quotient: pair.reserve0.quotient.toString(), - }, - reserve1: { - token: { - address: token1.address, + tokenOut: { + address: tokenOut.address, chainId: 1, - symbol: token1.symbol!, - decimals: String(token1.decimals), + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), }, - quotient: pair.reserve1.quotient.toString(), - }, - amountIn, - amountOut, - } - } - - const mockV3PoolInRoute = ( - pool: Pool, - tokenIn: Token, - tokenOut: Token, - amountIn: string, - amountOut: string - ): V3PoolInRoute => { - return { - type: PoolType.V3Pool, - tokenIn: { - address: tokenIn.address, - chainId: 1, - symbol: tokenIn.symbol!, - decimals: String(tokenIn.decimals), - }, - tokenOut: { - address: tokenOut.address, - chainId: 1, - symbol: tokenOut.symbol!, - decimals: String(tokenOut.decimals), - }, - sqrtRatioX96: pool.sqrtRatioX96.toString(), - liquidity: pool.liquidity.toString(), - tickCurrent: pool.tickCurrent.toString(), - fee: pool.fee.toString(), - amountIn, - amountOut, - } - } - - it('v2 - erc20 <> erc20', async () => { - const inputAmount = utils.parseEther('1000').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount) - - const opts = swapOptions({}) - const trade = new V2Trade(new RouteV2([USDC_DAI_V2], DAI, USDC), rawInputAmount, TradeType.EXACT_INPUT) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: DAI.address, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [[mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, inputAmount, outputAmount)]], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v3 - erc20 <> erc20', async () => { - const inputAmount = utils.parseEther('1000').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(DAI, inputAmount) - - const opts = swapOptions({}) - const trade = await V3Trade.fromRoute( - new RouteV3([USDC_DAI_V3], DAI, USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: DAI.address, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [[mockV3PoolInRoute(USDC_DAI_V3, DAI, USDC, inputAmount, outputAmount)]], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v2 - handles weth input properly', async () => { - const inputAmount = utils.parseEther('1').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) - - const opts = swapOptions({}) - const trade = new V2Trade(new RouteV2([WETH_USDC_V2], WETH, USDC), rawInputAmount, TradeType.EXACT_INPUT) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: WETH.address, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [[mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount)]], + reserve0: { + token: { + address: token0.address, + chainId: 1, + symbol: token0.symbol!, + decimals: String(token0.decimals), + }, + quotient: pair.reserve0.quotient.toString(), + }, + reserve1: { + token: { + address: token1.address, + chainId: 1, + symbol: token1.symbol!, + decimals: String(token1.decimals), + }, + quotient: pair.reserve1.quotient.toString(), + }, + amountIn, + amountOut, + } } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v3 - handles weth input properly', async () => { - const inputAmount = utils.parseEther('1').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) - - const opts = swapOptions({}) - const trade = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3], WETH, USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: WETH.address, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [[mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, inputAmount, outputAmount)]], + const mockV3PoolInRoute = ( + pool: Pool, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V3PoolInRoute => { + return { + type: PoolType.V3Pool, + tokenIn: { + address: tokenIn.address, + chainId: 1, + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), + }, + tokenOut: { + address: tokenOut.address, + chainId: 1, + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), + }, + sqrtRatioX96: pool.sqrtRatioX96.toString(), + liquidity: pool.liquidity.toString(), + tickCurrent: pool.tickCurrent.toString(), + fee: pool.fee.toString(), + amountIn, + amountOut, + } } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v2 - handles eth input properly', async () => { - const inputAmount = utils.parseEther('1').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) - - const opts = swapOptions({}) - const trade = new V2Trade( - new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: ETH_ADDRESS, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [ - [ - // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount), + it('v2 - erc20 <> erc20', async () => { + const [tokenIn, tokenOut] = [DAI, USDC] + const inputAmount = ethers.utils.parseUnits("1000", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + // amount should always be interms of output token + const trade = new V2Trade(new RouteV2([USDC_DAI_V2], DAI, USDC), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType, + route: [[mockV2PoolInRoute(USDC_DAI_V2, tokenIn, tokenOut, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - erc20 <> erc20', async () => { + const [tokenIn, tokenOut] = [DAI, USDC] + const inputAmount = ethers.utils.parseUnits("1000", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute(new RouteV3([USDC_DAI_V3], tokenIn, tokenOut), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType, + route: [[mockV3PoolInRoute(USDC_DAI_V3, tokenIn, tokenOut, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles weth input properly', async () => { + const [tokenIn, tokenOut] = [WETH, USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], tokenIn, tokenOut), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType, + route: [[mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles weth input properly', async () => { + const [tokenIn, tokenOut] = [WETH, USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute(new RouteV3([WETH_USDC_V3], WETH, USDC), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType, + route: [[mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth input properly', async () => { + const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: ETH_ADDRESS, + tokenOut: USDC.address, + tradeType, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + ], ], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - - it('v2 - handles eth input properly - 0xeeee...eeee address', async () => { - const inputAmount = utils.parseEther('1').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) - - const opts = swapOptions({}) - const trade = new V2Trade( - new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: E_ETH_ADDRESS, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [ - [ - // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, inputAmount, outputAmount), + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth input properly - 0xeeee...eeee address', async () => { + const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: E_ETH_ADDRESS, + tokenOut: USDC.address, + tradeType, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + ], ], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v3 - handles eth input properly', async () => { - const inputAmount = utils.parseEther('1').toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(Ether.onChain(1), inputAmount) - - const opts = swapOptions({}) - const trade = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3], Ether.onChain(1), USDC), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseUnits(rawOutputAmount.toExact(), USDC.decimals).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: ETH_ADDRESS, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [ - [ - // WETH here since all pools use WETH - mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, inputAmount, outputAmount), + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles eth input properly', async () => { + const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], Ether.onChain(1), USDC), + rawInputAmount, + tradeType + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: ETH_ADDRESS, + tokenOut: USDC.address, + tradeType, + route: [ + [ + // WETH here since all pools use WETH + mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + ], ], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(inputAmount) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v2 - handles eth output properly', async () => { - const inputAmount = utils.parseUnits('1000', 6).toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(USDC, inputAmount) - const opts = swapOptions({}) - const trade = new V2Trade( - new RouteV2([WETH_USDC_V2], USDC, Ether.onChain(1)), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseEther(rawOutputAmount.toExact()).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: USDC.address, - tokenOut: ETH_ADDRESS, - tradeType: TradeType.EXACT_INPUT, - route: [ - [ - // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, inputAmount, outputAmount), + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v2 - handles eth output properly', async () => { + const [tokenIn, tokenOut] = [USDC, Ether.onChain(1)] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = new V2Trade(new RouteV2([WETH_USDC_V2], tokenIn, tokenOut), rawInputAmount, tradeType) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: USDC.address, + tokenOut: ETH_ADDRESS, + tradeType, + route: [ + [ + // WETH here since all pairs use WETH + mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + ], ], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v3 - handles eth output properly', async () => { - const inputAmount = utils.parseUnits('1000', 6).toString() - const rawInputAmount = CurrencyAmount.fromRawAmount(USDC, inputAmount) - const opts = swapOptions({}) - const trade = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3], USDC, Ether.onChain(1)), - rawInputAmount, - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - - const rawOutputAmount = trade.outputAmount - const outputAmount = utils.parseEther(rawOutputAmount.toExact()).toString() - - const classicQuote: PartialClassicQuote = { - tokenIn: USDC.address, - tokenOut: ETH_ADDRESS, - tradeType: TradeType.EXACT_INPUT, - route: [ - [ - // WETH here since all pairs use WETH - mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, inputAmount, outputAmount), + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles eth output properly', async () => { + const [tokenIn, tokenOut] = [USDC, Ether.onChain(1)] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], tokenIn, tokenOut), + rawInputAmount, + tradeType + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: USDC.address, + tokenOut: ETH_ADDRESS, + tradeType, + route: [ + [ + // WETH here since all pairs use WETH + mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + ], ], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) - }) - - it('v3 - handles split routes properly', async () => { - const inputAmount = utils.parseEther('1').toString() - const splitRouteInputAmounts = [utils.parseEther('0.5').toString(), utils.parseEther('0.5').toString()] - const rawInputAmount = CurrencyAmount.fromRawAmount(WETH, inputAmount) - - const opts = swapOptions({}) - const trade1 = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3], WETH, USDC), - rawInputAmount.divide(2), - TradeType.EXACT_INPUT - ) - const trade2 = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3_LOW_FEE], WETH, USDC), - rawInputAmount.divide(2), - TradeType.EXACT_INPUT - ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade1, trade2]), opts)) - - const splitRouteOutputAmounts = [utils.parseUnits(trade1.outputAmount.toExact(), USDC.decimals).toString(), utils.parseUnits(trade2.outputAmount.toExact(), USDC.decimals).toString()] - - const classicQuote: PartialClassicQuote = { - tokenIn: WETH.address, - tokenOut: USDC.address, - tradeType: TradeType.EXACT_INPUT, - route: [ - [mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, splitRouteInputAmounts[0], splitRouteOutputAmounts[0])], - [mockV3PoolInRoute(WETH_USDC_V3_LOW_FEE, WETH, USDC, splitRouteInputAmounts[1], splitRouteOutputAmounts[1])], - ], - } - const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - multi pool erc20 <> erc20', async () => { + const [tokenIn, tokenOut] = [DAI, WETH] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await V3Trade.fromRoute( + new RouteV3([USDC_DAI_V3, WETH_USDC_V3], tokenIn, tokenOut), + rawInputAmount, + tradeType + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) + + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType, + route: [ + [ + mockV3PoolInRoute(USDC_DAI_V3, DAI, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) + + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) + + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) + + it('v3 - handles split routes properly', async () => { + const [tokenIn, tokenOut] = [WETH, USDC] + const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade1 = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3], tokenIn, tokenOut), + rawInputAmount.divide(2), + tradeType + ) + const trade2 = await V3Trade.fromRoute( + new RouteV3([WETH_USDC_V3_LOW_FEE], tokenIn, tokenOut), + rawInputAmount.divide(2), + tradeType + ) + const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade1, trade2]), opts)) + + const splitRouteInputAmounts = [ + trade1.inputAmount.quotient.toString(), + trade2.inputAmount.quotient.toString(), + ] + + const splitRouteOutputAmounts = [ + trade1.outputAmount.quotient.toString(), + trade2.outputAmount.quotient.toString(), + ] + + const classicQuote: PartialClassicQuote = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType, + route: [ + [mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, splitRouteInputAmounts[0], splitRouteOutputAmounts[0])], + [ + mockV3PoolInRoute( + WETH_USDC_V3_LOW_FEE, + WETH, + USDC, + splitRouteInputAmounts[1], + splitRouteOutputAmounts[1] + ), + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq('0') + // ensure that we can generate the encoded call parameters + const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) + expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + // ensure that the encoded call parameters are the same + expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + }) }) - }) + } }) From ee0376de6660fb95ab336c7603f9a0183439055b Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 17:25:44 -0400 Subject: [PATCH 18/27] prettier --- src/entities/protocols/uniswap.ts | 6 +- src/utils/routerTradeAdapter.ts | 6 +- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 174 ++++++++++++++++++++++++------ 4 files changed, 147 insertions(+), 41 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index c28dd25..b44c96a 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -49,13 +49,13 @@ interface Swap { // also translates trade objects from previous (v2, v3) SDKs export class UniswapTrade implements Command { readonly tradeType: RouterTradeType = RouterTradeType.UniswapTrade - readonly payerIsUser: boolean; + readonly payerIsUser: boolean constructor(public trade: RouterTrade, public options: SwapOptions) { if (!!options.fee && !!options.flatFee) throw new Error('Only one fee option permitted') - if(this.inputRequiresWrap) this.payerIsUser = false; - else if (this.options.directSend) this.payerIsUser = false; + if (this.inputRequiresWrap) this.payerIsUser = false + else if (this.options.directSend) this.payerIsUser = false else this.payerIsUser = true } diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index d575bd7..23ffff1 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -85,10 +85,10 @@ export class RouterTradeAdapter { if (!route) throw new Error('Expected route to be present') if (!route.length) throw new Error('Expected there to be at least one route') - if(route.some((r) => !r.length)) throw new Error('Expected all routes to have at least one pool') + if (route.some((r) => !r.length)) throw new Error('Expected all routes to have at least one pool') const firstRoute = route[0] - if(!firstRoute.length) throw new Error('Expected route to have at least one pool') - + if (!firstRoute.length) throw new Error('Expected route to have at least one pool') + const tokenInData = firstRoute[0].tokenIn const tokenOutData = firstRoute[firstRoute.length - 1].tokenOut diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +} diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index df1e6c6..9d8dd41 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -229,7 +229,7 @@ describe('Uniswap', () => { CurrencyAmount.fromRawAmount(USDC, outputUSDC), TradeType.EXACT_OUTPUT ) - const routerTrade = buildTrade([trade]); + const routerTrade = buildTrade([trade]) const opts = swapOptions({}) const methodParameters = SwapRouter.swapERC20CallParameters(buildTrade([trade]), opts) const methodParametersV2 = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) @@ -706,10 +706,19 @@ describe('Uniswap', () => { for (let tradeType of [TradeType.EXACT_INPUT, TradeType.EXACT_OUTPUT]) { describe('RouterTradeAdapter ' + tradeType, () => { - const getAmountToken = (tokenIn: Token | NativeCurrency, tokenOut: Token | NativeCurrency, tradeType: TradeType): Token | NativeCurrency => { + const getAmountToken = ( + tokenIn: Token | NativeCurrency, + tokenOut: Token | NativeCurrency, + tradeType: TradeType + ): Token | NativeCurrency => { return tradeType === TradeType.EXACT_INPUT ? tokenIn : tokenOut } - const getAmount = (tokenIn: Token | NativeCurrency, tokenOut: Token | NativeCurrency, amount: string, tradeType: TradeType): CurrencyAmount => { + const getAmount = ( + tokenIn: Token | NativeCurrency, + tokenOut: Token | NativeCurrency, + amount: string, + tradeType: TradeType + ): CurrencyAmount => { return tradeType === TradeType.EXACT_INPUT ? CurrencyAmount.fromRawAmount(tokenIn, amount) : CurrencyAmount.fromRawAmount(tokenOut, amount) @@ -794,7 +803,9 @@ describe('Uniswap', () => { } it('v2 - erc20 <> erc20', async () => { const [tokenIn, tokenOut] = [DAI, USDC] - const inputAmount = ethers.utils.parseUnits("1000", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1000', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -806,7 +817,17 @@ describe('Uniswap', () => { tokenIn: DAI.address, tokenOut: USDC.address, tradeType, - route: [[mockV2PoolInRoute(USDC_DAI_V2, tokenIn, tokenOut, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + route: [ + [ + mockV2PoolInRoute( + USDC_DAI_V2, + tokenIn, + tokenOut, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + ], + ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) @@ -821,7 +842,9 @@ describe('Uniswap', () => { it('v3 - erc20 <> erc20', async () => { const [tokenIn, tokenOut] = [DAI, USDC] - const inputAmount = ethers.utils.parseUnits("1000", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1000', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -832,7 +855,17 @@ describe('Uniswap', () => { tokenIn: DAI.address, tokenOut: USDC.address, tradeType, - route: [[mockV3PoolInRoute(USDC_DAI_V3, tokenIn, tokenOut, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + route: [ + [ + mockV3PoolInRoute( + USDC_DAI_V3, + tokenIn, + tokenOut, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + ], + ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) @@ -847,7 +880,9 @@ describe('Uniswap', () => { it('v2 - handles weth input properly', async () => { const [tokenIn, tokenOut] = [WETH, USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -858,7 +893,17 @@ describe('Uniswap', () => { tokenIn: WETH.address, tokenOut: USDC.address, tradeType, - route: [[mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + route: [ + [ + mockV2PoolInRoute( + WETH_USDC_V2, + WETH, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + ], + ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) @@ -873,7 +918,9 @@ describe('Uniswap', () => { it('v3 - handles weth input properly', async () => { const [tokenIn, tokenOut] = [WETH, USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -884,7 +931,17 @@ describe('Uniswap', () => { tokenIn: WETH.address, tokenOut: USDC.address, tradeType, - route: [[mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString())]], + route: [ + [ + mockV3PoolInRoute( + WETH_USDC_V3, + WETH, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + ], + ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) const uniswapTrade = new UniswapTrade(routerTrade, opts) @@ -899,7 +956,9 @@ describe('Uniswap', () => { it('v2 - handles eth input properly', async () => { const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -913,7 +972,13 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + mockV2PoolInRoute( + WETH_USDC_V2, + WETH, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -930,7 +995,9 @@ describe('Uniswap', () => { it('v2 - handles eth input properly - 0xeeee...eeee address', async () => { const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -944,7 +1011,13 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + mockV2PoolInRoute( + WETH_USDC_V2, + WETH, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -961,7 +1034,9 @@ describe('Uniswap', () => { it('v3 - handles eth input properly', async () => { const [tokenIn, tokenOut] = [Ether.onChain(1), USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -979,7 +1054,13 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pools use WETH - mockV3PoolInRoute(WETH_USDC_V3, WETH, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()) + mockV3PoolInRoute( + WETH_USDC_V3, + WETH, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -996,7 +1077,9 @@ describe('Uniswap', () => { it('v2 - handles eth output properly', async () => { const [tokenIn, tokenOut] = [USDC, Ether.onChain(1)] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -1010,7 +1093,13 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pairs use WETH - mockV2PoolInRoute(WETH_USDC_V2, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + mockV2PoolInRoute( + WETH_USDC_V2, + USDC, + WETH, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -1027,15 +1116,13 @@ describe('Uniswap', () => { it('v3 - handles eth output properly', async () => { const [tokenIn, tokenOut] = [USDC, Ether.onChain(1)] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) - const trade = await V3Trade.fromRoute( - new RouteV3([WETH_USDC_V3], tokenIn, tokenOut), - rawInputAmount, - tradeType - ) + const trade = await V3Trade.fromRoute(new RouteV3([WETH_USDC_V3], tokenIn, tokenOut), rawInputAmount, tradeType) const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { @@ -1045,7 +1132,13 @@ describe('Uniswap', () => { route: [ [ // WETH here since all pairs use WETH - mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + mockV3PoolInRoute( + WETH_USDC_V3, + USDC, + WETH, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -1062,7 +1155,9 @@ describe('Uniswap', () => { it('v3 - multi pool erc20 <> erc20', async () => { const [tokenIn, tokenOut] = [DAI, WETH] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -1079,8 +1174,20 @@ describe('Uniswap', () => { tradeType, route: [ [ - mockV3PoolInRoute(USDC_DAI_V3, DAI, USDC, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), - mockV3PoolInRoute(WETH_USDC_V3, USDC, WETH, trade.inputAmount.quotient.toString(), trade.outputAmount.quotient.toString()), + mockV3PoolInRoute( + USDC_DAI_V3, + DAI, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + mockV3PoolInRoute( + WETH_USDC_V3, + USDC, + WETH, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), ], ], } @@ -1097,7 +1204,9 @@ describe('Uniswap', () => { it('v3 - handles split routes properly', async () => { const [tokenIn, tokenOut] = [WETH, USDC] - const inputAmount = ethers.utils.parseUnits("1", getAmountToken(tokenIn, tokenOut, tradeType).decimals).toString(); + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) const opts = swapOptions({}) @@ -1113,10 +1222,7 @@ describe('Uniswap', () => { ) const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade1, trade2]), opts)) - const splitRouteInputAmounts = [ - trade1.inputAmount.quotient.toString(), - trade2.inputAmount.quotient.toString(), - ] + const splitRouteInputAmounts = [trade1.inputAmount.quotient.toString(), trade2.inputAmount.quotient.toString()] const splitRouteOutputAmounts = [ trade1.outputAmount.quotient.toString(), From 8ebb18f988e90cc50b6132172a69ca6222c77ffb Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 17:35:22 -0400 Subject: [PATCH 19/27] Add helper func --- test/uniswapTrades.test.ts | 106 ++++++------------------------------- 1 file changed, 17 insertions(+), 89 deletions(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index 9d8dd41..4877bfd 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -801,6 +801,12 @@ describe('Uniswap', () => { amountOut, } } + + function compareUniswapTrades(left: UniswapTrade, right: UniswapTrade): void { + expect(SwapRouter.swapCallParameters(left).calldata).to.eq(SwapRouter.swapCallParameters(right).calldata) + expect(SwapRouter.swapCallParameters(left).value).to.eq(SwapRouter.swapCallParameters(right).value) + } + it('v2 - erc20 <> erc20', async () => { const [tokenIn, tokenOut] = [DAI, USDC] const inputAmount = ethers.utils @@ -811,7 +817,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) // amount should always be interms of output token const trade = new V2Trade(new RouteV2([USDC_DAI_V2], DAI, USDC), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: DAI.address, @@ -830,14 +835,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - erc20 <> erc20', async () => { @@ -849,7 +848,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = await V3Trade.fromRoute(new RouteV3([USDC_DAI_V3], tokenIn, tokenOut), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: DAI.address, @@ -868,14 +866,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v2 - handles weth input properly', async () => { @@ -887,7 +879,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = new V2Trade(new RouteV2([WETH_USDC_V2], tokenIn, tokenOut), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: WETH.address, @@ -906,14 +897,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - handles weth input properly', async () => { @@ -925,7 +910,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = await V3Trade.fromRoute(new RouteV3([WETH_USDC_V3], WETH, USDC), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: WETH.address, @@ -944,14 +928,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v2 - handles eth input properly', async () => { @@ -963,7 +941,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: ETH_ADDRESS, @@ -983,14 +960,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v2 - handles eth input properly - 0xeeee...eeee address', async () => { @@ -1002,7 +973,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = new V2Trade(new RouteV2([WETH_USDC_V2], Ether.onChain(1), USDC), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: E_ETH_ADDRESS, @@ -1022,14 +992,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - handles eth input properly', async () => { @@ -1045,7 +1009,6 @@ describe('Uniswap', () => { rawInputAmount, tradeType ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: ETH_ADDRESS, @@ -1065,14 +1028,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v2 - handles eth output properly', async () => { @@ -1084,7 +1041,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = new V2Trade(new RouteV2([WETH_USDC_V2], tokenIn, tokenOut), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: USDC.address, @@ -1104,14 +1060,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - handles eth output properly', async () => { @@ -1123,7 +1073,6 @@ describe('Uniswap', () => { const opts = swapOptions({}) const trade = await V3Trade.fromRoute(new RouteV3([WETH_USDC_V3], tokenIn, tokenOut), rawInputAmount, tradeType) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: USDC.address, @@ -1143,14 +1092,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - multi pool erc20 <> erc20', async () => { @@ -1166,7 +1109,6 @@ describe('Uniswap', () => { rawInputAmount, tradeType ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) const classicQuote: PartialClassicQuote = { tokenIn: DAI.address, @@ -1192,14 +1134,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) it('v3 - handles split routes properly', async () => { @@ -1220,10 +1156,8 @@ describe('Uniswap', () => { rawInputAmount.divide(2), tradeType ) - const SDKMethodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade1, trade2]), opts)) const splitRouteInputAmounts = [trade1.inputAmount.quotient.toString(), trade2.inputAmount.quotient.toString()] - const splitRouteOutputAmounts = [ trade1.outputAmount.quotient.toString(), trade2.outputAmount.quotient.toString(), @@ -1247,14 +1181,8 @@ describe('Uniswap', () => { ], } const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) - const uniswapTrade = new UniswapTrade(routerTrade, opts) - - // ensure that we can generate the encoded call parameters - const methodParameters = SwapRouter.swapCallParameters(uniswapTrade) - expect(hexToDecimalString(methodParameters.value)).to.eq(hexToDecimalString(SDKMethodParameters.value)) - // ensure that the encoded call parameters are the same - expect(methodParameters.calldata).to.eq(SDKMethodParameters.calldata) + compareUniswapTrades(new UniswapTrade(buildTrade([trade1, trade2]), opts), new UniswapTrade(routerTrade, opts)) }) }) } From 1c2b5388a597eec69bfa06029735696e71af0859 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 17:42:08 -0400 Subject: [PATCH 20/27] Remove local native currency --- src/utils/routerTradeAdapter.ts | 28 ++-------------------------- test/forge/interop.json | 2 +- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 23ffff1..1414c9a 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -1,17 +1,15 @@ import { MixedRouteSDK, Trade as RouterTrade } from '@uniswap/router-sdk' import { - ChainId, Currency, CurrencyAmount, Ether, - NativeCurrency as SdkNativeCurrency, Token, TradeType, } from '@uniswap/sdk-core' import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' import { BigNumber } from 'ethers' -import { ETH_ADDRESS, E_ETH_ADDRESS, WRAPPED_NATIVE_CURRENCY } from './constants' +import { ETH_ADDRESS, E_ETH_ADDRESS } from './constants' export type TokenInRoute = { address: string @@ -168,7 +166,7 @@ export class RouterTradeAdapter { private static toCurrency(isNative: boolean, token: TokenInRoute): Currency { if (isNative) { - return new NativeCurrency(token.chainId, parseInt(token.decimals)) + return Ether.onChain(token.chainId) } return this.toToken(token) } @@ -216,25 +214,3 @@ export class RouterTradeAdapter { return route.every((pool) => pool.type === type) } } - -export class NativeCurrency extends SdkNativeCurrency { - constructor(chainId: ChainId, decimals: number, symbol?: string, name?: string) { - if (chainId === ChainId.MAINNET) { - return Ether.onChain(chainId) - } - super(chainId, decimals, symbol, name) - } - - equals(currency: Currency): boolean { - return currency.isNative && currency.chainId === this.chainId - } - - get wrapped(): Token { - const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId as ChainId] - if (!wrapped) { - throw new Error('unsupported chain') - } - - return wrapped - } -} diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file From c65b595307d5664d2233639185610f2eb3b23770 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 18:05:23 -0400 Subject: [PATCH 21/27] Add error handling tests --- src/utils/routerTradeAdapter.ts | 12 +- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 249 ++++++++++++++++++++++---------- 3 files changed, 173 insertions(+), 90 deletions(-) diff --git a/src/utils/routerTradeAdapter.ts b/src/utils/routerTradeAdapter.ts index 1414c9a..a6aacff 100644 --- a/src/utils/routerTradeAdapter.ts +++ b/src/utils/routerTradeAdapter.ts @@ -1,11 +1,5 @@ import { MixedRouteSDK, Trade as RouterTrade } from '@uniswap/router-sdk' -import { - Currency, - CurrencyAmount, - Ether, - Token, - TradeType, -} from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, Ether, Token, TradeType } from '@uniswap/sdk-core' import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, FeeAmount } from '@uniswap/v3-sdk' import { BigNumber } from 'ethers' @@ -85,7 +79,6 @@ export class RouterTradeAdapter { if (!route.length) throw new Error('Expected there to be at least one route') if (route.some((r) => !r.length)) throw new Error('Expected all routes to have at least one pool') const firstRoute = route[0] - if (!firstRoute.length) throw new Error('Expected route to have at least one pool') const tokenInData = firstRoute[0].tokenIn const tokenOutData = firstRoute[firstRoute.length - 1].tokenOut @@ -98,9 +91,6 @@ export class RouterTradeAdapter { const parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenOut), tokenOutData) const typedRoutes: RouteResult[] = route.map((subRoute) => { - if (subRoute.length === 0) { - throw new Error('Expected route to have at least one pair or pool') - } const rawAmountIn = subRoute[0].amountIn const rawAmountOut = subRoute[subRoute.length - 1].amountOut diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +} diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index 4877bfd..bf144d9 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -704,6 +704,84 @@ describe('Uniswap', () => { }) }) + const mockV2PoolInRoute = ( + pair: Pair, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V2PoolInRoute => { + // get token0 and token1 + const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut + const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn + + return { + type: PoolType.V2Pool, + tokenIn: { + address: tokenIn.address, + chainId: 1, + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), + }, + tokenOut: { + address: tokenOut.address, + chainId: 1, + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), + }, + reserve0: { + token: { + address: token0.address, + chainId: 1, + symbol: token0.symbol!, + decimals: String(token0.decimals), + }, + quotient: pair.reserve0.quotient.toString(), + }, + reserve1: { + token: { + address: token1.address, + chainId: 1, + symbol: token1.symbol!, + decimals: String(token1.decimals), + }, + quotient: pair.reserve1.quotient.toString(), + }, + amountIn, + amountOut, + } + } + + const mockV3PoolInRoute = ( + pool: Pool, + tokenIn: Token, + tokenOut: Token, + amountIn: string, + amountOut: string + ): V3PoolInRoute => { + return { + type: PoolType.V3Pool, + tokenIn: { + address: tokenIn.address, + chainId: 1, + symbol: tokenIn.symbol!, + decimals: String(tokenIn.decimals), + }, + tokenOut: { + address: tokenOut.address, + chainId: 1, + symbol: tokenOut.symbol!, + decimals: String(tokenOut.decimals), + }, + sqrtRatioX96: pool.sqrtRatioX96.toString(), + liquidity: pool.liquidity.toString(), + tickCurrent: pool.tickCurrent.toString(), + fee: pool.fee.toString(), + amountIn, + amountOut, + } + } + for (let tradeType of [TradeType.EXACT_INPUT, TradeType.EXACT_OUTPUT]) { describe('RouterTradeAdapter ' + tradeType, () => { const getAmountToken = ( @@ -724,84 +802,6 @@ describe('Uniswap', () => { : CurrencyAmount.fromRawAmount(tokenOut, amount) } - const mockV2PoolInRoute = ( - pair: Pair, - tokenIn: Token, - tokenOut: Token, - amountIn: string, - amountOut: string - ): V2PoolInRoute => { - // get token0 and token1 - const token0 = tokenIn.sortsBefore(tokenOut) ? tokenIn : tokenOut - const token1 = tokenIn.sortsBefore(tokenOut) ? tokenOut : tokenIn - - return { - type: PoolType.V2Pool, - tokenIn: { - address: tokenIn.address, - chainId: 1, - symbol: tokenIn.symbol!, - decimals: String(tokenIn.decimals), - }, - tokenOut: { - address: tokenOut.address, - chainId: 1, - symbol: tokenOut.symbol!, - decimals: String(tokenOut.decimals), - }, - reserve0: { - token: { - address: token0.address, - chainId: 1, - symbol: token0.symbol!, - decimals: String(token0.decimals), - }, - quotient: pair.reserve0.quotient.toString(), - }, - reserve1: { - token: { - address: token1.address, - chainId: 1, - symbol: token1.symbol!, - decimals: String(token1.decimals), - }, - quotient: pair.reserve1.quotient.toString(), - }, - amountIn, - amountOut, - } - } - - const mockV3PoolInRoute = ( - pool: Pool, - tokenIn: Token, - tokenOut: Token, - amountIn: string, - amountOut: string - ): V3PoolInRoute => { - return { - type: PoolType.V3Pool, - tokenIn: { - address: tokenIn.address, - chainId: 1, - symbol: tokenIn.symbol!, - decimals: String(tokenIn.decimals), - }, - tokenOut: { - address: tokenOut.address, - chainId: 1, - symbol: tokenOut.symbol!, - decimals: String(tokenOut.decimals), - }, - sqrtRatioX96: pool.sqrtRatioX96.toString(), - liquidity: pool.liquidity.toString(), - tickCurrent: pool.tickCurrent.toString(), - fee: pool.fee.toString(), - amountIn, - amountOut, - } - } - function compareUniswapTrades(left: UniswapTrade, right: UniswapTrade): void { expect(SwapRouter.swapCallParameters(left).calldata).to.eq(SwapRouter.swapCallParameters(right).calldata) expect(SwapRouter.swapCallParameters(left).value).to.eq(SwapRouter.swapCallParameters(right).value) @@ -1186,4 +1186,97 @@ describe('Uniswap', () => { }) }) } + + describe('RouterTradeAdapter handles malformed classic quote', () => { + it('throws on missing route', async () => { + const classicQuote: any = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw('Expected route to be present') + }) + it('throws on no route', async () => { + const classicQuote: any = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [], + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw( + 'Expected there to be at least one route' + ) + }) + it('throws on route with no pools', async () => { + const classicQuote: any = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [[]], + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw( + 'Expected all routes to have at least one pool' + ) + }) + it('throws on quote missing tokenIn/Out', async () => { + const classicQuote: any = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + { + ...mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, '1000', '1000'), + tokenIn: undefined, + }, + ], + ], + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw( + 'Expected both tokenIn and tokenOut to be present' + ) + }) + it('throws on route with mismatched token chainIds', async () => { + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + { + ...mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, '1000', '1000'), + tokenIn: { + address: DAI.address, + // Different chainId + chainId: 2, + symbol: DAI.symbol!, + decimals: String(DAI.decimals), + }, + }, + ], + ], + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw( + 'Expected tokenIn and tokenOut to be have same chainId' + ) + }) + it('throws on route with missing amountIn/Out', async () => { + const classicQuote: any = { + tokenIn: WETH.address, + tokenOut: USDC.address, + tradeType: TradeType.EXACT_INPUT, + route: [ + [ + { + ...mockV2PoolInRoute(USDC_DAI_V2, DAI, USDC, '1000', '1000'), + amountIn: undefined, + }, + ], + ], + } + expect(() => RouterTradeAdapter.fromClassicQuote(classicQuote)).to.throw( + 'Expected both raw amountIn and raw amountOut to be present' + ) + }) + }) }) From d6c433cc94b3d9b63056ebff1d2d86770ada6ccd Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 18:06:57 -0400 Subject: [PATCH 22/27] Remove weth mapping --- src/utils/constants.ts | 124 ---------------------------------------- test/forge/interop.json | 2 +- 2 files changed, 1 insertion(+), 125 deletions(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 0448988..1e50485 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -107,130 +107,6 @@ const CHAIN_CONFIGS: { [key: number]: ChainConfig } = { }, } -export const WRAPPED_NATIVE_CURRENCY: { [chainId in ChainId]: Token } = { - [ChainId.MAINNET]: new Token(1, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'Wrapped Ether'), - [ChainId.GOERLI]: new Token(5, '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 18, 'WETH', 'Wrapped Ether'), - [ChainId.SEPOLIA]: new Token(11155111, '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14', 18, 'WETH', 'Wrapped Ether'), - [ChainId.BNB]: new Token(56, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB', 'Wrapped BNB'), - [ChainId.OPTIMISM]: new Token( - ChainId.OPTIMISM, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.OPTIMISM_GOERLI]: new Token( - ChainId.OPTIMISM_GOERLI, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.OPTIMISM_SEPOLIA]: new Token( - ChainId.OPTIMISM_SEPOLIA, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.ARBITRUM_ONE]: new Token( - ChainId.ARBITRUM_ONE, - '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.ARBITRUM_GOERLI]: new Token( - ChainId.ARBITRUM_GOERLI, - '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.ARBITRUM_SEPOLIA]: new Token( - ChainId.ARBITRUM_SEPOLIA, - '0xc556bAe1e86B2aE9c22eA5E036b07E55E7596074', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.POLYGON]: new Token( - ChainId.POLYGON, - '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', - 18, - 'WMATIC', - 'Wrapped MATIC' - ), - [ChainId.POLYGON_MUMBAI]: new Token( - ChainId.POLYGON_MUMBAI, - '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889', - 18, - 'WMATIC', - 'Wrapped MATIC' - ), - - // The Celo native currency 'CELO' implements the erc-20 token standard - [ChainId.CELO]: new Token( - ChainId.CELO, - '0x471EcE3750Da237f93B8E339c536989b8978a438', - 18, - 'CELO', - 'Celo native asset' - ), - [ChainId.CELO_ALFAJORES]: new Token( - ChainId.CELO_ALFAJORES, - '0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9', - 18, - 'CELO', - 'Celo native asset' - ), - [ChainId.GNOSIS]: new Token( - ChainId.GNOSIS, - '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', - 18, - 'WXDAI', - 'Wrapped XDAI on Gnosis' - ), - [ChainId.MOONBEAM]: new Token( - ChainId.MOONBEAM, - '0xAcc15dC74880C9944775448304B263D191c6077F', - 18, - 'WGLMR', - 'Wrapped GLMR' - ), - [ChainId.AVALANCHE]: new Token( - ChainId.AVALANCHE, - '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', - 18, - 'WAVAX', - 'Wrapped AVAX' - ), - [ChainId.BASE]: new Token(ChainId.BASE, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), - [ChainId.BASE_GOERLI]: new Token( - ChainId.BASE_GOERLI, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.ROOTSTOCK]: new Token( - ChainId.ROOTSTOCK, - '0x542fDA317318eBF1d3DEAf76E0b632741A7e677d', - 18, - 'WRBTC', - 'Wrapped BTC' - ), - [ChainId.ZORA]: new Token(ChainId.ZORA, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'), - [ChainId.ZORA_SEPOLIA]: new Token( - ChainId.ZORA_SEPOLIA, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether' - ), - [ChainId.BLAST]: new Token(ChainId.BLAST, '0x4300000000000000000000000000000000000004', 18, 'WETH', 'Wrapped Ether'), -} - export const UNIVERSAL_ROUTER_ADDRESS = (chainId: number): string => { if (!(chainId in CHAIN_CONFIGS)) throw new Error(`Universal Router not deployed on chain ${chainId}`) return CHAIN_CONFIGS[chainId].router diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file From 384a5536f547876b8b9120d3ee9757dd368d47bd Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 19:05:22 -0400 Subject: [PATCH 23/27] fix mixed routes test --- test/uniswapTrades.test.ts | 50 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index bf144d9..d9ae13d 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -3,9 +3,9 @@ import JSBI from 'jsbi' import { BigNumber, ethers, utils, Wallet } from 'ethers' import { expandTo18Decimals } from '../src/utils/numbers' import { SwapRouter, UniswapTrade, FlatFeeOptions } from '../src' -import { MixedRouteTrade, MixedRouteSDK } from '@uniswap/router-sdk' +import { MixedRouteTrade, MixedRouteSDK, MixedRoute } from '@uniswap/router-sdk' import { Trade as V2Trade, Pair, Route as RouteV2 } from '@uniswap/v2-sdk' -import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions } from '@uniswap/v3-sdk' +import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions, Trade } from '@uniswap/v3-sdk' import { generatePermitSignature, toInputPermit, makePermit, generateEip2098PermitSignature } from './utils/permit2' import { CurrencyAmount, Ether, Percent, Token, TradeType } from '@uniswap/sdk-core' import { registerFixture } from './forge/writeInterop' @@ -1138,6 +1138,52 @@ describe('Uniswap', () => { compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) }) + // Mixed routes are only supported for exact input + if(tradeType === TradeType.EXACT_INPUT) { + it('v2/v3 - mixed route erc20 <> erc20', async () => { + const [tokenIn, tokenOut] = [DAI, WETH] + const inputAmount = ethers.utils + .parseUnits('1', getAmountToken(tokenIn, tokenOut, tradeType).decimals) + .toString() + const rawInputAmount = getAmount(tokenIn, tokenOut, inputAmount, tradeType) + + const opts = swapOptions({}) + const trade = await MixedRouteTrade.fromRoute( + new MixedRouteSDK([USDC_DAI_V3, WETH_USDC_V2], tokenIn, tokenOut), + rawInputAmount, + tradeType + ) + + const classicQuote: PartialClassicQuote = { + tokenIn: DAI.address, + tokenOut: USDC.address, + tradeType, + route: [ + [ + mockV3PoolInRoute( + USDC_DAI_V3, + DAI, + USDC, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + mockV2PoolInRoute( + WETH_USDC_V2, + USDC, + WETH, + trade.inputAmount.quotient.toString(), + trade.outputAmount.quotient.toString() + ), + ], + ], + } + const routerTrade = RouterTradeAdapter.fromClassicQuote(classicQuote) + + compareUniswapTrades(new UniswapTrade(buildTrade([trade]), opts), new UniswapTrade(routerTrade, opts)) + }) + } + + it('v3 - handles split routes properly', async () => { const [tokenIn, tokenOut] = [WETH, USDC] const inputAmount = ethers.utils From b42f0f3e2686bef68055f48e7a742a48b3fc9993 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 19:08:02 -0400 Subject: [PATCH 24/27] fix imports --- test/uniswapTrades.test.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index d9ae13d..b20b428 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -3,9 +3,9 @@ import JSBI from 'jsbi' import { BigNumber, ethers, utils, Wallet } from 'ethers' import { expandTo18Decimals } from '../src/utils/numbers' import { SwapRouter, UniswapTrade, FlatFeeOptions } from '../src' -import { MixedRouteTrade, MixedRouteSDK, MixedRoute } from '@uniswap/router-sdk' +import { MixedRouteTrade, MixedRouteSDK } from '@uniswap/router-sdk' import { Trade as V2Trade, Pair, Route as RouteV2 } from '@uniswap/v2-sdk' -import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions, Trade } from '@uniswap/v3-sdk' +import { Trade as V3Trade, Route as RouteV3, Pool, FeeOptions } from '@uniswap/v3-sdk' import { generatePermitSignature, toInputPermit, makePermit, generateEip2098PermitSignature } from './utils/permit2' import { CurrencyAmount, Ether, Percent, Token, TradeType } from '@uniswap/sdk-core' import { registerFixture } from './forge/writeInterop' @@ -13,7 +13,6 @@ import { buildTrade, getUniswapPools, swapOptions, ETHER, DAI, USDC, WETH } from import { hexToDecimalString } from './utils/hexToDecimalString' import { FORGE_PERMIT2_ADDRESS, FORGE_ROUTER_ADDRESS, TEST_FEE_RECIPIENT_ADDRESS } from './utils/addresses' import { - NativeCurrency, PartialClassicQuote, PoolType, RouterTradeAdapter, @@ -785,18 +784,18 @@ describe('Uniswap', () => { for (let tradeType of [TradeType.EXACT_INPUT, TradeType.EXACT_OUTPUT]) { describe('RouterTradeAdapter ' + tradeType, () => { const getAmountToken = ( - tokenIn: Token | NativeCurrency, - tokenOut: Token | NativeCurrency, + tokenIn: Token | Ether, + tokenOut: Token | Ether, tradeType: TradeType - ): Token | NativeCurrency => { + ): Token | Ether => { return tradeType === TradeType.EXACT_INPUT ? tokenIn : tokenOut } const getAmount = ( - tokenIn: Token | NativeCurrency, - tokenOut: Token | NativeCurrency, + tokenIn: Token | Ether, + tokenOut: Token | Ether, amount: string, tradeType: TradeType - ): CurrencyAmount => { + ): CurrencyAmount => { return tradeType === TradeType.EXACT_INPUT ? CurrencyAmount.fromRawAmount(tokenIn, amount) : CurrencyAmount.fromRawAmount(tokenOut, amount) @@ -1139,7 +1138,7 @@ describe('Uniswap', () => { }) // Mixed routes are only supported for exact input - if(tradeType === TradeType.EXACT_INPUT) { + if (tradeType === TradeType.EXACT_INPUT) { it('v2/v3 - mixed route erc20 <> erc20', async () => { const [tokenIn, tokenOut] = [DAI, WETH] const inputAmount = ethers.utils @@ -1183,7 +1182,6 @@ describe('Uniswap', () => { }) } - it('v3 - handles split routes properly', async () => { const [tokenIn, tokenOut] = [WETH, USDC] const inputAmount = ethers.utils From 8766773069e78880df7e3d52dc508bca2a421ac0 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 19:08:32 -0400 Subject: [PATCH 25/27] prettier --- test/forge/interop.json | 2 +- test/uniswapTrades.test.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +} diff --git a/test/uniswapTrades.test.ts b/test/uniswapTrades.test.ts index b20b428..9b52b2b 100644 --- a/test/uniswapTrades.test.ts +++ b/test/uniswapTrades.test.ts @@ -783,11 +783,7 @@ describe('Uniswap', () => { for (let tradeType of [TradeType.EXACT_INPUT, TradeType.EXACT_OUTPUT]) { describe('RouterTradeAdapter ' + tradeType, () => { - const getAmountToken = ( - tokenIn: Token | Ether, - tokenOut: Token | Ether, - tradeType: TradeType - ): Token | Ether => { + const getAmountToken = (tokenIn: Token | Ether, tokenOut: Token | Ether, tradeType: TradeType): Token | Ether => { return tradeType === TradeType.EXACT_INPUT ? tokenIn : tokenOut } const getAmount = ( From 02eedd7c96dba652980e59aee7991afede8d4419 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 29 Mar 2024 11:07:40 -0400 Subject: [PATCH 26/27] Fix comments --- src/entities/protocols/uniswap.ts | 7 ++++--- src/utils/constants.ts | 1 - test/forge/interop.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/entities/protocols/uniswap.ts b/src/entities/protocols/uniswap.ts index b44c96a..4163889 100644 --- a/src/entities/protocols/uniswap.ts +++ b/src/entities/protocols/uniswap.ts @@ -30,8 +30,9 @@ export type FlatFeeOptions = { // the existing router permit object doesn't include enough data for permit2 // so we extend swap options with the permit2 permit // when safe mode is enabled, the SDK will add an extra ETH sweep for security +// when useRouterBalance is enabled the SDK will use the balance in the router for the swap export type SwapOptions = Omit & { - directSend?: boolean + useRouterBalance?: boolean inputTokenPermit?: Permit2Permit flatFee?: FlatFeeOptions safeMode?: boolean @@ -55,7 +56,7 @@ export class UniswapTrade implements Command { if (!!options.fee && !!options.flatFee) throw new Error('Only one fee option permitted') if (this.inputRequiresWrap) this.payerIsUser = false - else if (this.options.directSend) this.payerIsUser = false + else if (this.options.useRouterBalance) this.payerIsUser = false else this.payerIsUser = true } @@ -65,7 +66,7 @@ export class UniswapTrade implements Command { encode(planner: RoutePlanner, _config: TradeConfig): void { // If the input currency is the native currency, we need to wrap it with the router as the recipient - if (this.trade.inputAmount.currency.isNative) { + if (this.inputRequiresWrap) { // TODO: optimize if only one v2 pool we can directly send this to the pool planner.addCommand(CommandType.WRAP_ETH, [ ROUTER_AS_RECIPIENT, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 1e50485..c126255 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,4 +1,3 @@ -import { ChainId, Token } from '@uniswap/sdk-core' import { BigNumber } from 'ethers' type ChainConfig = { diff --git a/test/forge/interop.json b/test/forge/interop.json index d4c1e55..0bc773c 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} +} \ No newline at end of file From 8e6ee01eb4de2b25cefff6dc529f8a33267998df Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 29 Mar 2024 12:12:29 -0400 Subject: [PATCH 27/27] prettier fix --- test/forge/interop.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/forge/interop.json b/test/forge/interop.json index 0bc773c..d4c1e55 100644 --- a/test/forge/interop.json +++ b/test/forge/interop.json @@ -247,4 +247,4 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000003eb3459f0ce6ae000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f46b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", "value": "1000000000000000000" } -} \ No newline at end of file +}