From c65b595307d5664d2233639185610f2eb3b23770 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 28 Mar 2024 18:05:23 -0400 Subject: [PATCH] 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 1414c9a3..a6aacfff 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 0bc773c9..d4c1e555 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 4877bfd2..bf144d96 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' + ) + }) + }) })