From 17187981c477df081223a50f46ff6470deb8d843 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 04:19:25 +0000 Subject: [PATCH 01/10] init --- .../liquidity-providers/PancakeSwapV3.ts | 6 + .../liquidity-providers/UniswapV3Base.ts | 79 +++++---- .../route-processor/test/DataFetcher.test.ts | 155 +++++++++++++++--- 3 files changed, 188 insertions(+), 52 deletions(-) diff --git a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts index 1c5462dec2..cd4725ab3b 100644 --- a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts @@ -1,9 +1,15 @@ import { PublicClient } from 'viem' import { ChainId } from '../../chain/index.js' +// import { +// PANCAKESWAP_V3_FEE_SPACING_MAP, +// PancakeSwapV3FeeAmount, +// } from '../../config/index.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' export class PancakeSwapV3Provider extends UniswapV3BaseProvider { + // override FEE = PancakeSwapV3FeeAmount + // override TICK_SPACINGS = PANCAKESWAP_V3_FEE_SPACING_MAP constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.ARBITRUM]: '0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9', diff --git a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts index fa356b5436..7b28f1e0a6 100644 --- a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts +++ b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts @@ -11,30 +11,35 @@ import { memoizer } from '../memoizer.js' import { type PoolCode, UniV3PoolCode } from '../pool-codes/index.js' import { LiquidityProvider } from './LiquidityProvider.js' +export interface UniV3FeeType { + readonly LOWEST: number + readonly LOW: number + readonly MEDIUM: number + readonly HIGH: number +} + +export type UniV3TickSpacingType = { + readonly [key: UniV3FeeType[keyof UniV3FeeType]]: number +} + interface StaticPool { address: Address token0: Token token1: Token - fee: SushiSwapV3FeeAmount + fee: keyof UniV3TickSpacingType } interface V3Pool { address: Address token0: Token token1: Token - fee: SushiSwapV3FeeAmount + fee: keyof UniV3TickSpacingType sqrtPriceX96: bigint activeTick: number } export const NUMBER_OF_SURROUNDING_TICKS = 1000 // 10% price impact -const getActiveTick = (tickCurrent: number, feeAmount: SushiSwapV3FeeAmount) => - typeof tickCurrent === 'number' && feeAmount - ? Math.floor(tickCurrent / TICK_SPACINGS[feeAmount]) * - TICK_SPACINGS[feeAmount] - : undefined - const bitmapIndex = (tick: number, tickSpacing: number) => { return Math.floor(tick / tickSpacing / 256) } @@ -42,6 +47,8 @@ const bitmapIndex = (tick: number, tickSpacing: number) => { type PoolFilter = { has: (arg: string) => boolean } export abstract class UniswapV3BaseProvider extends LiquidityProvider { + TICK_SPACINGS: UniV3TickSpacingType = TICK_SPACINGS + FEE: UniV3FeeType = SushiSwapV3FeeAmount poolsByTrade: Map = new Map() pools: Map = new Map() @@ -76,6 +83,15 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { } } + getActiveTick = ( + tickCurrent: number, + feeAmount: UniV3FeeType[keyof UniV3FeeType], + ) => + typeof tickCurrent === 'number' && feeAmount + ? Math.floor(tickCurrent / this.TICK_SPACINGS[feeAmount]!) * + this.TICK_SPACINGS[feeAmount]! + : undefined + async fetchPoolsForToken( t0: Token, t1: Token, @@ -165,7 +181,7 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { const tick = slot0[i]!.result?.[1] if (!sqrtPriceX96 || sqrtPriceX96 === 0n || typeof tick !== 'number') return - const activeTick = getActiveTick(tick, pool.fee) + const activeTick = this.getActiveTick(tick, pool.fee) if (typeof activeTick !== 'number') return existingPools.push({ ...pool, @@ -287,13 +303,13 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { const minIndexes = existingPools.map((pool) => bitmapIndex( pool.activeTick - NUMBER_OF_SURROUNDING_TICKS, - TICK_SPACINGS[pool.fee], + this.TICK_SPACINGS[pool.fee]!, ), ) const maxIndexes = existingPools.map((pool) => bitmapIndex( pool.activeTick + NUMBER_OF_SURROUNDING_TICKS, - TICK_SPACINGS[pool.fee], + this.TICK_SPACINGS[pool.fee]!, ), ) @@ -383,7 +399,8 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { })).sort((a, b) => a.index - b.index) const lowerUnknownTick = - minIndexes[i]! * TICK_SPACINGS[pool.fee] * 256 - TICK_SPACINGS[pool.fee] + minIndexes[i]! * this.TICK_SPACINGS[pool.fee]! * 256 - + this.TICK_SPACINGS[pool.fee]! console.assert( poolTicks.length === 0 || lowerUnknownTick < poolTicks[0]!.index, 'Error 236: unexpected min tick index', @@ -393,7 +410,7 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { DLiquidity: 0n, }) const upperUnknownTick = - (maxIndexes[i]! + 1) * TICK_SPACINGS[pool.fee] * 256 + (maxIndexes[i]! + 1) * this.TICK_SPACINGS[pool.fee]! * 256 console.assert( poolTicks[poolTicks.length - 1]!.index < upperUnknownTick, 'Error 244: unexpected max tick index', @@ -435,25 +452,23 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { getStaticPools(t1: Token, t2: Token): StaticPool[] { const currencyCombinations = getCurrencyCombinations(this.chainId, t1, t2) - const allCurrencyCombinationsWithAllFees: [ - Type, - Type, - SushiSwapV3FeeAmount, - ][] = currencyCombinations.reduce< - [Currency, Currency, SushiSwapV3FeeAmount][] - >((list, [tokenA, tokenB]) => { - if (tokenA !== undefined && tokenB !== undefined) { - return list.concat([ - [tokenA, tokenB, SushiSwapV3FeeAmount.LOWEST], - [tokenA, tokenB, SushiSwapV3FeeAmount.LOW], - [tokenA, tokenB, SushiSwapV3FeeAmount.MEDIUM], - [tokenA, tokenB, SushiSwapV3FeeAmount.HIGH], - ]) - } - return [] - }, []) - - const filtered: [Token, Token, SushiSwapV3FeeAmount][] = [] + const allCurrencyCombinationsWithAllFees: [Type, Type, number][] = + currencyCombinations.reduce<[Currency, Currency, number][]>( + (list, [tokenA, tokenB]) => { + if (tokenA !== undefined && tokenB !== undefined) { + return list.concat([ + [tokenA, tokenB, this.FEE.LOWEST], + [tokenA, tokenB, this.FEE.LOW], + [tokenA, tokenB, this.FEE.MEDIUM], + [tokenA, tokenB, this.FEE.HIGH], + ]) + } + return [] + }, + [], + ) + + const filtered: [Token, Token, number][] = [] allCurrencyCombinationsWithAllFees.forEach( ([currencyA, currencyB, feeAmount]) => { if (currencyA && currencyB && feeAmount) { diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index 10e83ff5ab..bea1bd906d 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -11,7 +11,7 @@ import { USDT, WNATIVE, } from 'sushi/currency' -import { DataFetcher, LiquidityProviders } from 'sushi/router' +import { DataFetcher, LiquidityProviders, Router } from 'sushi/router' async function testDF( _chainName: string, @@ -69,6 +69,40 @@ function reportMissingDexes(reports: Record[]): { else return { hasMissingDex: false, missingDexNames } } +// tries to find a route for a token pair from current fetched pools +function findRoute( + dataFetcher: DataFetcher, + fromToken: Type, + toToken: Type, + chainId: ChainId, +): boolean { + try { + // find the best route map + const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken) + const route = Router.findBestRoute( + pcMap, + chainId, + fromToken, + BigInt(`1${'0'.repeat(fromToken.decimals)}`), + toToken, + 30e9, + ) + // call rp4 route data encoder to build route data + Router.routeProcessor4Params( + pcMap, + route, + fromToken, + toToken, + `0x${'1'.repeat(40)}`, + `0x${'2'.repeat(40)}`, + ) + return route.status !== 'NoWay' + } catch { + // no route found + return false + } +} + // exclude testnets and unsupported chains (chains with no dex/pool) const excludedChains = [ ...TESTNET_CHAIN_IDS, @@ -93,6 +127,7 @@ async function runTest() { dataFetcher.startDataFetching() console.log(chName) const allFoundPools = [] + const foundRouteReports = [] // a pool with this pair is available in most dexes and chains, but some may not have this, so // for those, other pairs are tried if there happened to be a missing dex from previous results @@ -107,11 +142,22 @@ async function runTest() { ), ) + // check that route is found and rp4 encoder encoded a route data correctly + // this is repeated for each pair throughout the test + foundRouteReports.push( + findRoute( + dataFetcher, + WNATIVE[chainId], + USDC[chainId as keyof typeof USDC], + chainId, + ), + ) + // only for Dfyn and JetSwap on fantom chain if ( chainId === ChainId.FANTOM && reportMissingDexes(allFoundPools).hasMissingDex - ) + ) { allFoundPools.push( await testDF( chName, @@ -122,12 +168,21 @@ async function runTest() { 'DAI', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + WNATIVE[chainId], + DAI[chainId as keyof typeof DAI], + chainId, + ), + ) + } // only for Blast chain if ( chainId === ChainId.BLAST && reportMissingDexes(allFoundPools).hasMissingDex - ) + ) { allFoundPools.push( await testDF( chName, @@ -138,51 +193,80 @@ async function runTest() { 'USDB', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + WNATIVE[chainId], + USDB[chainId as keyof typeof USDB], + chainId, + ), + ) + } // only for Moonbeam chain if ( chainId === ChainId.MOONBEAM && reportMissingDexes(allFoundPools).hasMissingDex - ) + ) { + const token = new Token({ + chainId: ChainId.MOONBEAM, + address: '0xA649325Aa7C5093d12D6F98EB4378deAe68CE23F', + decimals: 18, + symbol: 'BUSD', + }) allFoundPools.push( await testDF( chName, dataFetcher, - new Token({ - chainId: ChainId.MOONBEAM, - address: '0xA649325Aa7C5093d12D6F98EB4378deAe68CE23F', - decimals: 18, - symbol: 'BUSD', - }), + token, USDC[chainId as keyof typeof USDC], 'USDC', 'BUSD', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + token, + USDC[chainId as keyof typeof USDC], + chainId, + ), + ) + } // only for Elk dex on Moonriver since it only has 1 pool deployed which is with following pair if ( chainId === ChainId.MOONRIVER && reportMissingDexes(allFoundPools).hasMissingDex - ) + ) { + const token = new Token({ + chainId: ChainId.MOONRIVER, + address: '0xE1C110E1B1b4A1deD0cAf3E42BfBdbB7b5d7cE1C', + decimals: 18, + symbol: 'ELK', + }) allFoundPools.push( await testDF( chName, dataFetcher, DAI[chainId as keyof typeof DAI], - new Token({ - chainId: ChainId.MOONRIVER, - address: '0xE1C110E1B1b4A1deD0cAf3E42BfBdbB7b5d7cE1C', - decimals: 18, - symbol: 'ELK', - }), + token, 'DAI', 'ELK', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + DAI[chainId as keyof typeof DAI], + token, + chainId, + ), + ) + } // shared pairs for all chains and dexes - if (reportMissingDexes(allFoundPools).hasMissingDex) + if (reportMissingDexes(allFoundPools).hasMissingDex) { allFoundPools.push( await testDF( chName, @@ -193,8 +277,17 @@ async function runTest() { 'USDT', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + WNATIVE[chainId], + USDT[chainId as keyof typeof USDT], + chainId, + ), + ) + } - if (reportMissingDexes(allFoundPools).hasMissingDex) + if (reportMissingDexes(allFoundPools).hasMissingDex) { allFoundPools.push( await testDF( chName, @@ -205,8 +298,17 @@ async function runTest() { 'FRAX', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + SUSHI[chainId as keyof typeof SUSHI], + FRAX[chainId as keyof typeof FRAX], + chainId, + ), + ) + } - if (reportMissingDexes(allFoundPools).hasMissingDex) + if (reportMissingDexes(allFoundPools).hasMissingDex) { allFoundPools.push( await testDF( chName, @@ -217,9 +319,22 @@ async function runTest() { 'USDT', ), ) + foundRouteReports.push( + findRoute( + dataFetcher, + SUSHI[chainId as keyof typeof SUSHI], + USDT[chainId as keyof typeof USDT], + chainId, + ), + ) + } dataFetcher.stopDataFetching() + // should have found route + assert.ok(foundRouteReports.some((v) => v)) + + // should not have any missing dex const { hasMissingDex, missingDexNames } = reportMissingDexes(allFoundPools) if (hasMissingDex) { From e0a979ef2796ed3c1a69341e2bf09afa68e7dbf7 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 04:24:16 +0000 Subject: [PATCH 02/10] Update PancakeSwapV3.ts --- .../src/router/liquidity-providers/PancakeSwapV3.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts index cd4725ab3b..5cbf4374b0 100644 --- a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts @@ -1,15 +1,15 @@ import { PublicClient } from 'viem' import { ChainId } from '../../chain/index.js' -// import { -// PANCAKESWAP_V3_FEE_SPACING_MAP, -// PancakeSwapV3FeeAmount, -// } from '../../config/index.js' +import { + PANCAKESWAP_V3_FEE_SPACING_MAP, + PancakeSwapV3FeeAmount, +} from '../../config/index.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' export class PancakeSwapV3Provider extends UniswapV3BaseProvider { - // override FEE = PancakeSwapV3FeeAmount - // override TICK_SPACINGS = PANCAKESWAP_V3_FEE_SPACING_MAP + override FEE = PancakeSwapV3FeeAmount + override TICK_SPACINGS = PANCAKESWAP_V3_FEE_SPACING_MAP constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.ARBITRUM]: '0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9', From 065bde99be92b319768c36709df251d657d834a9 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 15:00:32 +0000 Subject: [PATCH 03/10] Update DataFetcher.test.ts --- .../route-processor/test/DataFetcher.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index bea1bd906d..ea5af40f9f 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -1,6 +1,7 @@ import assert from 'assert' import { ChainId, TESTNET_CHAIN_IDS, chainName } from 'sushi/chain' import { + BUSD, DAI, FRAX, SUSHI, @@ -75,6 +76,7 @@ function findRoute( fromToken: Type, toToken: Type, chainId: ChainId, + liquidityProviders?: LiquidityProviders[], ): boolean { try { // find the best route map @@ -86,6 +88,7 @@ function findRoute( BigInt(`1${'0'.repeat(fromToken.decimals)}`), toToken, 30e9, + liquidityProviders, ) // call rp4 route data encoder to build route data Router.routeProcessor4Params( @@ -153,6 +156,36 @@ async function runTest() { ), ) + // only for pancakev3 with 0.2.5% fee pool pair + if (chainId === ChainId.BSC) { + const token = new Token({ + chainId: ChainId.BSC, + address: '0x4BE35Ec329343d7d9F548d42B0F8c17FFfe07db4', + decimals: 18, + symbol: 'USDT.z', + }) + allFoundPools.push( + await testDF( + chName, + dataFetcher, + token, + BUSD[chainId as keyof typeof BUSD], + 'USDT.z', + 'BUSD', + ), + ) + // explicitly find route for pancakev3 + const route = findRoute( + dataFetcher, + token, + BUSD[chainId as keyof typeof BUSD], + chainId, + [LiquidityProviders.PancakeSwapV3], + ) + assert(route) + foundRouteReports.push(route) + } + // only for Dfyn and JetSwap on fantom chain if ( chainId === ChainId.FANTOM && From 85624696f5716c2c2463488243cf20e8566e0bb2 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 15:18:12 +0000 Subject: [PATCH 04/10] Update DataFetcher.test.ts --- .../route-processor/test/DataFetcher.test.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index ea5af40f9f..9cda277ca9 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -174,16 +174,25 @@ async function runTest() { 'BUSD', ), ) - // explicitly find route for pancakev3 - const route = findRoute( - dataFetcher, + const pcMap = dataFetcher.getCurrentPoolCodeMap( token, BUSD[chainId as keyof typeof BUSD], - chainId, - [LiquidityProviders.PancakeSwapV3], ) - assert(route) - foundRouteReports.push(route) + assert.ok( + !pcMap.get( + '0xB30b2030b2F950401aBCD69763e9D0F81958d72d'.toLowerCase(), + ), + ) + + foundRouteReports.push( + findRoute( + dataFetcher, + token, + BUSD[chainId as keyof typeof BUSD], + chainId, + [LiquidityProviders.PancakeSwapV3], + ), + ) } // only for Dfyn and JetSwap on fantom chain From 5dc4df2494e5e73fbb8fd8e460d41f862d17317b Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 18:55:57 +0000 Subject: [PATCH 05/10] explicitly checks univ3 dexes fess/ticks --- .../liquidity-providers/LiquidityProvider.ts | 45 ++++++++++++++ .../liquidity-providers/PancakeSwapV3.ts | 60 ++++++++++++++++++- .../liquidity-providers/UniswapV3Base.ts | 31 +++++++++- .../route-processor/test/DataFetcher.test.ts | 32 ++++++---- 4 files changed, 155 insertions(+), 13 deletions(-) diff --git a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts index 9037bdbd08..8c770c7462 100644 --- a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts +++ b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts @@ -123,3 +123,48 @@ export abstract class LiquidityProvider { .sort((first, second) => (first > second ? -1 : 1)) .join(':') } + +export const UniV2LiquidityProviders: LiquidityProviders[] = [ + LiquidityProviders.SushiSwapV2, + LiquidityProviders.UniswapV2, + LiquidityProviders.QuickSwap, + LiquidityProviders.ApeSwap, + LiquidityProviders.PancakeSwapV2, + LiquidityProviders.TraderJoe, + LiquidityProviders.Dfyn, + LiquidityProviders.Elk, + LiquidityProviders.JetSwap, + LiquidityProviders.SpookySwapV2, + LiquidityProviders.NetSwap, + LiquidityProviders.HoneySwap, + LiquidityProviders.UbeSwap, + LiquidityProviders.Biswap, + LiquidityProviders.LaserSwap, + LiquidityProviders.BaseSwap, + LiquidityProviders.Solarbeam, + LiquidityProviders.Swapsicle, + LiquidityProviders.VVSStandard, + LiquidityProviders.Fraxswap, + LiquidityProviders.SwapBlast, + LiquidityProviders.BlastDEX, + LiquidityProviders.MonoswapV2, + LiquidityProviders.ThrusterV2, + LiquidityProviders.DyorV2, + LiquidityProviders.HyperBlast, + LiquidityProviders.KinetixV2, + LiquidityProviders.Camelot, + LiquidityProviders.Enosys, + LiquidityProviders.BlazeSwap, +] + +export const UniV3LiquidityProviders = [ + LiquidityProviders.SushiSwapV3, + LiquidityProviders.UniswapV3, + LiquidityProviders.DovishV3, + LiquidityProviders.KinetixV3, + LiquidityProviders.MonoswapV3, + LiquidityProviders.ThrusterV3, + LiquidityProviders.SpookySwapV3, + LiquidityProviders.PancakeSwapV3, + LiquidityProviders.Wagmi, +] as const diff --git a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts index 5cbf4374b0..e406e22e25 100644 --- a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts @@ -1,4 +1,5 @@ -import { PublicClient } from 'viem' +import { Address, PublicClient } from 'viem' +import { uniswapV3FactoryAbi } from '../../abi/uniswapV3FactoryAbi.js' import { ChainId } from '../../chain/index.js' import { PANCAKESWAP_V3_FEE_SPACING_MAP, @@ -53,4 +54,61 @@ export class PancakeSwapV3Provider extends UniswapV3BaseProvider { getPoolProviderName(): string { return 'PancakeSwapV3' } + override async ensureFeeAndTicks(): Promise { + const feeList = [ + this.FEE.LOWEST, + this.FEE.LOW, + this.FEE.MEDIUM, + this.FEE.HIGH, + ] as number[] + const factoryAddress = ( + await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: [ + { + address: this.factory[this.chainId as keyof typeof this.factory]!, + abi: [ + { + inputs: [], + name: 'factoryAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'factoryAddress', + } as const, + ], + }) + )[0] + + const results = (await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: feeList.map( + (fee) => + ({ + chainId: this.chainId, + address: factoryAddress as Address, + abi: uniswapV3FactoryAbi, + functionName: 'feeAmountTickSpacing', + args: [fee], + }) as const, + ), + })) as number[] + + // fetched fee map to ticks should match correctly with hardcoded literals in the dex + return results.every( + (v, i) => this.TICK_SPACINGS[feeList[i] as PancakeSwapV3FeeAmount] === v, + ) + } } diff --git a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts index 7b28f1e0a6..1090a08462 100644 --- a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts +++ b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts @@ -1,5 +1,5 @@ import { Address, PublicClient } from 'viem' -import { erc20Abi, tickLensAbi } from '../../abi/index.js' +import { erc20Abi, tickLensAbi, uniswapV3FactoryAbi } from '../../abi/index.js' import { ChainId } from '../../chain/index.js' import { SushiSwapV3FeeAmount, TICK_SPACINGS } from '../../config/index.js' import { Currency, Token, Type } from '../../currency/index.js' @@ -536,4 +536,33 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { if (this.unwatchBlockNumber) this.unwatchBlockNumber() this.blockListener = undefined } + + async ensureFeeAndTicks(): Promise { + const feeList = [ + this.FEE.LOWEST, + this.FEE.LOW, + this.FEE.MEDIUM, + this.FEE.HIGH, + ] as number[] + const results = (await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: feeList.map( + (fee) => + ({ + chainId: this.chainId, + address: this.factory[this.chainId as keyof typeof this.factory]!, + abi: uniswapV3FactoryAbi, + functionName: 'feeAmountTickSpacing', + args: [fee], + }) as const, + ), + })) as number[] + + // fetched fee map to ticks should match correctly with hardcoded literals in the dex + return results.every( + (v, i) => this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v, + ) + } } diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index 9cda277ca9..89f04f5a0e 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -1,7 +1,6 @@ import assert from 'assert' import { ChainId, TESTNET_CHAIN_IDS, chainName } from 'sushi/chain' import { - BUSD, DAI, FRAX, SUSHI, @@ -12,7 +11,13 @@ import { USDT, WNATIVE, } from 'sushi/currency' -import { DataFetcher, LiquidityProviders, Router } from 'sushi/router' +import { + DataFetcher, + LiquidityProviders, + Router, + UniV3LiquidityProviders, +} from 'sushi/router' +import { UniswapV3BaseProvider } from '../../../packages/sushi/dist/router/liquidity-providers/UniswapV3Base.js' async function testDF( _chainName: string, @@ -169,26 +174,22 @@ async function runTest() { chName, dataFetcher, token, - BUSD[chainId as keyof typeof BUSD], + USDT[chainId as keyof typeof USDT], 'USDT.z', - 'BUSD', + 'USDT', ), ) const pcMap = dataFetcher.getCurrentPoolCodeMap( token, - BUSD[chainId as keyof typeof BUSD], - ) - assert.ok( - !pcMap.get( - '0xB30b2030b2F950401aBCD69763e9D0F81958d72d'.toLowerCase(), - ), + USDT[chainId as keyof typeof USDT], ) + assert.ok(!!pcMap.get('0xB30b2030b2F950401aBCD69763e9D0F81958d72d')) foundRouteReports.push( findRoute( dataFetcher, token, - BUSD[chainId as keyof typeof BUSD], + USDT[chainId as keyof typeof USDT], chainId, [LiquidityProviders.PancakeSwapV3], ), @@ -373,6 +374,15 @@ async function runTest() { dataFetcher.stopDataFetching() + for (const dex of dataFetcher.providers) { + const dexName = dex.getType() + // if the current provider is univ3 type, ensure its fees and ticks + if (UniV3LiquidityProviders.some((v) => v === dexName)) { + const res = await (dex as UniswapV3BaseProvider).ensureFeeAndTicks() + assert.ok(res, `invalid fees/ticks for ${dexName} at ${chName}`) + } + } + // should have found route assert.ok(foundRouteReports.some((v) => v)) From 1836604dbe3875873747444cae383e5f253f4a26 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 20:43:40 +0000 Subject: [PATCH 06/10] fix bad dexes fees/ticks --- packages/sushi/src/config/viem.ts | 2 +- .../liquidity-providers/LiquidityProvider.ts | 2 +- .../router/liquidity-providers/MonoSwapV3.ts | 20 +++++ .../router/liquidity-providers/ThrusterV3.ts | 82 ++++++++++++++++++- .../src/router/liquidity-providers/Wagmi.ts | 30 +++++++ .../route-processor/test/DataFetcher.test.ts | 13 ++- 6 files changed, 144 insertions(+), 5 deletions(-) diff --git a/packages/sushi/src/config/viem.ts b/packages/sushi/src/config/viem.ts index fbf29f3516..62fad389e5 100644 --- a/packages/sushi/src/config/viem.ts +++ b/packages/sushi/src/config/viem.ts @@ -518,7 +518,7 @@ export const publicTransports = { [ChainId.GNOSIS]: http(gnosis.rpcUrls.default.http[0]), [ChainId.HARMONY]: http(harmonyOne.rpcUrls.default.http[0]), [ChainId.KAVA]: http(kava.rpcUrls.default.http[0]), - [ChainId.METIS]: http(metis.rpcUrls.default.http[0]), + [ChainId.METIS]: http('https://metis-mainnet.public.blastapi.io'), [ChainId.MOONBEAM]: http(moonbeam.rpcUrls.default.http[0]), [ChainId.MOONRIVER]: http(moonriver.rpcUrls.default.http[0]), [ChainId.OPTIMISM]: http(optimism.rpcUrls.default.http[0]), diff --git a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts index 8c770c7462..dc394543b6 100644 --- a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts +++ b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts @@ -155,7 +155,7 @@ export const UniV2LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.Camelot, LiquidityProviders.Enosys, LiquidityProviders.BlazeSwap, -] +] as const export const UniV3LiquidityProviders = [ LiquidityProviders.SushiSwapV3, diff --git a/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts b/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts index 6ebf4d9855..81400d4e24 100644 --- a/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts @@ -3,7 +3,27 @@ import { ChainId } from '../../chain/index.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' +enum MonoswapV3FeeAmount { + /** 0.01% */ + LOWEST = 100, + /** 0.05% */ + LOW = 500, + /** 0.3% */ + MEDIUM = 3000, + /** 1% */ + HIGH = 10000, +} + +const MonoswapV3TickSpacing: Record = { + 100: 0, + 500: 10, + 3000: 60, + 10_000: 200, +} + export class MonoswapV3Provider extends UniswapV3BaseProvider { + override FEE = MonoswapV3FeeAmount + override TICK_SPACINGS = MonoswapV3TickSpacing constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.BLAST]: '0x48d0F09710794313f33619c95147F34458BF7C3b', diff --git a/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts b/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts index ab7b3def22..2b8fc3962e 100644 --- a/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts +++ b/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts @@ -1,9 +1,31 @@ -import { PublicClient } from 'viem' +import { Address, PublicClient } from 'viem' +import { uniswapV3FactoryAbi } from '../../abi/uniswapV3FactoryAbi.js' import { ChainId } from '../../chain/index.js' +import { SushiSwapV3FeeAmount } from '../../config/sushiswap-v3.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' +enum ThrusterV3FeeAmount { + /** 0.01% */ + LOWEST = 100, + /** 0.05% */ + LOW = 500, + /** 0.3% */ + MEDIUM = 3000, + /** 1% */ + HIGH = 10000, +} + +const ThrusterV3TickSpacing: Record = { + 100: 0, + 500: 10, + 3000: 60, + 10_000: 200, +} + export class ThrusterV3Provider extends UniswapV3BaseProvider { + override FEE = ThrusterV3FeeAmount + override TICK_SPACINGS = ThrusterV3TickSpacing constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.BLAST]: '0xa08ae3d3f4dA51C22d3c041E468bdF4C61405AaB', @@ -23,4 +45,62 @@ export class ThrusterV3Provider extends UniswapV3BaseProvider { getPoolProviderName(): string { return 'ThrusterV3' } + + override async ensureFeeAndTicks(): Promise { + const feeList = [ + this.FEE.LOWEST, + this.FEE.LOW, + this.FEE.MEDIUM, + this.FEE.HIGH, + ] as number[] + const factoryAddress = ( + await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: [ + { + address: this.factory[this.chainId as keyof typeof this.factory]!, + abi: [ + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'factory', + } as const, + ], + }) + )[0] + + const results = (await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: feeList.map( + (fee) => + ({ + chainId: this.chainId, + address: factoryAddress as Address, + abi: uniswapV3FactoryAbi, + functionName: 'feeAmountTickSpacing', + args: [fee], + }) as const, + ), + })) as number[] + + // fetched fee map to ticks should match correctly with hardcoded literals in the dex + return results.every( + (v, i) => this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v, + ) + } } diff --git a/packages/sushi/src/router/liquidity-providers/Wagmi.ts b/packages/sushi/src/router/liquidity-providers/Wagmi.ts index 1ec5b38959..ab687f2d02 100644 --- a/packages/sushi/src/router/liquidity-providers/Wagmi.ts +++ b/packages/sushi/src/router/liquidity-providers/Wagmi.ts @@ -3,7 +3,27 @@ import { ChainId } from '../../chain/index.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' +enum WagmiFeeAmount { + /** 0.01% */ + LOWEST = 500, + /** 0.15% */ + LOW = 1500, + /** 0.3% */ + MEDIUM = 3000, + /** 1% */ + HIGH = 10000, +} + +const WagmiTickSpacing: Record = { + 500: 10, + 1500: 0, + 3000: 60, + 10_000: 200, +} + export class WagmiProvider extends UniswapV3BaseProvider { + override FEE = WagmiFeeAmount + override TICK_SPACINGS = WagmiTickSpacing constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.ETHEREUM]: '0xB9a14EE1cd3417f3AcC988F61650895151abde24', @@ -32,6 +52,16 @@ export class WagmiProvider extends UniswapV3BaseProvider { [ChainId.METIS]: '0x428065998a96F82bf66A0A427A157429A6Fdd649', } as const super(chainId, web3Client, factory, initCodeHash, tickLens) + + // ticks for Kava and Metis are different than other supported chains + if (chainId === ChainId.KAVA || chainId === ChainId.METIS) { + this.TICK_SPACINGS = { + 500: 10, + 1500: 30, + 3000: 60, + 10_000: 200, + } + } } getType(): LiquidityProviders { return LiquidityProviders.Wagmi diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index 89f04f5a0e..90ec2c9a04 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -374,17 +374,26 @@ async function runTest() { dataFetcher.stopDataFetching() + // univ3 based dexes should all have correct fees/ticks + const invalidDexes = [] for (const dex of dataFetcher.providers) { const dexName = dex.getType() // if the current provider is univ3 type, ensure its fees and ticks if (UniV3LiquidityProviders.some((v) => v === dexName)) { const res = await (dex as UniswapV3BaseProvider).ensureFeeAndTicks() - assert.ok(res, `invalid fees/ticks for ${dexName} at ${chName}`) + if (!res) invalidDexes.push(dexName) } } + assert.ok( + !!invalidDexes.length, + `invalid fees/ticks at ${chName} for: ${invalidDexes.join(', ')}`, + ) // should have found route - assert.ok(foundRouteReports.some((v) => v)) + assert.ok( + foundRouteReports.some((v) => v), + 'did not find any valid route', + ) // should not have any missing dex const { hasMissingDex, missingDexNames } = From 6bad66f70a1ef998c77e00325f3a9dac7855285a Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 21:02:10 +0000 Subject: [PATCH 07/10] fix --- .../src/router/liquidity-providers/LiquidityProvider.ts | 6 +++--- protocols/route-processor/test/DataFetcher.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts index dc394543b6..ee0bee8d28 100644 --- a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts +++ b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts @@ -155,9 +155,9 @@ export const UniV2LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.Camelot, LiquidityProviders.Enosys, LiquidityProviders.BlazeSwap, -] as const +] -export const UniV3LiquidityProviders = [ +export const UniV3LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.SushiSwapV3, LiquidityProviders.UniswapV3, LiquidityProviders.DovishV3, @@ -167,4 +167,4 @@ export const UniV3LiquidityProviders = [ LiquidityProviders.SpookySwapV3, LiquidityProviders.PancakeSwapV3, LiquidityProviders.Wagmi, -] as const +] diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index 90ec2c9a04..b46364a1ed 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -385,7 +385,7 @@ async function runTest() { } } assert.ok( - !!invalidDexes.length, + invalidDexes.length === 0, `invalid fees/ticks at ${chName} for: ${invalidDexes.join(', ')}`, ) From e1471c0a6a0a65ae6cccd99aab83e58649282bd4 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 21:08:45 +0000 Subject: [PATCH 08/10] Update viem.ts --- packages/sushi/src/config/viem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sushi/src/config/viem.ts b/packages/sushi/src/config/viem.ts index 62fad389e5..fbf29f3516 100644 --- a/packages/sushi/src/config/viem.ts +++ b/packages/sushi/src/config/viem.ts @@ -518,7 +518,7 @@ export const publicTransports = { [ChainId.GNOSIS]: http(gnosis.rpcUrls.default.http[0]), [ChainId.HARMONY]: http(harmonyOne.rpcUrls.default.http[0]), [ChainId.KAVA]: http(kava.rpcUrls.default.http[0]), - [ChainId.METIS]: http('https://metis-mainnet.public.blastapi.io'), + [ChainId.METIS]: http(metis.rpcUrls.default.http[0]), [ChainId.MOONBEAM]: http(moonbeam.rpcUrls.default.http[0]), [ChainId.MOONRIVER]: http(moonriver.rpcUrls.default.http[0]), [ChainId.OPTIMISM]: http(optimism.rpcUrls.default.http[0]), From 06ab3cbd3776a2bb5ca683e06ceddf46a2e5bd2d Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 23:00:35 +0000 Subject: [PATCH 09/10] standalone CI test for each chain --- .github/workflows/rain-ci.yml | 63 +++++++++++++++++-- .../route-processor/test/DataFetcher.test.ts | 9 ++- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rain-ci.yml b/.github/workflows/rain-ci.yml index f55884d649..35042c5212 100644 --- a/.github/workflows/rain-ci.yml +++ b/.github/workflows/rain-ci.yml @@ -2,7 +2,7 @@ name: Rain CI on: [push] jobs: - install-build-lint-test: + router-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -21,8 +21,61 @@ jobs: - name: Check ./packages/sushi Types run: nix develop -c pnpm exec turbo run check --filter=./packages/sushi - + - name: Test ./packages/sushi - run: | - nix develop -c pnpm exec turbo run test --filter=./packages/sushi - nix develop -c pnpm exec turbo run test --filter=./protocols/route-processor \ No newline at end of file + run: nix develop -c pnpm exec turbo run test --filter=./packages/sushi + + datafetcher-test: + strategy: + fail-fast: false + # chains to run datafetcher on (all supported chains) + matrix: + chain: [ + ETHEREUM, + POLYGON, + FANTOM, + GNOSIS, + BSC, + ARBITRUM, + ARBITRUM_NOVA, + AVALANCHE, + HARMONY, + OKEX, + CELO, + MOONRIVER, + FUSE, + TELOS, + MOONBEAM, + OPTIMISM, + KAVA, + METIS, + BOBA, + BOBA_BNB, + BTTC, + POLYGON_ZKEVM, + THUNDERCORE, + FILECOIN, + HAQQ, + CORE, + LINEA, + BASE, + SCROLL, + ZETACHAIN, + CRONOS, + BLAST, + FLARE, + ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: DeterminateSystems/nix-installer-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Install deps + run: nix develop -c pnpm install --frozen-lockfile + + - name: Test DataFecther + run: nix develop -c pnpm exec turbo run test --filter=./protocols/route-processor + env: + CHAIN: ${{ matrix.chain }} \ No newline at end of file diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index b46364a1ed..36cc6b4519 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -119,9 +119,12 @@ const excludedChains = [ ChainId.BOBA_AVAX, ChainId.ZKSYNC_ERA, ] -const chainIds = Object.values(ChainId).filter((v) => - excludedChains.every((e) => v !== e), -) +const chainIds = Object.values(ChainId).filter((v) => { + if (excludedChains.every((e) => v !== e) && process?.env?.CHAIN) { + return v === ChainId[process.env.CHAIN as keyof typeof ChainId] + } + return false +}) async function runTest() { describe.only('DataFetcher Pools/Time check', async () => { From 9496f1262fb6df6c3f1064211ecdf2258d806d10 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 26 Jun 2024 23:47:00 +0000 Subject: [PATCH 10/10] update --- .../router/liquidity-providers/MonoSwapV3.ts | 20 ---------------- .../liquidity-providers/PancakeSwapV3.ts | 4 +++- .../router/liquidity-providers/ThrusterV3.ts | 23 ++----------------- .../liquidity-providers/UniswapV3Base.ts | 4 +++- .../src/router/liquidity-providers/Wagmi.ts | 18 ++++----------- 5 files changed, 12 insertions(+), 57 deletions(-) diff --git a/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts b/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts index 81400d4e24..6ebf4d9855 100644 --- a/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/MonoSwapV3.ts @@ -3,27 +3,7 @@ import { ChainId } from '../../chain/index.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' -enum MonoswapV3FeeAmount { - /** 0.01% */ - LOWEST = 100, - /** 0.05% */ - LOW = 500, - /** 0.3% */ - MEDIUM = 3000, - /** 1% */ - HIGH = 10000, -} - -const MonoswapV3TickSpacing: Record = { - 100: 0, - 500: 10, - 3000: 60, - 10_000: 200, -} - export class MonoswapV3Provider extends UniswapV3BaseProvider { - override FEE = MonoswapV3FeeAmount - override TICK_SPACINGS = MonoswapV3TickSpacing constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.BLAST]: '0x48d0F09710794313f33619c95147F34458BF7C3b', diff --git a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts index e406e22e25..8fc5620b6e 100644 --- a/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts +++ b/packages/sushi/src/router/liquidity-providers/PancakeSwapV3.ts @@ -108,7 +108,9 @@ export class PancakeSwapV3Provider extends UniswapV3BaseProvider { // fetched fee map to ticks should match correctly with hardcoded literals in the dex return results.every( - (v, i) => this.TICK_SPACINGS[feeList[i] as PancakeSwapV3FeeAmount] === v, + (v, i) => + this.TICK_SPACINGS[feeList[i] as PancakeSwapV3FeeAmount] === v || + v === 0, ) } } diff --git a/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts b/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts index 2b8fc3962e..d8c7447385 100644 --- a/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts +++ b/packages/sushi/src/router/liquidity-providers/ThrusterV3.ts @@ -5,27 +5,7 @@ import { SushiSwapV3FeeAmount } from '../../config/sushiswap-v3.js' import { LiquidityProviders } from './LiquidityProvider.js' import { UniswapV3BaseProvider } from './UniswapV3Base.js' -enum ThrusterV3FeeAmount { - /** 0.01% */ - LOWEST = 100, - /** 0.05% */ - LOW = 500, - /** 0.3% */ - MEDIUM = 3000, - /** 1% */ - HIGH = 10000, -} - -const ThrusterV3TickSpacing: Record = { - 100: 0, - 500: 10, - 3000: 60, - 10_000: 200, -} - export class ThrusterV3Provider extends UniswapV3BaseProvider { - override FEE = ThrusterV3FeeAmount - override TICK_SPACINGS = ThrusterV3TickSpacing constructor(chainId: ChainId, web3Client: PublicClient) { const factory = { [ChainId.BLAST]: '0xa08ae3d3f4dA51C22d3c041E468bdF4C61405AaB', @@ -100,7 +80,8 @@ export class ThrusterV3Provider extends UniswapV3BaseProvider { // fetched fee map to ticks should match correctly with hardcoded literals in the dex return results.every( - (v, i) => this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v, + (v, i) => + this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v || v === 0, ) } } diff --git a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts index 1090a08462..1cc8f433d7 100644 --- a/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts +++ b/packages/sushi/src/router/liquidity-providers/UniswapV3Base.ts @@ -561,8 +561,10 @@ export abstract class UniswapV3BaseProvider extends LiquidityProvider { })) as number[] // fetched fee map to ticks should match correctly with hardcoded literals in the dex + // a tick can be 0 if there is no pools deployed with that fee yet return results.every( - (v, i) => this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v, + (v, i) => + this.TICK_SPACINGS[feeList[i] as SushiSwapV3FeeAmount] === v || v === 0, ) } } diff --git a/packages/sushi/src/router/liquidity-providers/Wagmi.ts b/packages/sushi/src/router/liquidity-providers/Wagmi.ts index ab687f2d02..4c6b5f846c 100644 --- a/packages/sushi/src/router/liquidity-providers/Wagmi.ts +++ b/packages/sushi/src/router/liquidity-providers/Wagmi.ts @@ -15,10 +15,10 @@ enum WagmiFeeAmount { } const WagmiTickSpacing: Record = { - 500: 10, - 1500: 0, - 3000: 60, - 10_000: 200, + [WagmiFeeAmount.LOWEST]: 10, + [WagmiFeeAmount.LOW]: 30, + [WagmiFeeAmount.MEDIUM]: 60, + [WagmiFeeAmount.HIGH]: 200, } export class WagmiProvider extends UniswapV3BaseProvider { @@ -52,16 +52,6 @@ export class WagmiProvider extends UniswapV3BaseProvider { [ChainId.METIS]: '0x428065998a96F82bf66A0A427A157429A6Fdd649', } as const super(chainId, web3Client, factory, initCodeHash, tickLens) - - // ticks for Kava and Metis are different than other supported chains - if (chainId === ChainId.KAVA || chainId === ChainId.METIS) { - this.TICK_SPACINGS = { - 500: 10, - 1500: 30, - 3000: 60, - 10_000: 200, - } - } } getType(): LiquidityProviders { return LiquidityProviders.Wagmi