diff --git a/package.json b/package.json index 55739ce6e..1c5e487f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.35.2", + "version": "2.36.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/src/abi/aerodrome/aerodrome-pool-factory.json b/src/abi/aerodrome/aerodrome-pool-factory.json new file mode 100644 index 000000000..56152f57d --- /dev/null +++ b/src/abi/aerodrome/aerodrome-pool-factory.json @@ -0,0 +1,586 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "FeeInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "FeeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPool", + "type": "error" + }, + { + "inputs": [], + "name": "NotFeeManager", + "type": "error" + }, + { + "inputs": [], + "name": "NotPauser", + "type": "error" + }, + { + "inputs": [], + "name": "NotVoter", + "type": "error" + }, + { + "inputs": [], + "name": "PoolAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "SameAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroFee", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "SetCustomFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "feeManager", + "type": "address" + } + ], + "name": "SetFeeManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "SetPauseState", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pauser", + "type": "address" + } + ], + "name": "SetPauser", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "SetVoter", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_FEE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZERO_FEE_INDICATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPools", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPoolsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "customFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauser", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "setCustomFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeManager", + "type": "address" + } + ], + "name": "setFeeManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_state", + "type": "bool" + } + ], + "name": "setPauseState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauser", + "type": "address" + } + ], + "name": "setPauser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_voter", + "type": "address" + } + ], + "name": "setVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stableFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "volatileFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/api3-proxy.json b/src/abi/api3-proxy.json index 41ce3c43e..ec343f133 100644 --- a/src/abi/api3-proxy.json +++ b/src/abi/api3-proxy.json @@ -42,5 +42,12 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [], + "name": "dapiNameHash", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" } ] diff --git a/src/config.ts b/src/config.ts index bac6becaf..2273e572a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -50,9 +50,9 @@ const baseConfigs: { [network: number]: BaseConfig } = { adapterAddresses: { Adapter01: '0x9bE264469eF954c139Da4A45Cf76CbCC5e3A6A73', Adapter02: '0xFC2Ba6E830a04C25e207B8214b26d8C713F6881F', - Adapter03: '0x7c7f62e5ba00783f57b39df0530e32c195696a57', + Adapter03: '0xfb2a3de6c7B8c77b520E3da16021f3D8A4E93168', Adapter04: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', - BuyAdapter: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + BuyAdapter: '0x613876f3dE2Ec633f8054fE7a561324c1a01d9cB', }, uniswapV2ExchangeRouterAddress: '0xF9234CB08edb93c0d4a4d4c70cC3FfD070e78e07', @@ -163,8 +163,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_56`]?.split(',') || [], adapterAddresses: { BscAdapter01: '0xA31d9C571DF00e0F428B0bD24c34D103E8112222', - BscAdapter02: '0x1d2Fd92a1942A92a51198168eFCd626ed441CEC0', - BscBuyAdapter: '0x64C856fafE4C83a818514cBDfD661a3563a71B98', + BscAdapter02: '0x02f2c31ebDE63E871AD0E74c01E21c819292a59D', + BscBuyAdapter: '0x301c2813e3ceb43A448a12f21551EDBcdC37F157', }, rpcPollingMaxAllowedStateDelayInBlocks: 1, rpcPollingBlocksBackToTriggerUpdate: 1, @@ -191,8 +191,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_137`]?.split(',') || [], adapterAddresses: { PolygonAdapter01: '0xE44769f42E1e9592f86B82f206407a8f7C84b4ed', - PolygonAdapter02: '0xa05d8C3F278fC7b20b39Ea7A3035E3aD8D808c78', - PolygonBuyAdapter: '0xB11bCA7B01b425afD0743A4D77B4f593883f94C0', + PolygonAdapter02: '0x654cD2Cf97D23059B3db4FaA38BB2b1F8351211d', + PolygonBuyAdapter: '0x4426a1F87Ee7e366542c58e29c02AFa2b5878b37', }, uniswapV2ExchangeRouterAddress: '0xf3938337F7294fEf84e9B2c6D548A93F956Cc281', @@ -221,8 +221,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_43114`]?.split(',') || [], adapterAddresses: { AvalancheAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - AvalancheAdapter02: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', - AvalancheBuyAdapter: '0x7ebbDBB57d2ab59079423cf8337cf8805e225bB1', + AvalancheAdapter02: '0xFb8773AA4Fd02e54bbd352061D8Be1911FAa210a', + AvalancheBuyAdapter: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', }, uniswapV2ExchangeRouterAddress: '0x53e693c6C7FFC4446c53B205Cf513105Bf140D7b', @@ -276,9 +276,9 @@ const baseConfigs: { [network: number]: BaseConfig } = { hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_42161`]?.split(',') || [], adapterAddresses: { - ArbitrumAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - ArbitrumAdapter02: '0x3ad7f275E27AC579cA88e0b4765828242A9E8C49', - ArbitrumBuyAdapter: '0x6c33C7f6CBB4a428fe9ee31ca500a787c9f1525b', + ArbitrumAdapter01: '0xD8134ACfc9c71Ab51452b5bA23A31354F4739032', + ArbitrumAdapter02: '0xD1F70c98a78d48A93F0B4dDa49057469dc5aC126', + ArbitrumBuyAdapter: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -306,8 +306,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_10`]?.split(',') || [], adapterAddresses: { - OptimismAdapter01: '0x4669D27A649f5451e0D44C20a2b246431F1B0572', - OptimismBuyAdapter: '0xb2634B3CBc1E401AB3C2743DB44d459C5c9aA662', + OptimismAdapter01: '0x3ad7f275E27AC579cA88e0b4765828242A9E8C49', + OptimismBuyAdapter: '0xfdDD975FE4c1af20c24A3Ad2b33e8609a62DDC73', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -317,7 +317,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingBlocksBackToTriggerUpdate: 3, forceRpcFallbackDexs: [], }, - [Network.ZKEVM]: { network: Network.ZKEVM, networkName: 'Polygon zkEVM', @@ -347,6 +346,33 @@ const baseConfigs: { [network: number]: BaseConfig } = { // FIXME: Not set properly uniswapV2ExchangeRouterAddress: '', }, + [Network.BASE]: { + network: Network.BASE, + networkName: 'Base', + isTestnet: false, + nativeTokenName: 'Ether', + nativeTokenSymbol: 'ETH', + wrappedNativeTokenAddress: '0x4200000000000000000000000000000000000006', + hasEIP1559: false, + augustusAddress: '0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52', + augustusRFQAddress: '0xa003dFBA51C9e1e56C67ae445b852bdEd7aC5EEd', + tokenTransferProxyAddress: '0x93aAAe79a53759cD164340E4C8766E4Db5331cD7', + multicallV2Address: '0xeDF6D2a16e8081F777eB623EeB4411466556aF3d', + privateHttpProvider: process.env.HTTP_PROVIDER_8453, + hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + hashFlowDisabledMMs: [], + adapterAddresses: { + BaseAdapter01: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + BaseBuyAdapter: '0xB11bCA7B01b425afD0743A4D77B4f593883f94C0', + }, + uniswapV2ExchangeRouterAddress: + '0x75d199EfB540e47D27D52c62Da3E7daC2B9e834F', + uniswapV3EventLoggingSampleRate: 0, + rfqConfigs: {}, + rpcPollingMaxAllowedStateDelayInBlocks: 5, + rpcPollingBlocksBackToTriggerUpdate: 3, + forceRpcFallbackDexs: [], + }, }; // Should not be used, except by internal test code diff --git a/src/constants.ts b/src/constants.ts index 08c210b0a..3c0bf6b9a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -38,6 +38,7 @@ export enum Network { FANTOM = 250, ARBITRUM = 42161, OPTIMISM = 10, + BASE = 8453, } export const SUBGRAPH_TIMEOUT = 20 * 1000; diff --git a/src/dex-helper/dummy-dex-helper.ts b/src/dex-helper/dummy-dex-helper.ts index c805c5556..287a76d8e 100644 --- a/src/dex-helper/dummy-dex-helper.ts +++ b/src/dex-helper/dummy-dex-helper.ts @@ -41,6 +41,7 @@ class DummyCache implements ICache { } async rawget(key: string): Promise { + return this.storage[key] ? this.storage[key] : null; return null; } @@ -49,10 +50,12 @@ class DummyCache implements ICache { value: string, ttl: number, ): Promise { - return null; + this.storage[key] = value; + return 'OK'; } async rawdel(key: string): Promise { + delete this.storage[key]; return; } diff --git a/src/dex/balancer-v2/balancer-v2-e2e.test.ts b/src/dex/balancer-v2/balancer-v2-e2e.test.ts index 1f7de204b..0938b3c25 100644 --- a/src/dex/balancer-v2/balancer-v2-e2e.test.ts +++ b/src/dex/balancer-v2/balancer-v2-e2e.test.ts @@ -2,7 +2,11 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Holders, Tokens } from '../../../tests/constants-e2e'; +import { + Holders, + NativeTokenSymbols, + Tokens, +} from '../../../tests/constants-e2e'; import { ContractMethod, Network, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; @@ -10,6 +14,99 @@ import { generateConfig } from '../../config'; jest.setTimeout(50 * 1000); +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('BalancerV2 E2E', () => { describe('BalancerV2 MAINNET', () => { const dexKey = 'BalancerV2'; @@ -1195,6 +1292,28 @@ describe('BalancerV2 E2E', () => { }); }); + describe('BalancerV2 Base', () => { + const dexKey = 'BalancerV2'; + const network = Network.BASE; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'GOLD'; + + const tokenAAmount: string = '11110010'; + const tokenBAmount: string = '210000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + describe('BeetsFi OPTIMISM', () => { const dexKey = 'BeetsFi'; const network = Network.OPTIMISM; diff --git a/src/dex/balancer-v2/config.ts b/src/dex/balancer-v2/config.ts index cbb0b3a77..1f05ae1c1 100644 --- a/src/dex/balancer-v2/config.ts +++ b/src/dex/balancer-v2/config.ts @@ -23,7 +23,12 @@ export const BalancerConfig: DexConfigMap = { subgraphURL: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-avalanche-v2', vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', - } + }, + [Network.BASE]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/24660/balancer-base-v2/version/latest', + vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + }, }, BeetsFi: { [Network.FANTOM]: { @@ -71,5 +76,9 @@ export const Adapters: Record = { [Network.AVALANCHE]: { [SwapSide.SELL]: [{ name: 'AvalancheAdapter01', index: 8 }], [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 7 }], - } + }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 4 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 3 }], + }, }; diff --git a/src/dex/curve-v1/config.ts b/src/dex/curve-v1/config.ts index 979f6ecde..45c599a36 100644 --- a/src/dex/curve-v1/config.ts +++ b/src/dex/curve-v1/config.ts @@ -939,44 +939,46 @@ export const CurveV1Config: DexConfigMap = { isMetapool: false, baseToken: '0x321162Cd933E2Be498Cd2267a90534A804051b11', }, - main_gDAI_gUSDC_gUSDT: { - underlying: [ - '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI - '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC - '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT - ], - coins: [ - '0x07E6332dD090D287d3489245038daF987955DCFB', // gDAI - '0xe578C856933D8e1082740bf7661e379Aa2A30b26', // gUSDC - '0x940F41F0ec9ba1A34CF001cc03347ac092F5F6B5', // gUSDT - ], - address: '0x0fa949783947Bf6c1b171DB13AEACBB488845B3f', - name: 'main_gDAI_gUSDC_gUSDT', - type: 2, - version: 3, - isLending: true, - isMetapool: false, - baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', - }, - main_iDAI_iUSDC_iFUSDT: { - underlying: [ - '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI - '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC - '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT - ], - coins: [ - '0x04c762a5dF2Fa02FE868F25359E0C259fB811CfE', // iDAI - '0x328A7b4d538A2b3942653a9983fdA3C12c571141', // iUSDC - '0x70faC71debfD67394D1278D98A29dea79DC6E57A', // iFUSDT - ], - address: '0x4FC8D635c3cB1d0aa123859e2B2587d0FF2707b1', - name: 'main_iDAI_iUSDC_iFUSDT', - type: 2, - version: 3, - isLending: true, - isMetapool: false, - baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', - }, + // Disabled pool + // main_gDAI_gUSDC_gUSDT: { + // underlying: [ + // '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI + // '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC + // '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT + // ], + // coins: [ + // '0x07E6332dD090D287d3489245038daF987955DCFB', // gDAI + // '0xe578C856933D8e1082740bf7661e379Aa2A30b26', // gUSDC + // '0x940F41F0ec9ba1A34CF001cc03347ac092F5F6B5', // gUSDT + // ], + // address: '0x0fa949783947Bf6c1b171DB13AEACBB488845B3f', + // name: 'main_gDAI_gUSDC_gUSDT', + // type: 2, + // version: 3, + // isLending: true, + // isMetapool: false, + // baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', + // }, + // Disabled pool + // main_iDAI_iUSDC_iFUSDT: { + // underlying: [ + // '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI + // '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC + // '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT + // ], + // coins: [ + // '0x04c762a5dF2Fa02FE868F25359E0C259fB811CfE', // iDAI + // '0x328A7b4d538A2b3942653a9983fdA3C12c571141', // iUSDC + // '0x70faC71debfD67394D1278D98A29dea79DC6E57A', // iFUSDT + // ], + // address: '0x4FC8D635c3cB1d0aa123859e2B2587d0FF2707b1', + // name: 'main_iDAI_iUSDC_iFUSDT', + // type: 2, + // version: 3, + // isLending: true, + // isMetapool: false, + // baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', + // }, }, }, [Network.AVALANCHE]: { diff --git a/src/dex/generic-rfq/generic-rfq.ts b/src/dex/generic-rfq/generic-rfq.ts index 2c682d8b1..c6c1584b1 100644 --- a/src/dex/generic-rfq/generic-rfq.ts +++ b/src/dex/generic-rfq/generic-rfq.ts @@ -6,6 +6,7 @@ import { PreprocessTransactionOptions, Config, PoolLiquidity, + Address, } from '../../types'; import { Network, SwapSide } from '../../constants'; import { IDexHelper } from '../../dex-helper'; @@ -61,17 +62,19 @@ export class GenericRFQ extends ParaSwapLimitOrders { return; } + getIdentifier(srcToken: Address, destToken: Address) { + // Keep only destination token in order to prevent taping into the same market maker liquidity during same swap (double spending) + return `${this.dexKey}_${destToken}`.toLowerCase(); + } + async getPoolIdentifiers( srcToken: Token, destToken: Token, side: SwapSide, blockNumber: number, ): Promise { - const _srcToken = this.dexHelper.config.wrapETH(srcToken); const _destToken = this.dexHelper.config.wrapETH(destToken); - return [ - `${this.dexKey}_${_srcToken.address}_${_destToken.address}`.toLowerCase(), - ]; + return [this.getIdentifier(srcToken.address, _destToken.address)]; } calcOutsFromAmounts( diff --git a/src/dex/hashflow/hashflow-e2e.test.ts b/src/dex/hashflow/hashflow-e2e.test.ts index 0ccd6797a..abd15126f 100644 --- a/src/dex/hashflow/hashflow-e2e.test.ts +++ b/src/dex/hashflow/hashflow-e2e.test.ts @@ -11,6 +11,9 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +// Give time for rate fetcher to fill the cache +const sleepMs = 3000; + function testForNetwork( network: Network, dexKey: string, @@ -58,6 +61,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { @@ -71,6 +79,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); } else { @@ -85,6 +98,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { @@ -98,6 +116,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { @@ -111,6 +134,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { @@ -124,6 +152,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); } @@ -163,9 +196,9 @@ describe('Hashflow E2E', () => { const tokenASymbol: string = 'USDC'; const tokenBSymbol: string = 'DAI'; - const tokenAAmount: string = '100000000'; - const tokenBAmount: string = '100000000000000000000'; - const nativeTokenAmount = '1000000000000000000'; + const tokenAAmount: string = '1000000000'; + const tokenBAmount: string = '1000000000000000000000'; + const nativeTokenAmount = '100000000000000000000'; testForNetwork( network, diff --git a/src/dex/index.ts b/src/dex/index.ts index 7484ca450..4595ad24f 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -47,6 +47,7 @@ import { Thena } from './solidly/forks-override/thena'; import { Chronos } from './solidly/forks-override/chronos'; import { Velodrome } from './solidly/forks-override/velodrome'; import { VelodromeV2 } from './solidly/forks-override/velodromeV2'; +import { Aerodrome } from './solidly/forks-override/aerodrome'; import { SpiritSwapV2 } from './solidly/forks-override/spiritSwapV2'; import { Synthetix } from './synthetix/synthetix'; import { Cone } from './solidly/forks-override/cone'; @@ -134,6 +135,7 @@ const Dexes = [ Chronos, Velodrome, VelodromeV2, + Aerodrome, Cone, SoliSnek, Equalizer, diff --git a/src/dex/maverick-v1/config.ts b/src/dex/maverick-v1/config.ts index bd85811b8..6c4c2185f 100644 --- a/src/dex/maverick-v1/config.ts +++ b/src/dex/maverick-v1/config.ts @@ -14,6 +14,12 @@ export const MaverickV1Config: DexConfigMap = { routerAddress: '0x4a585e0f7c18e2c414221d6402652d5e0990e5f8', poolInspectorAddress: '0xaA5BF61a664109e959D69C38734d4EA7dF74e456', }, + [Network.BASE]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/42519/maverick-base/version/latest', + routerAddress: '0x32AED3Bce901DA12ca8489788F3A99fCe1056e14', + poolInspectorAddress: '0x65A3AD03Be97619284bA7AA1E3Ca05638B9d6364', + }, }, }; @@ -22,4 +28,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'Adapter04', index: 2 }], [SwapSide.BUY]: [{ name: 'BuyAdapter', index: 8 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 2 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 2 }], + }, }; diff --git a/src/dex/maverick-v1/maverick-v1-e2e.test.ts b/src/dex/maverick-v1/maverick-v1-e2e.test.ts index eebb6f57c..6840f7464 100644 --- a/src/dex/maverick-v1/maverick-v1-e2e.test.ts +++ b/src/dex/maverick-v1/maverick-v1-e2e.test.ts @@ -11,10 +11,103 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('MaverickV1 E2E', () => { const dexKey = 'MaverickV1'; - describe('MaverickV1 MAINNET', () => { + describe('MAINNET', () => { const network = Network.MAINNET; const tokens = Tokens[network]; const holders = Holders[network]; @@ -121,4 +214,25 @@ describe('MaverickV1 E2E', () => { ); }); }); + + describe('BASE', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'MAV'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1100000000000000000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/maverick-v1/maverick-v1.ts b/src/dex/maverick-v1/maverick-v1.ts index 1dd608b32..b8d6b0779 100644 --- a/src/dex/maverick-v1/maverick-v1.ts +++ b/src/dex/maverick-v1/maverick-v1.ts @@ -78,6 +78,7 @@ export class MaverickV1 async setupEventPools(blockNumber: number) { const pools = await this.fetchAllSubgraphPools(); + await Promise.all( pools.map(async (pool: any) => { const eventPool = new MaverickV1EventPool( @@ -181,8 +182,17 @@ export class MaverickV1 getCalldataGasCost( poolPrices: PoolPrices, ): number | number[] { - // TODO: update if there is any payload in getAdapterParam - return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + const gasCost = CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + + const arr = new Array(poolPrices.prices.length); + poolPrices.prices.forEach((p, index) => { + if (p == 0n) { + arr[index] = 0; + } else { + arr[index] = gasCost; + } + }); + return arr; } // Returns pool prices for amounts. diff --git a/src/dex/pancakeswap-v3/config.ts b/src/dex/pancakeswap-v3/config.ts index 43636ee06..00ae8a2ac 100644 --- a/src/dex/pancakeswap-v3/config.ts +++ b/src/dex/pancakeswap-v3/config.ts @@ -53,6 +53,21 @@ export const PancakeswapV3Config: DexConfigMap = { subgraphURL: 'https://api.studio.thegraph.com/query/45376/exchange-v3-arbitrum/version/latest', }, + [Network.BASE]: { + factory: '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865', + deployer: '0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9', + quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', + router: '0x1b81D678ffb9C0263b24A97847620C99d213eB14', + supportedFees: PANCAKE_SUPPORTED_FEES, + stateMulticall: '0xeBF40A40CA3D4310Bf53048F48e860656e1D7C81', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 30, + initHash: + '0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2', + subgraphURL: + 'https://api.studio.thegraph.com/query/45376/exchange-v3-base/version/latest', + }, }, }; @@ -69,4 +84,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'ArbitrumAdapter01', index: 3 }], [SwapSide.BUY]: [{ name: 'ArbitrumBuyAdapter', index: 2 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 1 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 1 }], + }, }; diff --git a/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts b/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts index 9be62cd7e..af1065c76 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts @@ -11,6 +11,99 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('PancakeswapV3 E2E', () => { const dexKey = 'PancakeswapV3'; @@ -307,4 +400,25 @@ describe('PancakeswapV3 E2E', () => { }), ); }); + + describe('PancakeswapV3 Base', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '11111000000'; + const tokenBAmount: string = '210000000000000000'; + const nativeTokenAmount = '110000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index 8b922b236..26fe455ba 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -104,6 +104,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { const evenState = this.getState(blockNumber); if (evenState) return evenState; const onChainState = await this.generateState(blockNumber); + this.logger.warn(`State is generated in RPC fallback call`); this.setState(onChainState, blockNumber); return onChainState; } @@ -283,7 +284,8 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { for (let priceFeed of priceFeeds) { const api3ServerAddressCallData = Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); - const dataFeedIdCallData = Api3FeedSubscriber.getDataFeedId(priceFeed); + const dataFeedIdCallData = + Api3FeedSubscriber.getDapiNameHashMultiCallInput(priceFeed); multiCallData.push(...[api3ServerAddressCallData, dataFeedIdCallData]); multicallSlices.push([i, i + 2]); i += 2; @@ -315,24 +317,31 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { await multiContract.methods.aggregate(multiCallData).call({}, blockNumber) ).returnData; - const api3ServerV1: { + const api3ServerV1Prep: { [address: string]: { proxy: Address; api3ServerV1: Address; - dataFeedId: string; }; } = {}; + const getFeedIdRequests: MultiCallInput[] = []; + for (let token of tokens) { - const [api3ServerAddressRes, dataFeedIdRes] = configResults.slice( + const [api3ServerAddressRes, dapiNameHashRes] = configResults.slice( ...multicallSlices.shift()!, ); const serverV1Address = Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes); - const dataFeedId = Api3FeedSubscriber.decodeDataFeedId(dataFeedIdRes); - api3ServerV1[token] = { + const dapiNameHash = + Api3FeedSubscriber.decodeDapiNameHash(dapiNameHashRes); + getFeedIdRequests.push( + Api3FeedSubscriber.getFeedIdFromDapiNameHash( + serverV1Address, + dapiNameHash, + ), + ); + api3ServerV1Prep[token] = { proxy: priceFeeds.shift(), api3ServerV1: serverV1Address, - dataFeedId, }; } @@ -355,6 +364,32 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { const vaultConfigResults = configResults.slice(...multicallSlices.shift()!); const vaultConfig = Vault.getConfig(vaultConfigResults, tokens); + const feedIdResults = ( + await multiContract.methods + .aggregate(getFeedIdRequests) + .call({}, blockNumber) + ).returnData; + + const api3ServerV1: { + [address: string]: { + proxy: Address; + api3ServerV1: Address; + dataFeedId: string; + }; + } = {}; + for (let [i, token] of tokens.entries()) { + const { proxy, api3ServerV1: api3ServerAddress } = + api3ServerV1Prep[token]; + const dataFeedId = Api3FeedSubscriber.decodeFeedIdFromDapiNameHash( + feedIdResults[i], + ); + api3ServerV1[token] = { + proxy, + api3ServerV1: api3ServerAddress, + dataFeedId, + }; + } + return { vaultAddress: dexParams.vault, readerAddress: dexParams.reader, diff --git a/src/dex/quick-perps/quick-perps-events.test.ts b/src/dex/quick-perps/quick-perps-events.test.ts index f3d4999e2..16cf510aa 100644 --- a/src/dex/quick-perps/quick-perps-events.test.ts +++ b/src/dex/quick-perps/quick-perps-events.test.ts @@ -81,7 +81,6 @@ describe('QuickPerps Event', function () { dexKey, network, dexHelper, - logger, config, ); await testEventSubscriber( diff --git a/src/dex/quick-perps/quick-perps.ts b/src/dex/quick-perps/quick-perps.ts index ac0e4e0c3..0cbefeea7 100644 --- a/src/dex/quick-perps/quick-perps.ts +++ b/src/dex/quick-perps/quick-perps.ts @@ -117,45 +117,55 @@ export class QuickPerps extends SimpleExchange implements IDex { limitPools?: string[], ): Promise> { if (side === SwapSide.BUY || !this.pool) return null; - const srcAddress = this.dexHelper.config - .wrapETH(srcToken) - .address.toLowerCase(); - const destAddress = this.dexHelper.config - .wrapETH(destToken) - .address.toLowerCase(); - if ( - srcAddress === destAddress || - !( - this.supportedTokensMap[srcAddress] && - this.supportedTokensMap[destAddress] + try { + const srcAddress = this.dexHelper.config + .wrapETH(srcToken) + .address.toLowerCase(); + const destAddress = this.dexHelper.config + .wrapETH(destToken) + .address.toLowerCase(); + if ( + srcAddress === destAddress || + !( + this.supportedTokensMap[srcAddress] && + this.supportedTokensMap[destAddress] + ) ) - ) - return null; - const srcPoolIdentifier = `${this.dexKey}_${srcAddress}`; - const destPoolIdentifier = `${this.dexKey}_${destAddress}`; - const pools = [srcPoolIdentifier, destPoolIdentifier]; - if (limitPools && pools.some(p => !limitPools.includes(p))) return null; + return null; + const srcPoolIdentifier = `${this.dexKey}_${srcAddress}`; + const destPoolIdentifier = `${this.dexKey}_${destAddress}`; + const pools = [srcPoolIdentifier, destPoolIdentifier]; + if (limitPools && pools.some(p => !limitPools.includes(p))) return null; - const unitVolume = getBigIntPow(srcToken.decimals); - const prices = await this.pool.getAmountOut( - srcAddress, - destAddress, - [unitVolume, ...amounts], - blockNumber, - ); + const unitVolume = getBigIntPow(srcToken.decimals); + const prices = await this.pool.getAmountOut( + srcAddress, + destAddress, + [unitVolume, ...amounts], + blockNumber, + ); - if (!prices) return null; + if (!prices) return null; - return [ - { - prices: prices.slice(1), - unit: prices[0], - gasCost: QuickPerpsGasCost, - exchange: this.dexKey, - data: {}, - poolAddresses: [this.params.vault], - }, - ]; + return [ + { + prices: prices.slice(1), + unit: prices[0], + gasCost: QuickPerpsGasCost, + exchange: this.dexKey, + data: {}, + poolAddresses: [this.params.vault], + }, + ]; + } catch (e) { + this.logger.error( + `Error_getPrices ${srcToken.symbol || srcToken.address}, ${ + destToken.symbol || destToken.address + }, ${side}: `, + e, + ); + return null; + } } // Returns estimated gas cost of calldata for this DEX in multiSwap diff --git a/src/dex/solidly/config.ts b/src/dex/solidly/config.ts index b18dba6d7..baedb5183 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -83,6 +83,17 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 0, }, }, + Aerodrome: { + [Network.BASE]: { + // There is no subgraph for Aerodrome + factoryAddress: '0x420DD381b31aEf6683db6B902084cB0FFECe40Da', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0x1a8f01f7eab324003d9388f229ea17991eee9c9d14586f429799f3656790eba0', + poolGasCost: 180 * 1000, + feeCode: 0, + }, + }, Cone: { [Network.BSC]: { subgraphURL: 'https://api.thegraph.com/subgraphs/name/cone-exchange/cone', @@ -157,6 +168,15 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 0, poolGasCost: 180 * 1000, }, + [Network.BASE]: { + factoryAddress: '0xed8db60acc29e14bc867a497d94ca6e3ceb5ec04', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0x7ba31a081e879b8e7f06d4e8bf5ee26b5c2680669c5701f4cdbdcde51727b275', + feeCode: 0, + feeFactor: 1e18, + poolGasCost: 180 * 1000, + }, }, }; @@ -165,7 +185,7 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 3 }], // dystopia }, [Network.FANTOM]: { - [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly + spiritSwapV2 + Equalizer + [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly, spiritSwapV2, equalizer }, [Network.OPTIMISM]: { [SwapSide.SELL]: [{ name: 'OptimismAdapter01', index: 8 }], // velodrome @@ -182,4 +202,7 @@ export const Adapters: Record = { [Network.ARBITRUM]: { [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 1 }], // chronos, ramses }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 3 }], // aerodrome, equalizer + }, }; diff --git a/src/dex/solidly/forks-override/aerodrome.ts b/src/dex/solidly/forks-override/aerodrome.ts new file mode 100644 index 000000000..505ed4c71 --- /dev/null +++ b/src/dex/solidly/forks-override/aerodrome.ts @@ -0,0 +1,53 @@ +import { VelodromeV2 } from './velodromeV2'; +import { Network, NULL_ADDRESS } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import _ from 'lodash'; +import { SolidlyConfig } from '../config'; +import { Token } from '../../../types'; +import { IDexHelper } from '../../../dex-helper'; +import AerodromeFactoryABI from '../../../abi/aerodrome/aerodrome-pool-factory.json'; + +export class Aerodrome extends VelodromeV2 { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Aerodrome'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper); + + this.factory = new dexHelper.web3Provider.eth.Contract( + AerodromeFactoryABI as any, + SolidlyConfig[dexKey][network].factoryAddress, + ); + } + + async findSolidlyPair(from: Token, to: Token, stable: boolean) { + if (from.address.toLowerCase() === to.address.toLowerCase()) return null; + const [token0, token1] = + from.address.toLowerCase() < to.address.toLowerCase() + ? [from, to] + : [to, from]; + + const typePostfix = this.poolPostfix(stable); + const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}-${typePostfix}`; + let pair = this.pairs[key]; + if (pair) return pair; + + let exchange = await this.factory.methods + // Solidly has additional boolean parameter "StablePool" + // At first we look for uniswap-like volatile pool + .getPool(token0.address, token1.address, stable) + .call(); + + if (exchange === NULL_ADDRESS) { + pair = { token0, token1, stable }; + } else { + pair = { token0, token1, exchange, stable }; + } + this.pairs[key] = pair; + return pair; + } +} diff --git a/src/dex/solidly/solidly-e2e.test.ts b/src/dex/solidly/solidly-e2e.test.ts index 5395ad36b..79bfc833a 100644 --- a/src/dex/solidly/solidly-e2e.test.ts +++ b/src/dex/solidly/solidly-e2e.test.ts @@ -1404,4 +1404,92 @@ describe('Solidly E2E', () => { ); }); }); + + describe('Base', () => { + const network = Network.BASE; + + describe('Aerodrome', () => { + const dexKey = 'Aerodrome'; + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenAAmount: string = '1111100000'; + const nativeTokenAmount = '110000000000000000'; + + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + // TODO: Add any direct swap contractMethod name if it exists + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); + }); }); diff --git a/src/dex/solidly/solidly-integration.test.ts b/src/dex/solidly/solidly-integration.test.ts index 73f50a127..73fce1d7d 100644 --- a/src/dex/solidly/solidly-integration.test.ts +++ b/src/dex/solidly/solidly-integration.test.ts @@ -1202,4 +1202,70 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Base', () => { + const network = Network.BASE; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const equalizer = new Equalizer(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDbC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'ETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); + }); }); diff --git a/src/dex/solidly/solidly.ts b/src/dex/solidly/solidly.ts index 899acd50f..13b5a0c75 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -75,6 +75,7 @@ export class Solidly extends UniswapV2 { 'Chronos', 'Ramses', 'Equalizer', + 'Aerodrome', ]), ); diff --git a/src/dex/uniswap-v3/config.ts b/src/dex/uniswap-v3/config.ts index 86d0e6163..d2bbd45f4 100644 --- a/src/dex/uniswap-v3/config.ts +++ b/src/dex/uniswap-v3/config.ts @@ -106,6 +106,19 @@ export const UniswapV3Config: DexConfigMap = { subgraphURL: 'https://api.thegraph.com/subgraphs/name/lynnshaoyu/uniswap-v3-avax', }, + [Network.BASE]: { + factory: '0x33128a8fC17869897dcE68Ed026d694621f6FDfD', + quoter: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a', + router: '0xaeE2b8d4A154e36f479dAeCe3FB3e6c3c03d396E', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x7160f736c52e1e78e92FD4eE4D73e21A7Cf4F950', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.studio.thegraph.com/query/48211/uniswap-v3-base/version/latest', + }, }, SushiSwapV3: { [Network.MAINNET]: { @@ -197,6 +210,18 @@ export const UniswapV3Config: DexConfigMap = { subgraphURL: 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-optimism', }, + [Network.BASE]: { + factory: '0xc35DADB65012eC5796536bD9864eD8773aBc74C4', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xCc0e85901f33D375FcdD9a888B05Df9616F68277', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x7160f736c52e1e78e92FD4eE4D73e21A7Cf4F950', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.studio.thegraph.com/query/32073/v3-base/v0.0.1', + }, }, ChronosV3: { [Network.ARBITRUM]: { @@ -220,7 +245,7 @@ export const UniswapV3Config: DexConfigMap = { deployer: '0xb3e423ab9cE6C03D98326A3A2a0D7D96b0829f22', quoter: '0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20', router: '0xAA23611badAFB62D37E7295A682D21960ac85A90', - supportedFees: SUPPORTED_FEES, + supportedFees: [...SUPPORTED_FEES, 50n], stateMulticall: '0x50EE4112Cab9c79812F23bE079aB3911395ACc8e', stateMultiCallAbi: RamsesV2StateMulticallABI as AbiItem[], uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', @@ -283,4 +308,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 5 }], [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 6 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 1 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 1 }], + }, }; diff --git a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts index 4d097abc7..900de38a0 100644 --- a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts @@ -403,6 +403,95 @@ describe('UniswapV3 E2E', () => { ); }); + describe('UniswapV3 Base', () => { + const network = Network.BASE; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'PRIME'; + const tokenBSymbol: string = 'WETH'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); + describe('UniswapV3 Avalanche', () => { const network = Network.AVALANCHE; const tokens = Tokens[network]; @@ -999,5 +1088,26 @@ describe('UniswapV3 E2E', () => { nativeTokenAmount, ); }); + + describe('BASE', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '110000000000000000'; + const nativeTokenAmount = '1100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); }); diff --git a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts index bc286c582..32a463c97 100644 --- a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts @@ -959,4 +959,144 @@ describe('SushiSwapV3', () => { expect(falseChecksCounter).toBeLessThan(poolPrices!.length); }); }); + + describe('Base', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.BASE; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDbC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'DAI'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.MAINNET, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactInputSingle', + blockNumber, + '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await sushiSwapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); + }); }); diff --git a/src/dex/weth/config.ts b/src/dex/weth/config.ts index 3b528ab2d..95d779e20 100644 --- a/src/dex/weth/config.ts +++ b/src/dex/weth/config.ts @@ -25,6 +25,9 @@ export const WethConfig: DexConfigMap = { [Network.ZKEVM]: { poolGasCost: WethGasCost, }, + [Network.BASE]: { + poolGasCost: WethGasCost, + }, }, Wbnb: { [Network.BSC]: { diff --git a/src/dex/woo-fi-v2/config.ts b/src/dex/woo-fi-v2/config.ts index 3e353615b..3d020f99a 100644 --- a/src/dex/woo-fi-v2/config.ts +++ b/src/dex/woo-fi-v2/config.ts @@ -64,6 +64,16 @@ export const WooFiV2Config: DexConfigMap = { decimals: 6, }, }, + [Network.BASE]: { + wooPPV2Address: '0xb130a49065178465931d4f887056328CeA5D723f', + wooOracleV2Address: '0x2Fe5E5D341cFFa606a5d9DA1B6B646a381B0f7ec', + integrationHelperAddress: '0xC4E9B633685461E7B7A807D12a246C81f96F31B8', + // USDbC + quoteToken: { + address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + decimals: 6, + }, + }, }, }; @@ -84,4 +94,7 @@ export const Adapters: Record = { [Network.AVALANCHE]: { [SwapSide.SELL]: [{ name: 'AvalancheAdapter01', index: 12 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 5 }], + }, }; diff --git a/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts b/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts index 915364c7d..4f0ba65d6 100644 --- a/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts +++ b/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts @@ -213,4 +213,68 @@ describe('WooFiV2 E2E', () => { tokenQuoteAmount, ); }); + + describe('Base', () => { + const network = Network.BASE; + + const baseATokenSymbol = 'USDbC'; + const baseBTokenSymbol = 'ETH'; + + const tokenBaseAAmount = '100000000'; + const tokenBaseBAmount = '1000000000000000000'; + + const tokens = Tokens[network]; + const holders = Holders[network]; + + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${baseATokenSymbol} -> ${baseBTokenSymbol}`, async () => { + await testE2E( + tokens[baseATokenSymbol], + tokens[baseBTokenSymbol], + holders[baseATokenSymbol], + tokenBaseAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${baseBTokenSymbol} -> ${baseATokenSymbol}`, async () => { + await testE2E( + tokens[baseBTokenSymbol], + tokens[baseATokenSymbol], + holders[baseBTokenSymbol], + tokenBaseBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); }); diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts index 588204372..c0a7b90e8 100644 --- a/src/lib/api3-feed.ts +++ b/src/lib/api3-feed.ts @@ -51,6 +51,14 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< }; } + static getDapiNameHashMultiCallInput(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dapiNameHash'), + }; + } + static getDataFeedId(proxy: Address): MultiCallInput { return { target: proxy, @@ -59,6 +67,33 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< }; } + static getFeedIdFromDapiNameHash( + api3Server: Address, + dapiNameHash: string, + ): MultiCallInput { + return { + target: api3Server, + callData: Api3FeedSubscriber.api3ServerV1Iface.encodeFunctionData( + 'dapiNameHashToDataFeedId', + [dapiNameHash], + ), + }; + } + + static decodeFeedIdFromDapiNameHash(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.api3ServerV1Iface.decodeFunctionResult( + 'dapiNameHashToDataFeedId', + multicallOutput, + )[0]; + } + + static decodeDapiNameHash(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'dapiNameHash', + multicallOutput, + )[0]; + } + static decodeDataFeedId(multicallOutput: MultiCallOutput) { return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( 'dataFeedId', diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index fc6584a09..f596301db 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -883,6 +883,41 @@ export const Tokens: { decimals: 6, }, }, + [Network.BASE]: { + PRIME: { + address: '0xfA980cEd6895AC314E7dE34Ef1bFAE90a5AdD21b', + decimals: 18, + }, + WETH: { + address: '0x4200000000000000000000000000000000000006', + decimals: 18, + }, + MAV: { + address: '0x64b88c73A5DfA78D1713fE1b4c69a22d7E0faAa7', + decimals: 18, + }, + USDC: { + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + decimals: 6, + }, + USDbC: { + address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + decimals: 6, + }, + DAI: { + address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', + decimals: 18, + }, + BAL: { + address: '0x4158734d47fc9692176b5085e0f52ee0da5d47f1', + decimals: 18, + }, + GOLD: { + address: '0xbeFD5C25A59ef2C1316c5A4944931171F30Cd3E4', + decimals: 18, + }, + ETH: { address: ETHER_ADDRESS, decimals: 18 }, + }, }; export const Holders: { @@ -1102,6 +1137,17 @@ export const Holders: { WBTC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', USDC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', }, + [Network.BASE]: { + WETH: '0x4bb6b2efe7036020ba6f02a05602546c9f25bf28', + PRIME: '0x956bcc6b56c99db382d9d97a30ba5f1402144b3e', + ETH: '0xdd9176ea3e7559d6b68b537ef555d3e89403f742', + MAV: '0x7499785aa5d1bdf0a0ac862c1ef3698d3cba6568', + USDC: '0xaac391f166f33cdaefaa4afa6616a3bea66b694d', + USDbC: '0xc9d05a1c3c8e01dcb701d6185cdc21a5bb94becb', + DAI: '0x20f03e26968b179025f65c1f4afadfd3959c8d03', + BAL: '0x854b004700885a61107b458f11ecc169a019b764', + GOLD: '0x1374c25b3710758c326ee0c70ec48b595d5ccf8c', + }, }; export const SmartTokens = Object.keys(Tokens).reduce((acc, _network) => { @@ -1126,4 +1172,5 @@ export const NativeTokenSymbols: { [network: number]: string } = { [Network.FANTOM]: 'FTM', [Network.ARBITRUM]: 'ETH', [Network.OPTIMISM]: 'ETH', + [Network.BASE]: 'ETH', }; diff --git a/tests/tenderly-simulation.ts b/tests/tenderly-simulation.ts index 1d845b5ac..ddc53c786 100644 --- a/tests/tenderly-simulation.ts +++ b/tests/tenderly-simulation.ts @@ -149,13 +149,13 @@ export class TenderlySimulation implements TransactionSimulator { return { success: true, gasUsed: data.transaction.gas_used, - tenderlyUrl: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, transaction: data.transaction, }; } else { return { success: false, - tenderlyUrl: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, error: `Simulation failed: ${data.transaction.error_info.error_message} at ${data.transaction.error_info.address}`, }; } diff --git a/tests/utils-e2e.ts b/tests/utils-e2e.ts index ca5e068df..ece7ac264 100644 --- a/tests/utils-e2e.ts +++ b/tests/utils-e2e.ts @@ -86,6 +86,7 @@ const MULTISIG: { [nid: number]: string } = { [Network.AVALANCHE]: '0x1e2ECA5e812D08D2A7F8664D69035163ff5BfEC2', [Network.OPTIMISM]: '0xf01121e808F782d7F34E857c27dA31AD1f151b39', [Network.ARBITRUM]: '0x90DfD8a6454CFE19be39EaB42ac93CD850c7f339', + [Network.BASE]: '0x6C674c8Df1aC663b822c4B6A56B4E5e889379AE0', }; class APIParaswapSDK implements IParaSwapSDK { @@ -298,6 +299,7 @@ export async function testE2E( ), ); expect(whitelistTx.success).toEqual(true); + console.log(`Successfully whitelisted ${deployedTestContractAddress}`); if (testContractType === 'router') { const setImplementationTx = await ts.simulate( @@ -622,7 +624,7 @@ export async function newTestE2E({ parseInt(priceRoute.gasCost) - parseInt(swapTx!.gasUsed) }`, ); - console.log(`Tenderly URL: ${swapTx!.tenderlyUrl}`); + console.log(`Tenderly URL: ${swapTx!.url}`); expect(swapTx!.success).toEqual(true); } } finally {