diff --git a/.env.example b/.env.example index cc27ae949..cb38bd096 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,12 @@ HTTP_PROVIDER_43114= TENDERLY_TOKEN= TENDERLY_ACCOUNT_ID= -TENDERLY_PROJECT= \ No newline at end of file +TENDERLY_PROJECT= + + +API_KEY_BEBOP_AUTH_NAME=paraswap +API_KEY_BEBOP_AUTH_TOKEN=test +API_KEY_HASHFLOW_AUTH_TOKEN=test +API_KEY_SWAAP_V2_AUTH_TOKEN=test +API_KEY_DEXALOT_AUTH_TOKEN=test +API_KEY_SMARDEX_SUBGRAPH=test diff --git a/src/config.ts b/src/config.ts index 3b905b882..fcd8cf753 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,6 +34,7 @@ type BaseConfig = { idleDaoAuthToken?: string; swaapV2AuthToken?: string; dexalotAuthToken?: string; + bebopAuthName?: string; bebopAuthToken?: string; forceRpcFallbackDexs: string[]; }; @@ -74,6 +75,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingBlocksBackToTriggerUpdate: 0, swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + bebopAuthName: process.env.API_KEY_BEBOP_AUTH_NAME || '', bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', idleDaoAuthToken: process.env.API_KEY_IDLEDAO_AUTH_TOKEN || '', hashFlowDisabledMMs: @@ -147,6 +149,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xC50F4c1E81c873B2204D7eFf7069Ffec6Fbe136D', privateHttpProvider: process.env.HTTP_PROVIDER_56, augustusV6Address: '0x6a000f20005980200259b80c5102003040001068', + bebopAuthName: process.env.API_KEY_BEBOP_AUTH_NAME || '', bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', executorsAddresses: { Executor01: '0x000010036C0190E009a000d0fc3541100A07380A', @@ -301,6 +304,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { Executor03: '0xe009F00e200A090090fC70e02d70B232000c0802', }, dexalotAuthToken: process.env.API_KEY_DEXALOT_AUTH_TOKEN || '', + bebopAuthName: process.env.API_KEY_BEBOP_AUTH_NAME || '', bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', adapterAddresses: { ArbitrumAdapter01: '0x369A2FDb910d432f0a07381a5E3d27572c876713', @@ -348,6 +352,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', uniswapV3EventLoggingSampleRate: 0, + bebopAuthName: process.env.API_KEY_BEBOP_AUTH_NAME || '', bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 5, @@ -431,6 +436,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xeDF6D2a16e8081F777eB623EeB4411466556aF3d', privateHttpProvider: process.env.HTTP_PROVIDER_8453, dexalotAuthToken: process.env.API_KEY_DEXALOT_AUTH_TOKEN || '', + bebopAuthName: process.env.API_KEY_BEBOP_AUTH_NAME || '', bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', @@ -528,6 +534,7 @@ export function generateConfig(network: number): Config { idleDaoAuthToken: baseConfig.idleDaoAuthToken, swaapV2AuthToken: baseConfig.swaapV2AuthToken, dexalotAuthToken: baseConfig.dexalotAuthToken, + bebopAuthName: baseConfig.bebopAuthName, bebopAuthToken: baseConfig.bebopAuthToken, hashFlowDisabledMMs: baseConfig.hashFlowDisabledMMs, forceRpcFallbackDexs: baseConfig.forceRpcFallbackDexs, diff --git a/src/dex/bebop/bebop-e2e.test.ts b/src/dex/bebop/bebop-e2e.test.ts index e56cf7648..7cf42634d 100644 --- a/src/dex/bebop/bebop-e2e.test.ts +++ b/src/dex/bebop/bebop-e2e.test.ts @@ -50,6 +50,10 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + 100, ); }); it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { @@ -63,6 +67,10 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + 100, ); }); it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { @@ -76,6 +84,10 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + 100, ); }); }); diff --git a/src/dex/bebop/bebop.ts b/src/dex/bebop/bebop.ts index 463bfda0f..88e170c58 100644 --- a/src/dex/bebop/bebop.ts +++ b/src/dex/bebop/bebop.ts @@ -35,7 +35,6 @@ import { Interface } from 'ethers/lib/utils'; import { RateFetcher } from './rate-fetcher'; import { BEBOP_API_URL, - BEBOP_AUTH_NAME, BEBOP_ERRORS_CACHE_KEY, BEBOP_GAS_COST, BEBOP_INIT_TIMEOUT_MS, @@ -55,7 +54,7 @@ import { } from './constants'; import BigNumber from 'bignumber.js'; import { getBigNumberPow } from '../../bignumber-constants'; -import { utils } from 'ethers'; +import { ethers, utils } from 'ethers'; import qs from 'qs'; import { isEqual } from 'lodash'; @@ -76,6 +75,7 @@ export class Bebop extends SimpleExchange implements IDex { private tokensAddrCacheKey: string; private bebopAuthToken: string; + private bebopAuthName: string; logger: Logger; @@ -92,9 +92,19 @@ export class Bebop extends SimpleExchange implements IDex { this.pricesCacheKey = `prices`; this.tokensAddrCacheKey = `tokens_addr`; const token = this.dexHelper.config.data.bebopAuthToken; - if (!token) { - throw new Error('Bebop auth token is not set'); - } + const name = this.dexHelper.config.data.bebopAuthName; + + assert( + token !== undefined, + 'Bebop auth token is not specified with env variable', + ); + + assert( + name !== undefined, + 'Bebop auth name is not specified with env variable', + ); + + this.bebopAuthName = name; this.bebopAuthToken = token; this.rateFetcher = new RateFetcher( @@ -119,7 +129,7 @@ export class Bebop extends SimpleExchange implements IDex { BEBOP_WS_API_URL + `/pmm/${BebopConfig['Bebop'][network].chainName}/v3/pricing?format=protobuf`, headers: { - name: BEBOP_AUTH_NAME, + name: this.bebopAuthName, authorization: this.bebopAuthToken, }, }, @@ -650,12 +660,28 @@ export class Bebop extends SimpleExchange implements IDex { srcAmount, // modify filledTakerAmount to make insertFromAmount work ]); + const fromAmount = ethers.utils.defaultAbiCoder.encode( + ['uint256'], + [srcAmount], + ); + + const filledTakerAmountIndex = exchangeData + .replace('0x', '') + .lastIndexOf(fromAmount.replace('0x', '')); + + const filledTakerAmountPos = + (filledTakerAmountIndex !== -1 + ? filledTakerAmountIndex + : exchangeData.length) / 2; + return { exchangeData: exchangeData, needWrapNative: this.needWrapNative, dexFuncHasRecipient: true, targetExchange: this.settlementAddress, returnAmountPos: undefined, + sendEthButSupportsInsertFromAmount: true, + insertFromAmountPos: filledTakerAmountPos, }; } else { throw new Error('Not supported method'); @@ -681,7 +707,7 @@ export class Bebop extends SimpleExchange implements IDex { receiver_address: utils.getAddress(options.recipient), gasless: false, skip_validation: true, - source: BEBOP_AUTH_NAME, + source: this.bebopAuthName, }; try { diff --git a/src/dex/bebop/constants.ts b/src/dex/bebop/constants.ts index 847c67dba..561884a0f 100644 --- a/src/dex/bebop/constants.ts +++ b/src/dex/bebop/constants.ts @@ -5,7 +5,6 @@ export const BEBOP_TOKENS_POLLING_INTERVAL_MS = 30 * 1000; export const BEBOP_API_URL = 'https://api.bebop.xyz'; export const BEBOP_WS_API_URL = 'wss://api.bebop.xyz'; export const BEBOP_GAS_COST = 120_000; -export const BEBOP_AUTH_NAME = 'paraswap'; export const BEBOP_QUOTE_TIMEOUT_MS = 3000; export const BEBOP_ERRORS_CACHE_KEY = 'errors'; export const BEBOP_RESTRICTED_CACHE_KEY = 'restricted'; diff --git a/src/dex/cables/cables.ts b/src/dex/cables/cables.ts index f32acb295..4b4cea9d9 100644 --- a/src/dex/cables/cables.ts +++ b/src/dex/cables/cables.ts @@ -323,6 +323,7 @@ export class Cables extends SimpleExchange implements IDex { targetExchange: this.mainnetRFQAddress, returnAmountPos: undefined, insertFromAmountPos: filledAmountPos, + sendEthButSupportsInsertFromAmount: true, }; } diff --git a/src/dex/fluid-dex/config.ts b/src/dex/fluid-dex/config.ts index 48b14862e..980c07325 100644 --- a/src/dex/fluid-dex/config.ts +++ b/src/dex/fluid-dex/config.ts @@ -11,6 +11,13 @@ export const FluidDexConfig: DexConfigMap = { dexFactory: '0x91716C4EDA1Fb55e84Bf8b4c7085f84285c19085', }, }, + [Network.ARBITRUM]: { + commonAddresses: { + liquidityProxy: '0x52Aa899454998Be5b000Ad077a46Bbe360F4e497', + resolver: '0xb8f526718FF58758E256D9aD86bC194a9ff5986D', + dexFactory: '0x91716C4EDA1Fb55e84Bf8b4c7085f84285c19085', + }, + }, }, }; diff --git a/src/dex/fluid-dex/fluid-dex-e2e.test.ts b/src/dex/fluid-dex/fluid-dex-e2e.test.ts index 333aed44d..41ca4f4b5 100644 --- a/src/dex/fluid-dex/fluid-dex-e2e.test.ts +++ b/src/dex/fluid-dex/fluid-dex-e2e.test.ts @@ -169,6 +169,44 @@ describe('FluidDex E2E', () => { ); }); }); + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + + describe('ETH -> wstETH', () => { + const tokenASymbol: string = 'wstETH'; + const tokenBSymbol: string = 'ETH'; + + const tokenAAmount: string = '1000000000000000'; + const tokenBAmount: string = '1000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + + describe('ETH -> weETH', () => { + const tokenBSymbol: string = 'ETH'; + const tokenASymbol: string = 'weETH'; + + const tokenAAmount: string = '1000000000000000'; + const tokenBAmount: string = '1000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + }); }); function NewColReservesOne(): CollateralReserves { diff --git a/src/dex/solidly/config.ts b/src/dex/solidly/config.ts index 574499f00..e86c34fd0 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -84,6 +84,7 @@ export const SolidlyConfig: DexConfigMap = { // There is no subgraph for Aerodrome factoryAddress: '0x420DD381b31aEf6683db6B902084cB0FFECe40Da', router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + subgraphURL: '7uEwiKmfbRQqV8Ec9nvdKrMFVFQv5qaM271gdBvHtywj', initCode: '0x1a8f01f7eab324003d9388f229ea17991eee9c9d14586f429799f3656790eba0', poolGasCost: 180 * 1000, diff --git a/src/dex/solidly/solidly.ts b/src/dex/solidly/solidly.ts index 369b2b21d..2721bb204 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -445,6 +445,7 @@ export class Solidly extends UniswapV2 { if (!this.subgraphURL) return []; let stableFieldKey = ''; + let skipReserveCheck = false; if (this.dexKey.toLowerCase() === 'solidly') { stableFieldKey = 'stable'; @@ -452,8 +453,15 @@ export class Solidly extends UniswapV2 { stableFieldKey = 'isStable'; } + // aerodrome subgraph has broken reserve and other volume fields with all 0s + if (this.dexKey.toLowerCase() === 'aerodrome') { + skipReserveCheck = true; + } + const query = `query ($token: Bytes!, $count: Int) { - pools0: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token0: $token, reserve0_gt: 1, reserve1_gt: 1}) { + pools0: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token0: $token ${ + skipReserveCheck ? '' : ', reserve0_gt: 1, reserve1_gt: 1' + }}) { id ${stableFieldKey} token0 { @@ -466,7 +474,9 @@ export class Solidly extends UniswapV2 { } reserveUSD } - pools1: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token1: $token, reserve0_gt: 1, reserve1_gt: 1}) { + pools1: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token1: $token ${ + skipReserveCheck ? '' : ', reserve0_gt: 1, reserve1_gt: 1' + }}) { id ${stableFieldKey} token0 { @@ -502,7 +512,8 @@ export class Solidly extends UniswapV2 { decimals: parseInt(pool.token1.decimals), }, ], - liquidityUSD: parseFloat(pool.reserveUSD), + liquidityUSD: + parseFloat(pool.reserveUSD) || (skipReserveCheck ? 10e5 : 0), })); const pools1 = _.map(data.pools1, pool => ({ @@ -515,7 +526,8 @@ export class Solidly extends UniswapV2 { decimals: parseInt(pool.token0.decimals), }, ], - liquidityUSD: parseFloat(pool.reserveUSD), + liquidityUSD: + parseFloat(pool.reserveUSD) || (skipReserveCheck ? 10e5 : 0), })); return _.slice( diff --git a/src/executor/Executor02BytecodeBuilder.ts b/src/executor/Executor02BytecodeBuilder.ts index 90cf76be8..120819c4e 100644 --- a/src/executor/Executor02BytecodeBuilder.ts +++ b/src/executor/Executor02BytecodeBuilder.ts @@ -7,7 +7,6 @@ import { } from '@paraswap/core'; import { DexExchangeBuildParam, - DexExchangeParam, DexExchangeParamWithBooleanNeedWrapNative, } from '../types'; import { Executors, Flag, SpecialDex } from './types'; @@ -475,6 +474,7 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< swap: OptimalSwap, swapCallData: string, flag: Flag, + isRoot = false, ) { const data = this.packVerticalBranchingData(swapCallData); @@ -486,16 +486,34 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< let destTokenPos: number; if (isEthDest) { - anyDexOnSwapNeedsWrapNative = this.anyDexOnSwapNeedsWrapNative( - priceRoute, - swap, - exchangeParams, - ); - anyDexOnSwapDoesntNeedWrapNative = this.anyDexOnSwapDoesntNeedWrapNative( - priceRoute, - swap, - exchangeParams, - ); + if (!isRoot) { + anyDexOnSwapNeedsWrapNative = this.anyDexOnSwapNeedsWrapNative( + priceRoute, + swap, + exchangeParams, + ); + anyDexOnSwapDoesntNeedWrapNative = + this.anyDexOnSwapDoesntNeedWrapNative( + priceRoute, + swap, + exchangeParams, + ); + } else { + anyDexOnSwapNeedsWrapNative = priceRoute.bestRoute.some(route => + this.anyDexOnSwapNeedsWrapNative( + priceRoute, + route.swaps[route.swaps.length - 1], + exchangeParams, + ), + ); + anyDexOnSwapDoesntNeedWrapNative = priceRoute.bestRoute.some(route => + this.anyDexOnSwapDoesntNeedWrapNative( + priceRoute, + route.swaps[route.swaps.length - 1], + exchangeParams, + ), + ); + } } if ( @@ -1313,6 +1331,7 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< needWrapEth ? Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 0 : Flag.DONT_INSERT_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP, // 8 + true, // isRoot branch ); } diff --git a/src/executor/Executor03BytecodeBuilder.ts b/src/executor/Executor03BytecodeBuilder.ts index c9331b80c..f15b63c03 100644 --- a/src/executor/Executor03BytecodeBuilder.ts +++ b/src/executor/Executor03BytecodeBuilder.ts @@ -15,7 +15,6 @@ import { ExecutorBytecodeBuilder, SingleSwapCallDataParams, } from './ExecutorBytecodeBuilder'; -import { assert } from 'ts-essentials'; const { utils: { hexlify, hexDataLength, hexConcat, hexZeroPad, solidityPack }, @@ -330,6 +329,7 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< let fromAmountPos = 0; let toAmountPos = 0; + if (insertAmount) { if (exchangeParam.insertFromAmountPos) { fromAmountPos = exchangeParam.insertFromAmountPos; diff --git a/src/executor/executor02-bytecode-builder-e2e.test.ts b/src/executor/executor02-bytecode-builder-e2e.test.ts index 514fc60e2..04b47edf7 100644 --- a/src/executor/executor02-bytecode-builder-e2e.test.ts +++ b/src/executor/executor02-bytecode-builder-e2e.test.ts @@ -1497,6 +1497,36 @@ describe('Executor02ByteCodeBuilder e2e tests', () => { ); }); }); + + describe('USDCe -> MATIC', () => { + const dexKeys = ['CurveV2', 'UniswapV3', 'SushiSwapV3', 'SwaapV2']; + + const tokenASymbol: string = 'USDCe'; + const tokenBSymbol: string = 'MATIC'; + const tokenAAmount: string = '1978798814'; + + const side = SwapSide.SELL; + + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + tokenAAmount, + side, + dexKeys, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + 100, + 2000, + false, + ); + }); + }); }); }); diff --git a/src/types.ts b/src/types.ts index 843cf67e8..91c855043 100644 --- a/src/types.ts +++ b/src/types.ts @@ -312,6 +312,7 @@ export type Config = { uniswapV3EventLoggingSampleRate?: number; swaapV2AuthToken?: string; dexalotAuthToken?: string; + bebopAuthName?: string; bebopAuthToken?: string; idleDaoAuthToken?: string; forceRpcFallbackDexs: string[]; diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 744baed67..58a825ebb 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -1308,6 +1308,10 @@ export const Tokens: { address: '0x5979D7b546E38E414F7E9822514be443A4800529', decimals: 18, }, + weETH: { + address: '0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe', + decimals: 18, + }, RDPX: { address: '0x32eb7902d4134bf98a28b963d26de779af92a212', decimals: 18, @@ -1957,6 +1961,7 @@ export const Holders: { SEN: '0x76d39045d856caf9bfae12ba611ca4a94449a4f1', RDPX: '0x115b818593c00da4f9d1d8f5ce7d7f88cce48bee', ARB: '0xb65edba80a3d81903ecd499c8eb9cf0e19096bd0', + weETH: '0xE957D8386d20D077F511E86672Fc7ee4b83067da', ETH: '0xfa0a32e5c33b6123122b6b68099001d9371d14e9', DAI: '0x2d070ed1321871841245d8ee5b84bd2712644322', WETH: '0x3368e17064c9ba5d6f1f93c4c678bea00cc78555', @@ -1983,7 +1988,7 @@ export const Holders: { LINK: '0x7f1fa204bb700853d36994da19f830b6ad18455c', DMT: '0x40414f138eb2ef938e6c3629897ef99d4464d4e8', PENDLE: '0x5bdf85216ec1e38d6458c870992a69e38e03f7ef', - wstETH: '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', + wstETH: '0xb3843D7BBb8530f95e5bEaBeff451fA5380510a8', EURA: '0x6dd7b830896b56812aa667bdd14b71c8b3252f8e', stEUR: '0xE588611e7A2392507879E3be80531654b85C16aA', USDA: '0xa86ff337db9107b54862d30d1a598f8be847b05e',