From 83cae575fadfb3cd4e930b96189b29453696d286 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 3 Nov 2021 13:12:39 -0400 Subject: [PATCH] `@0x/asset-swapper`: hack together basic sampler service integration --- packages/asset-swapper/package.json | 1 + packages/asset-swapper/src/constants.ts | 18 - .../exchange_proxy_swap_quote_consumer.ts | 115 +- .../quote_consumers/swap_quote_consumer.ts | 7 +- packages/asset-swapper/src/swap_quoter.ts | 139 +- packages/asset-swapper/src/types.ts | 14 +- .../utils/market_operation_utils/constants.ts | 76 +- .../src/utils/market_operation_utils/fills.ts | 117 +- .../src/utils/market_operation_utils/index.ts | 511 +++-- .../market_operation_utils/multihop_utils.ts | 75 +- .../utils/market_operation_utils/orders.ts | 114 +- .../src/utils/market_operation_utils/path.ts | 4 +- .../market_operation_utils/path_optimizer.ts | 6 +- .../market_operation_utils/rate_utils.ts | 29 +- .../utils/market_operation_utils/sampler.ts | 225 +-- .../sampler_operations.ts | 1725 ----------------- .../sampler_service_rpc_client.ts | 128 ++ .../src/utils/market_operation_utils/types.ts | 26 +- .../src/utils/protocol_fee_utils.ts | 3 +- .../src/utils/quote_report_generator.ts | 175 +- .../src/utils/quote_requestor.ts | 2 +- .../src/utils/quote_simulation.ts | 4 +- packages/asset-swapper/tsconfig.json | 4 +- yarn.lock | 25 + 24 files changed, 890 insertions(+), 2653 deletions(-) delete mode 100644 packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts create mode 100644 packages/asset-swapper/src/utils/market_operation_utils/sampler_service_rpc_client.ts diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index e5a5629282..fc716622df 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -80,6 +80,7 @@ "@ethersproject/contracts": "^5.0.1", "@ethersproject/providers": "^5.0.4", "@ethersproject/strings": "^5.0.10", + "@open-rpc/client-js": "^1.7.1", "axios": "^0.21.1", "axios-mock-adapter": "^1.19.0", "cream-sor": "^0.3.3", diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index 250a7a8478..6983754290 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -1,4 +1,3 @@ -import { ChainId } from '@0x/contract-addresses'; import { SignatureType } from '@0x/protocol-utils'; import { BigNumber, logUtils } from '@0x/utils'; @@ -11,12 +10,10 @@ import { RfqRequestOpts, SwapQuoteGetOutputOpts, SwapQuoteRequestOpts, - SwapQuoterOpts, } from './types'; import { DEFAULT_GET_MARKET_ORDERS_OPTS, DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID, - DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID, } from './utils/market_operation_utils/constants'; const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json'; @@ -43,20 +40,6 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0); // default 50% buffer for selecting native orders to be aggregated with other sources const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5; -const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = { - chainId: ChainId.Mainnet, - orderRefreshIntervalMs: 10000, // 10 seconds - ...DEFAULT_ORDER_PRUNER_OPTS, - samplerGasLimit: 500e6, - ethGasStationUrl: ETH_GAS_STATION_API_URL, - rfqt: { - integratorsWhitelist: [], - makerAssetOfferings: {}, - txOriginBlacklist: new Set(), - }, - tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID[ChainId.Mainnet], -}; - const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = { isFromETH: false, isToETH: false, @@ -111,7 +94,6 @@ export const constants = { ONE_AMOUNT: new BigNumber(1), ONE_SECOND_MS, ONE_MINUTE_MS, - DEFAULT_SWAP_QUOTER_OPTS, DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID, DEFAULT_SWAP_QUOTE_REQUEST_OPTS, DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS, diff --git a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts index 04601caf96..8ae1d1bd89 100644 --- a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts @@ -95,9 +95,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { private readonly _exchangeProxy: IZeroExContract; - constructor(public readonly contractAddresses: ContractAddresses, options: Partial = {}) { - const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); - assert.isNumber('chainId', chainId); + constructor(public readonly contractAddresses: ContractAddresses, options: SwapQuoteConsumerOpts) { + const { chainId } = options; this.chainId = chainId; this.contractAddresses = contractAddresses; this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER); @@ -276,62 +275,62 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { }; } - if ( - this.chainId === ChainId.Mainnet && - isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Curve, ERC20BridgeSource.Swerve]) && - // Curve VIP cannot currently support WETH buy/sell as the functionality needs to WITHDRAW or DEPOSIT - // into WETH prior/post the trade. - // ETH buy/sell is supported - ![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet]) - ) { - const fillData = slippedOrders[0].fills[0].fillData as CurveFillData; - return { - calldataHexString: this._exchangeProxy - .sellToLiquidityProvider( - isFromETH ? ETH_TOKEN_ADDRESS : sellToken, - isToETH ? ETH_TOKEN_ADDRESS : buyToken, - CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId], - NULL_ADDRESS, - sellAmount, - minBuyAmount, - encodeCurveLiquidityProviderData({ - curveAddress: fillData.pool.poolAddress, - exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector, - fromCoinIdx: new BigNumber(fillData.fromTokenIdx), - toCoinIdx: new BigNumber(fillData.toTokenIdx), - }), - ) - .getABIEncodedTransactionData(), - ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT, - toAddress: this._exchangeProxy.address, - allowanceTarget: this._exchangeProxy.address, - gasOverhead: ZERO_AMOUNT, - }; - } + // if ( + // this.chainId === ChainId.Mainnet && + // isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Curve, ERC20BridgeSource.Swerve]) && + // // Curve VIP cannot currently support WETH buy/sell as the functionality needs to WITHDRAW or DEPOSIT + // // into WETH prior/post the trade. + // // ETH buy/sell is supported + // ![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet]) + // ) { + // const fillData = slippedOrders[0].fills[0].fillData as CurveFillData; + // return { + // calldataHexString: this._exchangeProxy + // .sellToLiquidityProvider( + // isFromETH ? ETH_TOKEN_ADDRESS : sellToken, + // isToETH ? ETH_TOKEN_ADDRESS : buyToken, + // CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId], + // NULL_ADDRESS, + // sellAmount, + // minBuyAmount, + // encodeCurveLiquidityProviderData({ + // curveAddress: fillData.pool.poolAddress, + // exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector, + // fromCoinIdx: new BigNumber(fillData.fromTokenIdx), + // toCoinIdx: new BigNumber(fillData.toTokenIdx), + // }), + // ) + // .getABIEncodedTransactionData(), + // ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT, + // toAddress: this._exchangeProxy.address, + // allowanceTarget: this._exchangeProxy.address, + // gasOverhead: ZERO_AMOUNT, + // }; + // } - if ( - this.chainId === ChainId.Mainnet && - isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap]) - ) { - const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData; - return { - calldataHexString: this._exchangeProxy - .sellToLiquidityProvider( - isFromETH ? ETH_TOKEN_ADDRESS : sellToken, - isToETH ? ETH_TOKEN_ADDRESS : buyToken, - MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId], - NULL_ADDRESS, - sellAmount, - minBuyAmount, - poolEncoder.encode([fillData.poolAddress]), - ) - .getABIEncodedTransactionData(), - ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT, - toAddress: this._exchangeProxy.address, - allowanceTarget: this.contractAddresses.exchangeProxy, - gasOverhead: ZERO_AMOUNT, - }; - } + // if ( + // this.chainId === ChainId.Mainnet && + // isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap]) + // ) { + // const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData; + // return { + // calldataHexString: this._exchangeProxy + // .sellToLiquidityProvider( + // isFromETH ? ETH_TOKEN_ADDRESS : sellToken, + // isToETH ? ETH_TOKEN_ADDRESS : buyToken, + // MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId], + // NULL_ADDRESS, + // sellAmount, + // minBuyAmount, + // poolEncoder.encode([fillData.poolAddress]), + // ) + // .getABIEncodedTransactionData(), + // ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT, + // toAddress: this._exchangeProxy.address, + // allowanceTarget: this.contractAddresses.exchangeProxy, + // gasOverhead: ZERO_AMOUNT, + // }; + // } if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) { return { diff --git a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts index 4f91003407..4ee23159bb 100644 --- a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts @@ -20,13 +20,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase { private readonly _contractAddresses: ContractAddresses; private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer; - public static getSwapQuoteConsumer(options: Partial = {}): SwapQuoteConsumer { + public static getSwapQuoteConsumer(options: SwapQuoteConsumerOpts): SwapQuoteConsumer { return new SwapQuoteConsumer(options); } - constructor(options: Partial = {}) { - const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); - assert.isNumber('chainId', chainId); + constructor(options: SwapQuoteConsumerOpts) { + const { chainId } = options; this.chainId = chainId; this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId); diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 54006ba846..759763fb91 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -1,4 +1,4 @@ -import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses'; +import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses'; import { FillQuoteTransformerOrderType, LimitOrder } from '@0x/protocol-utils'; import { BigNumber, providerUtils } from '@0x/utils'; import Axios, { AxiosInstance } from 'axios'; @@ -26,9 +26,8 @@ import { } from './types'; import { assert } from './utils/assert'; import { MarketOperationUtils } from './utils/market_operation_utils'; -import { BancorService } from './utils/market_operation_utils/bancor_service'; import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants'; -import { DexOrderSampler } from './utils/market_operation_utils/sampler'; +import { SamplerClient } from './utils/market_operation_utils/sampler'; import { SourceFilters } from './utils/market_operation_utils/source_filters'; import { ERC20BridgeSource, @@ -85,20 +84,15 @@ export class SwapQuoter { * * @return An instance of SwapQuoter */ - constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial = {}) { + constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: SwapQuoterOpts) { const { chainId, expiryBufferMs, permittedOrderFeeTypes, - samplerGasLimit, rfqt, - tokenAdjacencyGraph, - liquidityProviderRegistry, - } = { ...constants.DEFAULT_SWAP_QUOTER_OPTS, ...options }; + } = options; const provider = providerUtils.standardizeOrThrow(supportedProvider); assert.isValidOrderbook('orderbook', orderbook); - assert.isNumber('chainId', chainId); - assert.isNumber('expiryBufferMs', expiryBufferMs); this.chainId = chainId; this.provider = provider; this.orderbook = orderbook; @@ -113,45 +107,11 @@ export class SwapQuoter { constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS, options.ethGasStationUrl, ); - // Allow the sampler bytecode to be overwritten using geths override functionality - const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); - // Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work - const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS; - const defaultCodeOverrides = samplerBytecode - ? { - [samplerAddress]: { code: samplerBytecode }, - } - : {}; - const samplerOverrides = _.assign( - { block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides }, - options.samplerOverrides, - ); - const fastAbi = new FastABI(ERC20BridgeSamplerContract.ABI() as MethodAbi[], { BigNumber }); - const samplerContract = new ERC20BridgeSamplerContract( - samplerAddress, - this.provider, - { - gas: samplerGasLimit, - }, - {}, - undefined, - { - encodeInput: (fnName: string, values: any) => fastAbi.encodeInput(fnName, values), - decodeOutput: (fnName: string, data: string) => fastAbi.decodeOutput(fnName, data), - }, - ); this._marketOperationUtils = new MarketOperationUtils( - new DexOrderSampler( + SamplerClient.createFromChainIdAndEndpoint( this.chainId, - samplerContract, - samplerOverrides, - undefined, // pools caches for balancer and cream - tokenAdjacencyGraph, - liquidityProviderRegistry, - this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet - ? async () => BancorService.createAsync(provider) - : async () => undefined, + options.samplerServiceUrl, ), this._contractAddresses, { @@ -243,49 +203,50 @@ export class SwapQuoter { takerAssetAmount: BigNumber, options: Partial = {}, ): Promise { - assert.isString('makerToken', makerToken); - assert.isString('takerToken', takerToken); - const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources); - - let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native) - ? [[], []] - : await Promise.all([ - this.orderbook.getOrdersAsync(makerToken, takerToken), - this.orderbook.getOrdersAsync(takerToken, makerToken), - ]); - if (!sellOrders || sellOrders.length === 0) { - sellOrders = [createDummyOrder(makerToken, takerToken)]; - } - if (!buyOrders || buyOrders.length === 0) { - buyOrders = [createDummyOrder(takerToken, makerToken)]; - } - - const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => { - const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes; - const { side } = marketSideLiquidity; - - return [ - ...dexQuotes, - nativeOrders.map(o => { - return { - input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount, - output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount, - fillData: o, - source: ERC20BridgeSource.Native, - }; - }), - ]; - }; - const [bids, asks] = await Promise.all([ - this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options), - this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options), - ]); - return { - bids: getMarketDepthSide(bids), - asks: getMarketDepthSide(asks), - makerTokenDecimals: asks.makerTokenDecimals, - takerTokenDecimals: asks.takerTokenDecimals, - }; + throw new Error(`Not implemented`); + // assert.isString('makerToken', makerToken); + // assert.isString('takerToken', takerToken); + // const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources); + // + // let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native) + // ? [[], []] + // : await Promise.all([ + // this.orderbook.getOrdersAsync(makerToken, takerToken), + // this.orderbook.getOrdersAsync(takerToken, makerToken), + // ]); + // if (!sellOrders || sellOrders.length === 0) { + // sellOrders = [createDummyOrder(makerToken, takerToken)]; + // } + // if (!buyOrders || buyOrders.length === 0) { + // buyOrders = [createDummyOrder(takerToken, makerToken)]; + // } + // + // const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => { + // const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes; + // const { side } = marketSideLiquidity; + // + // return [ + // ...dexQuotes, + // nativeOrders.map(o => { + // return { + // input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount, + // output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount, + // fillData: o, + // source: ERC20BridgeSource.Native, + // }; + // }), + // ]; + // }; + // const [bids, asks] = await Promise.all([ + // this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options), + // this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options), + // ]); + // return { + // bids: getMarketDepthSide(bids), + // asks: getMarketDepthSide(asks), + // makerTokenDecimals: asks.makerTokenDecimals, + // takerTokenDecimals: asks.takerTokenDecimals, + // }; } /** diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index d23213013a..6179309795 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -22,6 +22,9 @@ import { import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator'; import { MetricsProxy } from './utils/quote_requestor'; +export type Address = string; +export type Bytes = string; + /** * expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m). * permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for @@ -326,15 +329,16 @@ export interface SwapQuoterOpts extends OrderPrunerOpts { chainId: ChainId; orderRefreshIntervalMs: number; expiryBufferMs: number; - ethereumRpcUrl?: string; + // ethereumRpcUrl?: string; contractAddresses?: AssetSwapperContractAddresses; samplerGasLimit?: number; - multiBridgeAddress?: string; + // multiBridgeAddress?: string; ethGasStationUrl?: string; rfqt?: SwapQuoterRfqOpts; - samplerOverrides?: SamplerOverrides; - tokenAdjacencyGraph?: TokenAdjacencyGraph; - liquidityProviderRegistry?: LiquidityProviderRegistry; + // samplerOverrides?: SamplerOverrides; + // tokenAdjacencyGraph?: TokenAdjacencyGraph; + // liquidityProviderRegistry?: LiquidityProviderRegistry; + samplerServiceUrl: string; } /** diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index ea7226a1c8..26f7163150 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -1722,13 +1722,14 @@ export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId { - // TODO: Different base cost if to/from ETH. - let gas = 90e3; - const path = (fillData as UniswapV2FillData).tokenAddressPath; - if (path.length > 2) { - gas += (path.length - 2) * 60e3; // +60k for each hop. - } - return gas; + return 90e3; + // // TODO: Different base cost if to/from ETH. + // let gas = 90e3; + // const path = (fillData as UniswapV2FillData).tokenAddressPath; + // if (path.length > 2) { + // gas += (path.length - 2) * 60e3; // +60k for each hop. + // } + // return gas; }; /** @@ -1777,21 +1778,23 @@ export const DEFAULT_GAS_SCHEDULE: Required = { [ERC20BridgeSource.Cream]: () => 120e3, [ERC20BridgeSource.MStable]: () => 200e3, [ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => { - const psmFillData = fillData as MakerPsmFillData; - return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3; + return 210e3; + // const psmFillData = fillData as MakerPsmFillData; + // return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3; }, [ERC20BridgeSource.Mooniswap]: () => 130e3, [ERC20BridgeSource.Shell]: () => 170e3, [ERC20BridgeSource.Component]: () => 188e3, [ERC20BridgeSource.MultiHop]: (fillData?: FillData) => { - const firstHop = (fillData as MultiHopFillData).firstHopSource; - const secondHop = (fillData as MultiHopFillData).secondHopSource; - const firstHopGas = DEFAULT_GAS_SCHEDULE[firstHop.source](firstHop.fillData); - const secondHopGas = DEFAULT_GAS_SCHEDULE[secondHop.source](secondHop.fillData); - return new BigNumber(firstHopGas) - .plus(secondHopGas) - .plus(30e3) - .toNumber(); + return 0; + // const firstHop = (fillData as MultiHopFillData).firstHopSource; + // const secondHop = (fillData as MultiHopFillData).secondHopSource; + // const firstHopGas = DEFAULT_GAS_SCHEDULE[firstHop.source](firstHop.fillData); + // const secondHopGas = DEFAULT_GAS_SCHEDULE[secondHop.source](secondHop.fillData); + // return new BigNumber(firstHopGas) + // .plus(secondHopGas) + // .plus(30e3) + // .toNumber(); }, [ERC20BridgeSource.Dodo]: (fillData?: FillData) => { const isSellBase = (fillData as DODOFillData).isSellBase; @@ -1801,29 +1804,32 @@ export const DEFAULT_GAS_SCHEDULE: Required = { }, [ERC20BridgeSource.DodoV2]: (_fillData?: FillData) => 100e3, [ERC20BridgeSource.Bancor]: (fillData?: FillData) => { - let gas = 200e3; - const path = (fillData as BancorFillData).path; - if (path.length > 2) { - gas += (path.length - 2) * 60e3; // +60k for each hop. - } - return gas; + return 200e3; + // let gas = 200e3; + // const path = (fillData as BancorFillData).path; + // if (path.length > 2) { + // gas += (path.length - 2) * 60e3; // +60k for each hop. + // } + // return gas; }, [ERC20BridgeSource.KyberDmm]: (fillData?: FillData) => { + return 95e3; // TODO: Different base cost if to/from ETH. - let gas = 95e3; - const path = (fillData as UniswapV2FillData).tokenAddressPath; - if (path.length > 2) { - gas += (path.length - 2) * 65e3; // +65k for each hop. - } - return gas; + // let gas = 95e3; + // const path = (fillData as UniswapV2FillData).tokenAddressPath; + // if (path.length > 2) { + // gas += (path.length - 2) * 65e3; // +65k for each hop. + // } + // return gas; }, [ERC20BridgeSource.UniswapV3]: (fillData?: FillData) => { - let gas = 100e3; - const path = (fillData as UniswapV3FillData).tokenAddressPath; - if (path.length > 2) { - gas += (path.length - 2) * 32e3; // +32k for each hop. - } - return gas; + return 100e3; + // let gas = 100e3; + // const path = (fillData as UniswapV3FillData).tokenAddressPath; + // if (path.length > 2) { + // gas += (path.length - 2) * 32e3; // +32k for each hop. + // } + // return gas; }, [ERC20BridgeSource.Lido]: () => 226e3, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index 0cabbf240b..1307de44ba 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -97,61 +97,62 @@ export function nativeOrdersToFills( inputAmountPerEth: BigNumber, fees: FeeSchedule, ): Fill[] { - const sourcePathId = hexUtils.random(); - // Create a single path from all orders. - let fills: Array = []; - for (const o of orders) { - const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o; - const makerAmount = fillableMakerAmount; - const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount); - const input = side === MarketOperation.Sell ? takerAmount : makerAmount; - const output = side === MarketOperation.Sell ? makerAmount : takerAmount; - const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o); - const outputPenalty = ethToOutputAmount({ - input, - output, - inputAmountPerEth, - outputAmountPerEth, - ethAmount: fee, - }); - // targetInput can be less than the order size - // whilst the penalty is constant, it affects the adjusted output - // only up until the target has been exhausted. - // A large order and an order at the exact target should be penalized - // the same. - const clippedInput = BigNumber.min(targetInput, input); - // scale the clipped output inline with the input - const clippedOutput = clippedInput.dividedBy(input).times(output); - const adjustedOutput = - side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty); - const adjustedRate = - side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput); - // Skip orders with rates that are <= 0. - if (adjustedRate.lte(0)) { - continue; - } - fills.push({ - sourcePathId, - adjustedRate, - adjustedOutput, - input: clippedInput, - output: clippedOutput, - flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'], - index: 0, // TBD - parent: undefined, // TBD - source: ERC20BridgeSource.Native, - type, - fillData: { ...o }, - }); - } - // Sort by descending adjusted rate. - fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate)); - // Re-index fills. - for (let i = 0; i < fills.length; ++i) { - fills[i].parent = i === 0 ? undefined : fills[i - 1]; - fills[i].index = i; - } - return fills; + throw new Error(`Not implemented`); + // const sourcePathId = hexUtils.random(); + // // Create a single path from all orders. + // let fills: Array = []; + // for (const o of orders) { + // const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o; + // const makerAmount = fillableMakerAmount; + // const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount); + // const input = side === MarketOperation.Sell ? takerAmount : makerAmount; + // const output = side === MarketOperation.Sell ? makerAmount : takerAmount; + // const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o); + // const outputPenalty = ethToOutputAmount({ + // input, + // output, + // inputAmountPerEth, + // outputAmountPerEth, + // ethAmount: fee, + // }); + // // targetInput can be less than the order size + // // whilst the penalty is constant, it affects the adjusted output + // // only up until the target has been exhausted. + // // A large order and an order at the exact target should be penalized + // // the same. + // const clippedInput = BigNumber.min(targetInput, input); + // // scale the clipped output inline with the input + // const clippedOutput = clippedInput.dividedBy(input).times(output); + // const adjustedOutput = + // side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty); + // const adjustedRate = + // side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput); + // // Skip orders with rates that are <= 0. + // if (adjustedRate.lte(0)) { + // continue; + // } + // fills.push({ + // sourcePathId, + // adjustedRate, + // adjustedOutput, + // input: clippedInput, + // output: clippedOutput, + // flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'], + // index: 0, // TBD + // parent: undefined, // TBD + // source: ERC20BridgeSource.Native, + // type, + // fillData: { ...o }, + // }); + // } + // // Sort by descending adjusted rate. + // fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate)); + // // Re-index fills. + // for (let i = 0; i < fills.length; ++i) { + // fills[i].parent = i === 0 ? undefined : fills[i - 1]; + // fills[i].index = i; + // } + // return fills; } export function dexSamplesToFills( @@ -171,10 +172,10 @@ export function dexSamplesToFills( for (let i = 0; i < nonzeroSamples.length; i++) { const sample = nonzeroSamples[i]; const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1]; - const { source, fillData } = sample; + const { source, encodedFillData } = sample; const input = sample.input.minus(prevSample ? prevSample.input : 0); const output = sample.output.minus(prevSample ? prevSample.output : 0); - const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0; + const fee = fees[source] === undefined ? 0 : fees[source]!(sample.encodedFillData) || 0; let penalty = ZERO_AMOUNT; if (i === 0) { // Only the first fill in a DEX path incurs a penalty. @@ -194,7 +195,7 @@ export function dexSamplesToFills( output, adjustedOutput, source, - fillData, + encodedFillData, type: FillQuoteTransformerOrderType.Bridge, index: i, parent: i !== 0 ? fills[fills.length - 1] : undefined, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/index.ts b/packages/asset-swapper/src/utils/market_operation_utils/index.ts index d3d3b58025..ce4cc5e398 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -40,7 +40,7 @@ import { getBestTwoHopQuote } from './multihop_utils'; import { createOrdersFromTwoHopSample } from './orders'; import { Path, PathPenaltyOpts } from './path'; import { fillsToSortedPaths, findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer'; -import { DexOrderSampler, getSampleAmounts } from './sampler'; +import { Sampler } from './sampler'; import { SourceFilters } from './source_filters'; import { AggregationError, @@ -65,7 +65,6 @@ export class MarketOperationUtils { private readonly _buySources: SourceFilters; private readonly _feeSources: SourceFilters; private readonly _nativeFeeToken: string; - private readonly _nativeFeeTokenAmount: BigNumber; private static _computeQuoteReport( quoteRequestor: QuoteRequestor | undefined, @@ -73,9 +72,10 @@ export class MarketOperationUtils { optimizerResult: OptimizerResult, comparisonPrice?: BigNumber | undefined, ): QuoteReport { - const { side, quotes } = marketSideLiquidity; - const { liquidityDelivered } = optimizerResult; - return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor); + throw new Error(`Not implemented`); + // const { side, quotes } = marketSideLiquidity; + // const { liquidityDelivered } = optimizerResult; + // return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor); } private static _computePriceComparisonsReport( @@ -83,24 +83,25 @@ export class MarketOperationUtils { marketSideLiquidity: MarketSideLiquidity, comparisonPrice?: BigNumber | undefined, ): PriceComparisonsReport { - const { side, quotes } = marketSideLiquidity; - const dexSources = _.flatten(quotes.dexQuotes).map(quote => dexSampleToReportSource(quote, side)); - const multiHopSources = quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, side)); - const nativeSources = quotes.nativeOrders.map(order => - nativeOrderToReportEntry( - order.type, - order as any, - order.fillableTakerAmount, - comparisonPrice, - quoteRequestor, - ), - ); - - return { dexSources, multiHopSources, nativeSources }; + throw new Error(`Not implemented`); + // const { side, quotes } = marketSideLiquidity; + // const dexSources = _.flatten(quotes.dexQuotes).map(quote => dexSampleToReportSource(quote, side)); + // const multiHopSources = quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, side)); + // const nativeSources = quotes.nativeOrders.map(order => + // nativeOrderToReportEntry( + // order.type, + // order as any, + // order.fillableTakerAmount, + // comparisonPrice, + // quoteRequestor, + // ), + // ); + // + // return { dexSources, multiHopSources, nativeSources }; } constructor( - private readonly _sampler: DexOrderSampler, + private readonly _sampler: Sampler, private readonly contractAddresses: AssetSwapperContractAddresses, private readonly _orderDomain: OrderDomain, ) { @@ -108,7 +109,6 @@ export class MarketOperationUtils { this._sellSources = SELL_SOURCE_FILTER_BY_CHAIN_ID[_sampler.chainId]; this._feeSources = new SourceFilters(FEE_QUOTE_SOURCES_BY_CHAIN_ID[_sampler.chainId]); this._nativeFeeToken = NATIVE_FEE_TOKEN_BY_CHAIN_ID[_sampler.chainId]; - this._nativeFeeTokenAmount = NATIVE_FEE_TOKEN_AMOUNT_BY_CHAIN_ID[_sampler.chainId]; } /** @@ -125,87 +125,53 @@ export class MarketOperationUtils { ): Promise { const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; const { makerToken, takerToken } = nativeOrders[0].order; - const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase); const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); const quoteSourceFilters = this._sellSources.merge(requestFilters); const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); - // Used to determine whether the tx origin is an EOA or a contract - const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS; - - // Call the sampler contract. - const samplerPromise = this._sampler.executeAsync( - this._sampler.getTokenDecimals([makerToken, takerToken]), - // Get native order fillable amounts. - this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), - // Get ETH -> maker token price. - this._sampler.getMedianSellRate( - feeSourceFilters.sources, - makerToken, - this._nativeFeeToken, - this._nativeFeeTokenAmount, - ), - // Get ETH -> taker token price. - this._sampler.getMedianSellRate( - feeSourceFilters.sources, - takerToken, - this._nativeFeeToken, - this._nativeFeeTokenAmount, - ), - // Get sell quotes for taker -> maker. - this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), - this._sampler.getTwoHopSellQuotes( - quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], - makerToken, - takerToken, - takerAmount, - ), - this._sampler.isAddressContract(txOrigin), - ); - - // Refresh the cached pools asynchronously if required - void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken); - const [ - [ - tokenDecimals, - orderFillableTakerAmounts, - outputAmountPerEth, - inputAmountPerEth, - dexQuotes, - rawTwoHopQuotes, - isTxOriginContract, - ], - ] = await Promise.all([samplerPromise]); + tokenInfos, + [makerTokenToEthPrice, takerTokenToEthPrice], + dexQuotes, + ] = await Promise.all([ + this._sampler.getTokenInfosAsync( + [makerToken, takerToken], + ), + this._sampler.getPricesAsync( + [ + [makerToken, this._nativeFeeToken], + [takerToken, this._nativeFeeToken], + ], + feeSourceFilters.sources, + ), + this._sampler.getSellLiquidityAsync( + [makerToken, takerToken], + takerAmount, + quoteSourceFilters.sources, + ), + ]); - // Filter out any invalid two hop quotes where we couldn't find a route - const twoHopQuotes = rawTwoHopQuotes.filter( - q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, - ); + const [makerTokenInfo, takerTokenInfo] = tokenInfos; + const makerTokenDecimals = makerTokenInfo.decimals; + const takerTokenDecimals = takerTokenInfo.decimals; - const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals; - - const isRfqSupported = !!(_opts.rfqt && !isTxOriginContract); - const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({ - ...order, - ...getNativeAdjustedFillableAmountsFromTakerAmount(order, orderFillableTakerAmounts[i]), - })); + const isRfqSupported = !!_opts.rfqt; return { side: MarketOperation.Sell, inputAmount: takerAmount, inputToken: takerToken, outputToken: makerToken, - outputAmountPerEth, - inputAmountPerEth, + outputAmountPerEth: makerTokenToEthPrice, + inputAmountPerEth: takerTokenToEthPrice, quoteSourceFilters, - makerTokenDecimals: makerTokenDecimals.toNumber(), - takerTokenDecimals: takerTokenDecimals.toNumber(), + makerTokenDecimals: makerTokenDecimals, + takerTokenDecimals: takerTokenDecimals, quotes: { - nativeOrders: limitOrdersWithFillableAmounts, + nativeOrders: [], rfqtIndicativeQuotes: [], - twoHopQuotes, + // twoHopQuotes: [], dexQuotes, }, isRfqSupported, @@ -224,93 +190,94 @@ export class MarketOperationUtils { makerAmount: BigNumber, opts?: Partial, ): Promise { - const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; - const { makerToken, takerToken } = nativeOrders[0].order; - const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase); - - const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); - const quoteSourceFilters = this._buySources.merge(requestFilters); - const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); - - // Used to determine whether the tx origin is an EOA or a contract - const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS; - - // Call the sampler contract. - const samplerPromise = this._sampler.executeAsync( - this._sampler.getTokenDecimals([makerToken, takerToken]), - // Get native order fillable amounts. - this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), - // Get ETH -> makerToken token price. - this._sampler.getMedianSellRate( - feeSourceFilters.sources, - makerToken, - this._nativeFeeToken, - this._nativeFeeTokenAmount, - ), - // Get ETH -> taker token price. - this._sampler.getMedianSellRate( - feeSourceFilters.sources, - takerToken, - this._nativeFeeToken, - this._nativeFeeTokenAmount, - ), - // Get buy quotes for taker -> maker. - this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), - this._sampler.getTwoHopBuyQuotes( - quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], - makerToken, - takerToken, - makerAmount, - ), - this._sampler.isAddressContract(txOrigin), - ); - - // Refresh the cached pools asynchronously if required - void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken); - - const [ - [ - tokenDecimals, - orderFillableMakerAmounts, - ethToMakerAssetRate, - ethToTakerAssetRate, - dexQuotes, - rawTwoHopQuotes, - isTxOriginContract, - ], - ] = await Promise.all([samplerPromise]); - - // Filter out any invalid two hop quotes where we couldn't find a route - const twoHopQuotes = rawTwoHopQuotes.filter( - q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, - ); - - const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals; - const isRfqSupported = !isTxOriginContract; - - const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({ - ...order, - ...getNativeAdjustedFillableAmountsFromMakerAmount(order, orderFillableMakerAmounts[i]), - })); - - return { - side: MarketOperation.Buy, - inputAmount: makerAmount, - inputToken: makerToken, - outputToken: takerToken, - outputAmountPerEth: ethToTakerAssetRate, - inputAmountPerEth: ethToMakerAssetRate, - quoteSourceFilters, - makerTokenDecimals: makerTokenDecimals.toNumber(), - takerTokenDecimals: takerTokenDecimals.toNumber(), - quotes: { - nativeOrders: limitOrdersWithFillableAmounts, - rfqtIndicativeQuotes: [], - twoHopQuotes, - dexQuotes, - }, - isRfqSupported, - }; + throw new Error(`Not implemented`); + // const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; + // const { makerToken, takerToken } = nativeOrders[0].order; + // const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase); + // + // const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); + // const quoteSourceFilters = this._buySources.merge(requestFilters); + // const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); + // + // // Used to determine whether the tx origin is an EOA or a contract + // const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS; + // + // // Call the sampler contract. + // const samplerPromise = this._sampler.executeAsync( + // this._sampler.getTokenDecimals([makerToken, takerToken]), + // // Get native order fillable amounts. + // this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), + // // Get ETH -> makerToken token price. + // this._sampler.getMedianSellRate( + // feeSourceFilters.sources, + // makerToken, + // this._nativeFeeToken, + // this._nativeFeeTokenAmount, + // ), + // // Get ETH -> taker token price. + // this._sampler.getMedianSellRate( + // feeSourceFilters.sources, + // takerToken, + // this._nativeFeeToken, + // this._nativeFeeTokenAmount, + // ), + // // Get buy quotes for taker -> maker. + // this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), + // this._sampler.getTwoHopBuyQuotes( + // quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], + // makerToken, + // takerToken, + // makerAmount, + // ), + // this._sampler.isAddressContract(txOrigin), + // ); + // + // // Refresh the cached pools asynchronously if required + // void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken); + // + // const [ + // [ + // tokenDecimals, + // orderFillableMakerAmounts, + // ethToMakerAssetRate, + // ethToTakerAssetRate, + // dexQuotes, + // rawTwoHopQuotes, + // isTxOriginContract, + // ], + // ] = await Promise.all([samplerPromise]); + // + // // Filter out any invalid two hop quotes where we couldn't find a route + // const twoHopQuotes = rawTwoHopQuotes.filter( + // q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, + // ); + // + // const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals; + // const isRfqSupported = !isTxOriginContract; + // + // const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({ + // ...order, + // ...getNativeAdjustedFillableAmountsFromMakerAmount(order, orderFillableMakerAmounts[i]), + // })); + // + // return { + // side: MarketOperation.Buy, + // inputAmount: makerAmount, + // inputToken: makerToken, + // outputToken: takerToken, + // outputAmountPerEth: ethToTakerAssetRate, + // inputAmountPerEth: ethToMakerAssetRate, + // quoteSourceFilters, + // makerTokenDecimals: makerTokenDecimals.toNumber(), + // takerTokenDecimals: takerTokenDecimals.toNumber(), + // quotes: { + // nativeOrders: limitOrdersWithFillableAmounts, + // rfqtIndicativeQuotes: [], + // twoHopQuotes, + // dexQuotes, + // }, + // isRfqSupported, + // }; } /** @@ -329,98 +296,99 @@ export class MarketOperationUtils { makerAmounts: BigNumber[], opts: Partial & { gasPrice: BigNumber }, ): Promise> { - if (batchNativeOrders.length === 0) { - throw new Error(AggregationError.EmptyOrders); - } - const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; - - const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); - const quoteSourceFilters = this._buySources.merge(requestFilters); - - const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); - - const ops = [ - ...batchNativeOrders.map(orders => - this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy), - ), - ...batchNativeOrders.map(orders => - this._sampler.getMedianSellRate( - feeSourceFilters.sources, - orders[0].order.takerToken, - this._nativeFeeToken, - this._nativeFeeTokenAmount, - ), - ), - ...batchNativeOrders.map((orders, i) => - this._sampler.getBuyQuotes( - quoteSourceFilters.sources, - orders[0].order.makerToken, - orders[0].order.takerToken, - [makerAmounts[i]], - ), - ), - ...batchNativeOrders.map(orders => - this._sampler.getTokenDecimals([orders[0].order.makerToken, orders[0].order.takerToken]), - ), - ]; - - const executeResults = await this._sampler.executeBatchAsync(ops); - const batchOrderFillableMakerAmounts = executeResults.splice(0, batchNativeOrders.length) as BigNumber[][]; - const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[]; - const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][]; - const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][]; - const inputAmountPerEth = ZERO_AMOUNT; - - return Promise.all( - batchNativeOrders.map(async (nativeOrders, i) => { - if (nativeOrders.length === 0) { - throw new Error(AggregationError.EmptyOrders); - } - const { makerToken, takerToken } = nativeOrders[0].order; - const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i]; - const outputAmountPerEth = batchEthToTakerAssetRate[i]; - const dexQuotes = batchDexQuotes[i]; - const makerAmount = makerAmounts[i]; - try { - const optimizerResult = await this._generateOptimizedOrdersAsync( - { - side: MarketOperation.Buy, - inputToken: makerToken, - outputToken: takerToken, - inputAmount: makerAmount, - outputAmountPerEth, - inputAmountPerEth, - quoteSourceFilters, - makerTokenDecimals: batchTokenDecimals[i][0], - takerTokenDecimals: batchTokenDecimals[i][1], - quotes: { - nativeOrders: nativeOrders.map((o, k) => ({ - ...o, - ...getNativeAdjustedFillableAmountsFromMakerAmount(o, orderFillableMakerAmounts[k]), - })), - dexQuotes, - rfqtIndicativeQuotes: [], - twoHopQuotes: [], - }, - isRfqSupported: false, - }, - { - bridgeSlippage: _opts.bridgeSlippage, - maxFallbackSlippage: _opts.maxFallbackSlippage, - excludedSources: _opts.excludedSources, - feeSchedule: _opts.feeSchedule, - allowFallback: _opts.allowFallback, - gasPrice: _opts.gasPrice, - }, - ); - return optimizerResult; - } catch (e) { - // It's possible for one of the pairs to have no path - // rather than throw NO_OPTIMAL_PATH we return undefined - return undefined; - } - }), - ); + throw new Error(`Not implemented`); + // if (batchNativeOrders.length === 0) { + // throw new Error(AggregationError.EmptyOrders); + // } + // const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; + // + // const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); + // const quoteSourceFilters = this._buySources.merge(requestFilters); + // + // const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); + // + // const ops = [ + // ...batchNativeOrders.map(orders => + // this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy), + // ), + // ...batchNativeOrders.map(orders => + // this._sampler.getMedianSellRate( + // feeSourceFilters.sources, + // orders[0].order.takerToken, + // this._nativeFeeToken, + // this._nativeFeeTokenAmount, + // ), + // ), + // ...batchNativeOrders.map((orders, i) => + // this._sampler.getBuyQuotes( + // quoteSourceFilters.sources, + // orders[0].order.makerToken, + // orders[0].order.takerToken, + // [makerAmounts[i]], + // ), + // ), + // ...batchNativeOrders.map(orders => + // this._sampler.getTokenDecimals([orders[0].order.makerToken, orders[0].order.takerToken]), + // ), + // ]; + // + // const executeResults = await this._sampler.executeBatchAsync(ops); + // const batchOrderFillableMakerAmounts = executeResults.splice(0, batchNativeOrders.length) as BigNumber[][]; + // const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[]; + // const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][]; + // const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][]; + // const inputAmountPerEth = ZERO_AMOUNT; + // + // return Promise.all( + // batchNativeOrders.map(async (nativeOrders, i) => { + // if (nativeOrders.length === 0) { + // throw new Error(AggregationError.EmptyOrders); + // } + // const { makerToken, takerToken } = nativeOrders[0].order; + // const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i]; + // const outputAmountPerEth = batchEthToTakerAssetRate[i]; + // const dexQuotes = batchDexQuotes[i]; + // const makerAmount = makerAmounts[i]; + // try { + // const optimizerResult = await this._generateOptimizedOrdersAsync( + // { + // side: MarketOperation.Buy, + // inputToken: makerToken, + // outputToken: takerToken, + // inputAmount: makerAmount, + // outputAmountPerEth, + // inputAmountPerEth, + // quoteSourceFilters, + // makerTokenDecimals: batchTokenDecimals[i][0], + // takerTokenDecimals: batchTokenDecimals[i][1], + // quotes: { + // nativeOrders: nativeOrders.map((o, k) => ({ + // ...o, + // ...getNativeAdjustedFillableAmountsFromMakerAmount(o, orderFillableMakerAmounts[k]), + // })), + // dexQuotes, + // rfqtIndicativeQuotes: [], + // twoHopQuotes: [], + // }, + // isRfqSupported: false, + // }, + // { + // bridgeSlippage: _opts.bridgeSlippage, + // maxFallbackSlippage: _opts.maxFallbackSlippage, + // excludedSources: _opts.excludedSources, + // feeSchedule: _opts.feeSchedule, + // allowFallback: _opts.allowFallback, + // gasPrice: _opts.gasPrice, + // }, + // ); + // return optimizerResult; + // } catch (e) { + // // It's possible for one of the pairs to have no path + // // rather than throw NO_OPTIMAL_PATH we return undefined + // return undefined; + // } + // }), + // ); } public async _generateOptimizedOrdersAsync( @@ -515,7 +483,7 @@ export class MarketOperationUtils { const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts); return { optimizedOrders: twoHopOrders, - liquidityDelivered: bestTwoHopQuote, + // liquidityDelivered: bestTwoHopQuote, sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop], marketSideLiquidity, adjustedRate: bestTwoHopRate, @@ -536,7 +504,7 @@ export class MarketOperationUtils { return { optimizedOrders: collapsedPath.orders, - liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[], + // liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[], sourceFlags: collapsedPath.sourceFlags, marketSideLiquidity, adjustedRate: optimalPathRate, @@ -713,17 +681,6 @@ export class MarketOperationUtils { return { ...optimizerResult, quoteReport, priceComparisonsReport }; } - private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise { - void Promise.all( - Object.values(this._sampler.poolsCaches).map(async cache => { - if (cache.isFresh(takerToken, makerToken)) { - return Promise.resolve([]); - } - return cache.getFreshPoolsForPairAsync(takerToken, makerToken); - }), - ); - } - // tslint:disable-next-line: prefer-function-over-method private async _addOptionalFallbackAsync( side: MarketOperation, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/multihop_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/multihop_utils.ts index 3734d6feb1..f921020559 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/multihop_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/multihop_utils.ts @@ -38,41 +38,42 @@ export function getBestTwoHopQuote( marketSideLiquidity: Omit, feeSchedule?: FeeSchedule, exchangeProxyOverhead?: ExchangeProxyOverhead, -): { quote: DexSample | undefined; adjustedRate: BigNumber } { - const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity; - const { twoHopQuotes } = quotes; - // Ensure the expected data we require exists. In the case where all hops reverted - // or there were no sources included that allowed for multi hop, - // we can end up with empty, but not undefined, fill data - const filteredQuotes = twoHopQuotes.filter( - quote => - quote && - quote.fillData && - quote.fillData.firstHopSource && - quote.fillData.secondHopSource && - quote.output.isGreaterThan(ZERO_AMOUNT), - ); - if (filteredQuotes.length === 0) { - return { quote: undefined, adjustedRate: ZERO_AMOUNT }; - } - const best = filteredQuotes - .map(quote => - getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead), - ) - .reduce( - (prev, curr, i) => - curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev, - { - adjustedRate: getTwoHopAdjustedRate( - side, - filteredQuotes[0], - inputAmount, - outputAmountPerEth, - feeSchedule, - exchangeProxyOverhead, - ), - quote: filteredQuotes[0], - }, - ); - return best; +): { quote: DexSample | undefined; adjustedRate: BigNumber } { + throw new Error(`No implementado`); + // const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity; + // const { twoHopQuotes } = quotes; + // // Ensure the expected data we require exists. In the case where all hops reverted + // // or there were no sources included that allowed for multi hop, + // // we can end up with empty, but not undefined, fill data + // const filteredQuotes = twoHopQuotes.filter( + // quote => + // quote && + // quote.fillData && + // quote.fillData.firstHopSource && + // quote.fillData.secondHopSource && + // quote.output.isGreaterThan(ZERO_AMOUNT), + // ); + // if (filteredQuotes.length === 0) { + // return { quote: undefined, adjustedRate: ZERO_AMOUNT }; + // } + // const best = filteredQuotes + // .map(quote => + // getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead), + // ) + // .reduce( + // (prev, curr, i) => + // curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev, + // { + // adjustedRate: getTwoHopAdjustedRate( + // side, + // filteredQuotes[0], + // inputAmount, + // outputAmountPerEth, + // feeSchedule, + // exchangeProxyOverhead, + // ), + // quote: filteredQuotes[0], + // }, + // ); + // return best; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index d6818ed96f..4e97f8396b 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -48,33 +48,34 @@ export interface CreateOrderFromPathOpts { } export function createOrdersFromTwoHopSample( - sample: DexSample, + sample: DexSample, opts: CreateOrderFromPathOpts, ): OptimizedMarketOrder[] { - const [makerToken, takerToken] = getMakerTakerTokens(opts); - const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData; - const firstHopFill: CollapsedFill = { - sourcePathId: '', - source: firstHopSource.source, - type: FillQuoteTransformerOrderType.Bridge, - input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT, - output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output, - subFills: [], - fillData: firstHopSource.fillData, - }; - const secondHopFill: CollapsedFill = { - sourcePathId: '', - source: secondHopSource.source, - type: FillQuoteTransformerOrderType.Bridge, - input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input, - output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256, - subFills: [], - fillData: secondHopSource.fillData, - }; - return [ - createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side), - createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts.side), - ]; + throw new Error(`Not implemented`); + // const [makerToken, takerToken] = getMakerTakerTokens(opts); + // const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData; + // const firstHopFill: CollapsedFill = { + // sourcePathId: '', + // source: firstHopSource.source, + // type: FillQuoteTransformerOrderType.Bridge, + // input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT, + // output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output, + // subFills: [], + // fillData: firstHopSource.fillData, + // }; + // const secondHopFill: CollapsedFill = { + // sourcePathId: '', + // source: secondHopSource.source, + // type: FillQuoteTransformerOrderType.Bridge, + // input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input, + // output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256, + // subFills: [], + // fillData: secondHopSource.fillData, + // }; + // return [ + // createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side), + // createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts.side), + // ]; } export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): string { @@ -348,7 +349,7 @@ export function createBridgeOrder( takerToken, makerAmount, takerAmount, - fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill), + fillData: fill.encodedFillData, source: fill.source, sourcePathId: fill.sourcePathId, type: FillQuoteTransformerOrderType.Bridge, @@ -356,36 +357,6 @@ export function createBridgeOrder( }; } -function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData { - switch (fill.source) { - case ERC20BridgeSource.UniswapV3: { - const fd = fill.fillData as UniswapV3FillData; - return { - router: fd.router, - tokenAddressPath: fd.tokenAddressPath, - uniswapPath: getBestUniswapV3PathForInputAmount(fd, fill.input), - }; - } - default: - break; - } - return fill.fillData; -} - -function getBestUniswapV3PathForInputAmount(fillData: UniswapV3FillData, inputAmount: BigNumber): string { - if (fillData.pathAmounts.length === 0) { - throw new Error(`No Uniswap V3 paths`); - } - // Find the best path that can satisfy `inputAmount`. - // Assumes `fillData.pathAmounts` is sorted ascending. - for (const { inputAmount: pathInputAmount, uniswapPath } of fillData.pathAmounts) { - if (pathInputAmount.gte(inputAmount)) { - return uniswapPath; - } - } - return fillData.pathAmounts[fillData.pathAmounts.length - 1].uniswapPath; -} - export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] { const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken; const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken; @@ -506,19 +477,20 @@ export function createNativeOptimizedOrder( fill: NativeCollapsedFill, side: MarketOperation, ): OptimizedMarketOrderBase | OptimizedMarketOrderBase { - const fillData = fill.fillData; - const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side); - const base = { - type: fill.type, - source: ERC20BridgeSource.Native, - makerToken: fillData.order.makerToken, - takerToken: fillData.order.takerToken, - makerAmount, - takerAmount, - fills: [fill], - fillData, - }; - return fill.type === FillQuoteTransformerOrderType.Rfq - ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData } - : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData }; + throw new Error(`No implementado`); + // const fillData = fill.fillData; + // const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side); + // const base = { + // type: fill.type, + // source: ERC20BridgeSource.Native, + // makerToken: fillData.order.makerToken, + // takerToken: fillData.order.takerToken, + // makerAmount, + // takerAmount, + // fills: [fill], + // fillData, + // }; + // return fill.type === FillQuoteTransformerOrderType.Rfq + // ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData } + // : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData }; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path.ts b/packages/asset-swapper/src/utils/market_operation_utils/path.ts index 6cb7ba84fe..63cb820683 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path.ts @@ -257,7 +257,7 @@ export class Path { if (prevFill.sourcePathId === fill.sourcePathId) { prevFill.input = prevFill.input.plus(fill.input); prevFill.output = prevFill.output.plus(fill.output); - prevFill.fillData = fill.fillData; + prevFill.encodedFillData = fill.encodedFillData; prevFill.subFills.push(fill); continue; } @@ -266,7 +266,7 @@ export class Path { sourcePathId: fill.sourcePathId, source: fill.source, type: fill.type, - fillData: fill.fillData, + encodedFillData: fill.encodedFillData, input: fill.input, output: fill.output, subFills: [fill], diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts index 7b152851f6..074b020652 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts @@ -45,8 +45,8 @@ function calculateOuputFee( fees: FeeSchedule, ): BigNumber { if (isDexSample(sampleOrNativeOrder)) { - const { input, output, source, fillData } = sampleOrNativeOrder; - const fee = fees[source]?.(fillData) || 0; + const { input, output, source, encodedFillData } = sampleOrNativeOrder; + const fee = fees[source]?.(encodedFillData) || 0; const outputFee = ethToOutputAmount({ input, output, @@ -259,7 +259,7 @@ function findRoutesAndCreateOptimalPath( // NOTE: For DexSamples only let fill = createFill(current); - const routeSamples = routeSamplesAndNativeOrders as Array>; + const routeSamples = routeSamplesAndNativeOrders as Array; // Descend to approach a closer fill for fillData which may not be consistent // throughout the path (UniswapV3) and for a closer guesstimate at // gas used diff --git a/packages/asset-swapper/src/utils/market_operation_utils/rate_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/rate_utils.ts index 87f309a695..4e0d29ab8f 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/rate_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/rate_utils.ts @@ -13,25 +13,26 @@ import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, Multi */ export function getTwoHopAdjustedRate( side: MarketOperation, - twoHopQuote: DexSample, + twoHopQuote: DexSample, targetInput: BigNumber, outputAmountPerEth: BigNumber, fees: FeeSchedule = {}, exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT, ): BigNumber { - const { output, input, fillData } = twoHopQuote; - if (input.isLessThan(targetInput) || output.isZero()) { - return ZERO_AMOUNT; - } - const penalty = outputAmountPerEth.times( - exchangeProxyOverhead( - SOURCE_FLAGS.MultiHop | - SOURCE_FLAGS[fillData.firstHopSource.source] | - SOURCE_FLAGS[fillData.secondHopSource.source], - ).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)), - ); - const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); - return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput); + throw new Error(`Not implemented`); + // const { output, input, fillData } = twoHopQuote; + // if (input.isLessThan(targetInput) || output.isZero()) { + // return ZERO_AMOUNT; + // } + // const penalty = outputAmountPerEth.times( + // exchangeProxyOverhead( + // SOURCE_FLAGS.MultiHop | + // SOURCE_FLAGS[fillData.firstHopSource.source] | + // SOURCE_FLAGS[fillData.secondHopSource.source], + // ).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)), + // ); + // const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); + // return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput); } /** diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts index 7afffec2f1..0bb9704521 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts @@ -1,167 +1,86 @@ import { ChainId } from '@0x/contract-addresses'; -import { BigNumber, NULL_BYTES } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; -import { SamplerOverrides } from '../../types'; -import { ERC20BridgeSamplerContract } from '../../wrappers'; +import { Address } from '../../types'; -import { BancorService } from './bancor_service'; -import { PoolsCache } from './pools_cache'; -import { SamplerOperations } from './sampler_operations'; -import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types'; +import { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types'; +import { SamplerServiceRpcClient } from './sampler_service_rpc_client'; -/** - * Generate sample amounts up to `maxFillAmount`. - */ -export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] { - const distribution = [...Array(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i)); - const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution))); - const amounts = stepSizes.map((_s, i) => { - if (i === numSamples - 1) { - return maxFillAmount; - } - return maxFillAmount - .times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)])) - .integerValue(BigNumber.ROUND_UP); - }); - return amounts; +interface TokenInfo { + decimals: number; + address: Address; + gasCost: number; + symbol: string; } -type BatchedOperationResult = T extends BatchedOperation ? TResult : never; +export interface Sampler { + chainId: ChainId; + getTokenInfosAsync(tokens: Address[]): Promise; + getPricesAsync(paths: Address[][], sources: ERC20BridgeSource[]): Promise; + getSellLiquidityAsync(path: Address[], takerAmount: BigNumber, sources: ERC20BridgeSource[]): Promise; +} -/** - * Encapsulates interactions with the `ERC20BridgeSampler` contract. - */ -export class DexOrderSampler extends SamplerOperations { - constructor( - public readonly chainId: ChainId, - _samplerContract: ERC20BridgeSamplerContract, - private readonly _samplerOverrides?: SamplerOverrides, - poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache }, - tokenAdjacencyGraph?: TokenAdjacencyGraph, - liquidityProviderRegistry?: LiquidityProviderRegistry, - bancorServiceFn: () => Promise = async () => undefined, - ) { - super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn); +export class SamplerClient implements Sampler { + static createFromChainIdAndEndpoint(chainId: ChainId, endpoint: string): SamplerClient { + return new SamplerClient(chainId, new SamplerServiceRpcClient(endpoint)); } - /* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */ - - // prettier-ignore - public async executeAsync< - T1 - >(...ops: [T1]): Promise<[ - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2 - >(...ops: [T1, T2]): Promise<[ - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3 - >(...ops: [T1, T2, T3]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3, T4 - >(...ops: [T1, T2, T3, T4]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3, T4, T5 - >(...ops: [T1, T2, T3, T4, T5]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3, T4, T5, T6 - >(...ops: [T1, T2, T3, T4, T5, T6]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3, T4, T5, T6, T7 - >(...ops: [T1, T2, T3, T4, T5, T6, T7]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - // prettier-ignore - public async executeAsync< - T1, T2, T3, T4, T5, T6, T7, T8 - >(...ops: [T1, T2, T3, T4, T5, T6, T7, T8]): Promise<[ - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult, - BatchedOperationResult - ]>; - - /** - * Run a series of operations from `DexOrderSampler.ops` in a single transaction. - */ - public async executeAsync(...ops: any[]): Promise { - return this.executeBatchAsync(ops); + static async createFromEndpointAsync(endpoint: string): Promise { + const service = new SamplerServiceRpcClient(endpoint); + const chainId = await service.getChainIdAsync(); + return new SamplerClient( + chainId, + service, + ); } - /** - * Run a series of operations from `DexOrderSampler.ops` in a single transaction. - * Takes an arbitrary length array, but is not typesafe. - */ - public async executeBatchAsync>>(ops: T): Promise { - const callDatas = ops.map(o => o.encodeCall()); - const { overrides, block } = this._samplerOverrides - ? this._samplerOverrides - : { overrides: undefined, block: undefined }; + private constructor( + private readonly _chainId: number, + private readonly _service: SamplerServiceRpcClient, + ) {} - // All operations are NOOPs - if (callDatas.every(cd => cd === NULL_BYTES)) { - return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES)); - } - // Execute all non-empty calldatas. - const rawCallResults = await this._samplerContract - .batchCall(callDatas.filter(cd => cd !== NULL_BYTES)) - .callAsync({ overrides }, block); - // Return the parsed results. - let rawCallResultsIdx = 0; - return callDatas.map((callData, i) => { - // tslint:disable-next-line:boolean-naming - const { data, success } = - callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : { success: true, data: NULL_BYTES }; - return success ? ops[i].handleCallResults(data) : ops[i].handleRevert(data); - }); + public get chainId(): ChainId { + return this._chainId; + } + + public async getPricesAsync( + paths: Address[][], + sources: ERC20BridgeSource[], + ): Promise { + return this._service.getPricesAsync(paths.map(p => ({ + tokenPath: p, + demand: true, + sources, + }))); + } + + public async getTokenInfosAsync(tokens: Address[]): Promise { + return this._service.getTokensAsync(tokens); + } + + public async getSellLiquidityAsync( + path: Address[], + takerAmount: BigNumber, + sources: ERC20BridgeSource[], + ): Promise { + const liquidity = await this._service.getSellLiquidityAsync( + sources.map(s => ({ + tokenPath: path, + inputAmount: takerAmount, + source: s, + demand: true, + })), + ); + return liquidity.map( + liq => liq.liquidityCurves.map( + pts => + pts.map(pt => ({ + input: pt.sellAmount, + output: pt.buyAmount, + encodedFillData: pt.encodedFillData, + gasCost: pt.gasCost, + source: liq.source, + }) as DexSample), + )).flat(1); } } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts deleted file mode 100644 index a35183a0db..0000000000 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ /dev/null @@ -1,1725 +0,0 @@ -import { ChainId } from '@0x/contract-addresses'; -import { LimitOrderFields } from '@0x/protocol-utils'; -import { BigNumber, logUtils } from '@0x/utils'; -import * as _ from 'lodash'; - -import { SamplerCallResult, SignedNativeOrder } from '../../types'; -import { ERC20BridgeSamplerContract } from '../../wrappers'; - -import { BancorService } from './bancor_service'; -import { - getCurveLikeInfosForPair, - getDodoV2Offsets, - getKyberOffsets, - getShellLikeInfosForPair, - isAllowedKyberReserveId, - isBadTokenForSource, - isValidAddress, - uniswapV2LikeRouterAddress, -} from './bridge_source_utils'; -import { - BALANCER_V2_VAULT_ADDRESS_BY_CHAIN, - BANCOR_REGISTRY_BY_CHAIN_ID, - DODOV1_CONFIG_BY_CHAIN_ID, - DODOV2_FACTORIES_BY_CHAIN_ID, - KYBER_CONFIG_BY_CHAIN_ID, - KYBER_DMM_ROUTER_BY_CHAIN_ID, - LIDO_INFO_BY_CHAIN, - LINKSWAP_ROUTER_BY_CHAIN_ID, - LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID, - MAINNET_TOKENS, - MAKER_PSM_INFO_BY_CHAIN_ID, - MAX_UINT256, - MOONISWAP_REGISTRIES_BY_CHAIN_ID, - NATIVE_FEE_TOKEN_BY_CHAIN_ID, - NULL_ADDRESS, - NULL_BYTES, - SELL_SOURCE_FILTER_BY_CHAIN_ID, - UNISWAPV1_ROUTER_BY_CHAIN_ID, - UNISWAPV3_CONFIG_BY_CHAIN_ID, - ZERO_AMOUNT, -} from './constants'; -import { getLiquidityProvidersForPair } from './liquidity_provider_utils'; -import { getIntermediateTokens } from './multihop_utils'; -import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache'; -import { SamplerContractOperation } from './sampler_contract_operation'; -import { SourceFilters } from './source_filters'; -import { - BalancerFillData, - BalancerV2FillData, - BalancerV2PoolInfo, - BancorFillData, - BatchedOperation, - CurveFillData, - CurveInfo, - DexSample, - DODOFillData, - ERC20BridgeSource, - GenericRouterFillData, - HopInfo, - KyberDmmFillData, - KyberFillData, - KyberSamplerOpts, - LidoFillData, - LidoInfo, - LiquidityProviderFillData, - LiquidityProviderRegistry, - MakerPsmFillData, - MooniswapFillData, - MultiHopFillData, - PsmInfo, - ShellFillData, - SourceQuoteOperation, - SourcesWithPoolsCache, - TokenAdjacencyGraph, - UniswapV2FillData, - UniswapV3FillData, -} from './types'; - -/** - * Source filters for `getTwoHopBuyQuotes()` and `getTwoHopSellQuotes()`. - */ -export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([ - ERC20BridgeSource.MultiHop, - ERC20BridgeSource.Native, -]); -/** - * Source filters for `getSellQuotes()` and `getBuyQuotes()`. - */ -export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]); - -// tslint:disable:no-inferred-empty-object-type no-unbound-method - -/** - * Composable operations that can be batched in a single transaction, - * for use with `DexOrderSampler.executeAsync()`. - */ -export class SamplerOperations { - public readonly liquidityProviderRegistry: LiquidityProviderRegistry; - public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache }; - protected _bancorService?: BancorService; - public static constant(result: T): BatchedOperation { - return { - encodeCall: () => '0x', - handleCallResults: _callResults => result, - handleRevert: _callResults => result, - }; - } - - constructor( - public readonly chainId: ChainId, - protected readonly _samplerContract: ERC20BridgeSamplerContract, - poolsCaches?: { [key in SourcesWithPoolsCache]: PoolsCache }, - protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] }, - liquidityProviderRegistry: LiquidityProviderRegistry = {}, - bancorServiceFn: () => Promise = async () => undefined, - ) { - this.liquidityProviderRegistry = { - ...LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID[chainId], - ...liquidityProviderRegistry, - }; - this.poolsCaches = poolsCaches - ? poolsCaches - : { - [ERC20BridgeSource.BalancerV2]: new BalancerV2PoolsCache(chainId), - [ERC20BridgeSource.Balancer]: new BalancerPoolsCache(), - [ERC20BridgeSource.Cream]: new CreamPoolsCache(), - }; - // Initialize the Bancor service, fetching paths in the background - bancorServiceFn() - .then(service => (this._bancorService = service)) - .catch(/* do nothing */); - } - - public getTokenDecimals(tokens: string[]): BatchedOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Native, - contract: this._samplerContract, - function: this._samplerContract.getTokenDecimals, - params: [tokens], - }); - } - - public isAddressContract(address: string): BatchedOperation { - return { - encodeCall: () => this._samplerContract.isContract(address).getABIEncodedTransactionData(), - handleCallResults: (callResults: string) => - this._samplerContract.getABIDecodedReturnData('isContract', callResults), - handleRevert: () => { - /* should never happen */ - throw new Error('Invalid address for isAddressContract'); - }, - }; - } - - public getLimitOrderFillableTakerAmounts( - orders: SignedNativeOrder[], - exchangeAddress: string, - ): BatchedOperation { - // Skip checking empty or invalid orders on-chain, returning a constant - if (orders.length === 0) { - return SamplerOperations.constant([]); - } - if (orders.length === 1 && orders[0].order.maker === NULL_ADDRESS) { - return SamplerOperations.constant([ZERO_AMOUNT]); - } - return new SamplerContractOperation({ - source: ERC20BridgeSource.Native, - contract: this._samplerContract, - function: this._samplerContract.getLimitOrderFillableTakerAssetAmounts, - // tslint:disable-next-line:no-unnecessary-type-assertion - params: [orders.map(o => o.order as LimitOrderFields), orders.map(o => o.signature), exchangeAddress], - }); - } - - public getLimitOrderFillableMakerAmounts( - orders: SignedNativeOrder[], - exchangeAddress: string, - ): BatchedOperation { - // Skip checking empty or invalid orders on-chain, returning a constant - if (orders.length === 0) { - return SamplerOperations.constant([]); - } - if (orders.length === 1 && orders[0].order.maker === NULL_ADDRESS) { - return SamplerOperations.constant([ZERO_AMOUNT]); - } - return new SamplerContractOperation({ - source: ERC20BridgeSource.Native, - contract: this._samplerContract, - function: this._samplerContract.getLimitOrderFillableMakerAssetAmounts, - // tslint:disable-next-line:no-unnecessary-type-assertion - params: [orders.map(o => o.order as LimitOrderFields), orders.map(o => o.signature), exchangeAddress], - }); - } - - public getKyberSellQuotes( - kyberOpts: KyberSamplerOpts, - reserveOffset: BigNumber, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Kyber, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromKyberNetwork, - params: [{ ...kyberOpts, reserveOffset, hint: NULL_BYTES }, takerToken, makerToken, takerFillAmounts], - callback: (callResults: string, fillData: KyberFillData): BigNumber[] => { - const [reserveId, hint, samples] = this._samplerContract.getABIDecodedReturnData< - [string, string, BigNumber[]] - >('sampleSellsFromKyberNetwork', callResults); - fillData.hint = hint; - fillData.reserveId = reserveId; - fillData.networkProxy = kyberOpts.networkProxy; - return isAllowedKyberReserveId(reserveId) ? samples : []; - }, - }); - } - - public getKyberBuyQuotes( - kyberOpts: KyberSamplerOpts, - reserveOffset: BigNumber, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Kyber, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromKyberNetwork, - params: [{ ...kyberOpts, reserveOffset, hint: NULL_BYTES }, takerToken, makerToken, makerFillAmounts], - callback: (callResults: string, fillData: KyberFillData): BigNumber[] => { - const [reserveId, hint, samples] = this._samplerContract.getABIDecodedReturnData< - [string, string, BigNumber[]] - >('sampleBuysFromKyberNetwork', callResults); - fillData.hint = hint; - fillData.reserveId = reserveId; - fillData.networkProxy = kyberOpts.networkProxy; - return isAllowedKyberReserveId(reserveId) ? samples : []; - }, - }); - } - - public getKyberDmmSellQuotes( - router: string, - tokenAddressPath: string[], - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.KyberDmm, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromKyberDmm, - params: [router, tokenAddressPath, takerFillAmounts], - callback: (callResults: string, fillData: KyberDmmFillData): BigNumber[] => { - const [pools, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>( - 'sampleSellsFromKyberDmm', - callResults, - ); - fillData.poolsPath = pools; - fillData.router = router; - fillData.tokenAddressPath = tokenAddressPath; - return samples; - }, - }); - } - - public getKyberDmmBuyQuotes( - router: string, - tokenAddressPath: string[], - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.KyberDmm, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromKyberDmm, - params: [router, tokenAddressPath, makerFillAmounts], - callback: (callResults: string, fillData: KyberDmmFillData): BigNumber[] => { - const [pools, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>( - 'sampleBuysFromKyberDmm', - callResults, - ); - fillData.poolsPath = pools; - fillData.router = router; - fillData.tokenAddressPath = tokenAddressPath; - return samples; - }, - }); - } - - public getUniswapSellQuotes( - router: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - // Uniswap uses ETH instead of WETH, represented by address(0) - const uniswapTakerToken = takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken; - const uniswapMakerToken = makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken; - return new SamplerContractOperation({ - source: ERC20BridgeSource.Uniswap, - fillData: { router }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromUniswap, - params: [router, uniswapTakerToken, uniswapMakerToken, takerFillAmounts], - }); - } - - public getUniswapBuyQuotes( - router: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - // Uniswap uses ETH instead of WETH, represented by address(0) - const uniswapTakerToken = takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken; - const uniswapMakerToken = makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken; - return new SamplerContractOperation({ - source: ERC20BridgeSource.Uniswap, - fillData: { router }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromUniswap, - params: [router, uniswapTakerToken, uniswapMakerToken, makerFillAmounts], - }); - } - - public getUniswapV2SellQuotes( - router: string, - tokenAddressPath: string[], - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { tokenAddressPath, router }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromUniswapV2, - params: [router, tokenAddressPath, takerFillAmounts], - }); - } - - public getUniswapV2BuyQuotes( - router: string, - tokenAddressPath: string[], - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { tokenAddressPath, router }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromUniswapV2, - params: [router, tokenAddressPath, makerFillAmounts], - }); - } - - public getLiquidityProviderSellQuotes( - providerAddress: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - gasCost: number, - source: ERC20BridgeSource = ERC20BridgeSource.LiquidityProvider, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { - poolAddress: providerAddress, - gasCost, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromLiquidityProvider, - params: [providerAddress, takerToken, makerToken, takerFillAmounts], - }); - } - - public getLiquidityProviderBuyQuotes( - providerAddress: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - gasCost: number, - source: ERC20BridgeSource = ERC20BridgeSource.LiquidityProvider, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { - poolAddress: providerAddress, - gasCost, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromLiquidityProvider, - params: [providerAddress, takerToken, makerToken, makerFillAmounts], - }); - } - - public getCurveSellQuotes( - pool: CurveInfo, - fromTokenIdx: number, - toTokenIdx: number, - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.Curve, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { - pool, - fromTokenIdx, - toTokenIdx, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromCurve, - params: [ - { - poolAddress: pool.poolAddress, - sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector, - buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector, - }, - new BigNumber(fromTokenIdx), - new BigNumber(toTokenIdx), - takerFillAmounts, - ], - }); - } - - public getCurveBuyQuotes( - pool: CurveInfo, - fromTokenIdx: number, - toTokenIdx: number, - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.Curve, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { - pool, - fromTokenIdx, - toTokenIdx, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromCurve, - params: [ - { - poolAddress: pool.poolAddress, - sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector, - buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector, - }, - new BigNumber(fromTokenIdx), - new BigNumber(toTokenIdx), - makerFillAmounts, - ], - }); - } - - public getSmoothySellQuotes( - pool: CurveInfo, - fromTokenIdx: number, - toTokenIdx: number, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Smoothy, - fillData: { - pool, - fromTokenIdx, - toTokenIdx, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromSmoothy, - params: [ - { - poolAddress: pool.poolAddress, - sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector, - buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector, - }, - new BigNumber(fromTokenIdx), - new BigNumber(toTokenIdx), - takerFillAmounts, - ], - }); - } - - public getSmoothyBuyQuotes( - pool: CurveInfo, - fromTokenIdx: number, - toTokenIdx: number, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Smoothy, - fillData: { - pool, - fromTokenIdx, - toTokenIdx, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromSmoothy, - params: [ - { - poolAddress: pool.poolAddress, - sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector, - buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector, - }, - new BigNumber(fromTokenIdx), - new BigNumber(toTokenIdx), - makerFillAmounts, - ], - }); - } - - public getBalancerV2SellQuotes( - poolInfo: BalancerV2PoolInfo, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: poolInfo, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromBalancerV2, - params: [poolInfo, takerToken, makerToken, takerFillAmounts], - }); - } - - public getBalancerV2BuyQuotes( - poolInfo: BalancerV2PoolInfo, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: poolInfo, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromBalancerV2, - params: [poolInfo, takerToken, makerToken, makerFillAmounts], - }); - } - - public getBalancerSellQuotes( - poolAddress: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { poolAddress }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromBalancer, - params: [poolAddress, takerToken, makerToken, takerFillAmounts], - }); - } - - public getBalancerBuyQuotes( - poolAddress: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { poolAddress }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromBalancer, - params: [poolAddress, takerToken, makerToken, makerFillAmounts], - }); - } - - public getMStableSellQuotes( - router: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.MStable, - fillData: { router }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromMStable, - params: [router, takerToken, makerToken, takerFillAmounts], - }); - } - - public getMStableBuyQuotes( - router: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.MStable, - fillData: { router }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromMStable, - params: [router, takerToken, makerToken, makerFillAmounts], - }); - } - - public getBancorSellQuotes( - registry: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - const paths = this._bancorService ? this._bancorService.getPaths(takerToken, makerToken) : []; - return new SamplerContractOperation({ - source: ERC20BridgeSource.Bancor, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromBancor, - params: [{ registry, paths }, takerToken, makerToken, takerFillAmounts], - callback: (callResults: string, fillData: BancorFillData): BigNumber[] => { - const [networkAddress, path, samples] = this._samplerContract.getABIDecodedReturnData< - [string, string[], BigNumber[]] - >('sampleSellsFromBancor', callResults); - fillData.networkAddress = networkAddress; - fillData.path = path; - return samples; - }, - }); - } - - // Unimplemented - public getBancorBuyQuotes( - registry: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Bancor, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromBancor, - params: [{ registry, paths: [] }, takerToken, makerToken, makerFillAmounts], - callback: (callResults: string, fillData: BancorFillData): BigNumber[] => { - const [networkAddress, path, samples] = this._samplerContract.getABIDecodedReturnData< - [string, string[], BigNumber[]] - >('sampleBuysFromBancor', callResults); - fillData.networkAddress = networkAddress; - fillData.path = path; - return samples; - }, - }); - } - - public getMooniswapSellQuotes( - registry: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - // Mooniswap uses ETH instead of WETH, represented by address(0) - const mooniswapTakerToken = - takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken; - const mooniswapMakerToken = - makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken; - return new SamplerContractOperation({ - source: ERC20BridgeSource.Mooniswap, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromMooniswap, - params: [registry, mooniswapTakerToken, mooniswapMakerToken, takerFillAmounts], - callback: (callResults: string, fillData: MooniswapFillData): BigNumber[] => { - const [poolAddress, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( - 'sampleSellsFromMooniswap', - callResults, - ); - fillData.poolAddress = poolAddress; - return samples; - }, - }); - } - - public getMooniswapBuyQuotes( - registry: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - // Mooniswap uses ETH instead of WETH, represented by address(0) - const mooniswapTakerToken = - takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken; - const mooniswapMakerToken = - makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken; - return new SamplerContractOperation({ - source: ERC20BridgeSource.Mooniswap, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromMooniswap, - params: [registry, mooniswapTakerToken, mooniswapMakerToken, makerFillAmounts], - callback: (callResults: string, fillData: MooniswapFillData): BigNumber[] => { - const [poolAddress, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( - 'sampleBuysFromMooniswap', - callResults, - ); - fillData.poolAddress = poolAddress; - return samples; - }, - }); - } - - public getUniswapV3SellQuotes( - router: string, - quoter: string, - tokenAddressPath: string[], - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.UniswapV3, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromUniswapV3, - params: [quoter, tokenAddressPath, takerFillAmounts], - callback: (callResults: string, fillData: UniswapV3FillData): BigNumber[] => { - const [paths, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>( - 'sampleSellsFromUniswapV3', - callResults, - ); - fillData.router = router; - fillData.tokenAddressPath = tokenAddressPath; - fillData.pathAmounts = paths.map((uniswapPath, i) => ({ - uniswapPath, - inputAmount: takerFillAmounts[i], - })); - return samples; - }, - }); - } - - public getUniswapV3BuyQuotes( - router: string, - quoter: string, - tokenAddressPath: string[], - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.UniswapV3, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromUniswapV3, - params: [quoter, tokenAddressPath, makerFillAmounts], - callback: (callResults: string, fillData: UniswapV3FillData): BigNumber[] => { - const [paths, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>( - 'sampleBuysFromUniswapV3', - callResults, - ); - fillData.router = router; - fillData.tokenAddressPath = tokenAddressPath; - fillData.pathAmounts = paths.map((uniswapPath, i) => ({ - uniswapPath, - inputAmount: makerFillAmounts[i], - })); - return samples; - }, - }); - } - - public getTwoHopSellQuotes( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - sellAmount: BigNumber, - ): BatchedOperation>> { - const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources); - if (_sources.length === 0) { - return SamplerOperations.constant([]); - } - const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph); - const subOps = intermediateTokens.map(intermediateToken => { - const firstHopOps = this._getSellQuoteOperations(_sources, intermediateToken, takerToken, [ZERO_AMOUNT]); - const secondHopOps = this._getSellQuoteOperations(_sources, makerToken, intermediateToken, [ZERO_AMOUNT]); - return new SamplerContractOperation({ - contract: this._samplerContract, - source: ERC20BridgeSource.MultiHop, - function: this._samplerContract.sampleTwoHopSell, - params: [firstHopOps.map(op => op.encodeCall()), secondHopOps.map(op => op.encodeCall()), sellAmount], - fillData: { intermediateToken } as MultiHopFillData, // tslint:disable-line:no-object-literal-type-assertion - callback: (callResults: string, fillData: MultiHopFillData): BigNumber[] => { - const [firstHop, secondHop, buyAmount] = this._samplerContract.getABIDecodedReturnData< - [HopInfo, HopInfo, BigNumber] - >('sampleTwoHopSell', callResults); - // Ensure the hop sources are set even when the buy amount is zero - fillData.firstHopSource = firstHopOps[firstHop.sourceIndex.toNumber()]; - fillData.secondHopSource = secondHopOps[secondHop.sourceIndex.toNumber()]; - if (buyAmount.isZero()) { - return [ZERO_AMOUNT]; - } - fillData.firstHopSource.handleCallResults(firstHop.returnData); - fillData.secondHopSource.handleCallResults(secondHop.returnData); - return [buyAmount]; - }, - }); - }); - return this._createBatch( - subOps, - (samples: BigNumber[][]) => { - return subOps.map((op, i) => { - return { - source: op.source, - output: samples[i][0], - input: sellAmount, - fillData: op.fillData, - }; - }); - }, - () => { - logUtils.warn('SamplerContractOperation: Two hop sampler reverted'); - return []; - }, - ); - } - - public getTwoHopBuyQuotes( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - buyAmount: BigNumber, - ): BatchedOperation>> { - const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources); - if (_sources.length === 0) { - return SamplerOperations.constant([]); - } - const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph); - const subOps = intermediateTokens.map(intermediateToken => { - const firstHopOps = this._getBuyQuoteOperations(_sources, intermediateToken, takerToken, [ - new BigNumber(0), - ]); - const secondHopOps = this._getBuyQuoteOperations(_sources, makerToken, intermediateToken, [ - new BigNumber(0), - ]); - return new SamplerContractOperation({ - contract: this._samplerContract, - source: ERC20BridgeSource.MultiHop, - function: this._samplerContract.sampleTwoHopBuy, - params: [firstHopOps.map(op => op.encodeCall()), secondHopOps.map(op => op.encodeCall()), buyAmount], - fillData: { intermediateToken } as MultiHopFillData, // tslint:disable-line:no-object-literal-type-assertion - callback: (callResults: string, fillData: MultiHopFillData): BigNumber[] => { - const [firstHop, secondHop, sellAmount] = this._samplerContract.getABIDecodedReturnData< - [HopInfo, HopInfo, BigNumber] - >('sampleTwoHopBuy', callResults); - if (sellAmount.isEqualTo(MAX_UINT256)) { - return [sellAmount]; - } - fillData.firstHopSource = firstHopOps[firstHop.sourceIndex.toNumber()]; - fillData.secondHopSource = secondHopOps[secondHop.sourceIndex.toNumber()]; - fillData.firstHopSource.handleCallResults(firstHop.returnData); - fillData.secondHopSource.handleCallResults(secondHop.returnData); - return [sellAmount]; - }, - }); - }); - return this._createBatch( - subOps, - (samples: BigNumber[][]) => { - return subOps.map((op, i) => { - return { - source: op.source, - output: samples[i][0], - input: buyAmount, - fillData: op.fillData, - }; - }); - }, - () => { - logUtils.warn('SamplerContractOperation: Two hop sampler reverted'); - return []; - }, - ); - } - - public getShellSellQuotes( - poolAddress: string, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.Shell, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { poolAddress }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromShell, - params: [poolAddress, takerToken, makerToken, takerFillAmounts], - }); - } - - public getShellBuyQuotes( - poolAddress: string, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - source: ERC20BridgeSource = ERC20BridgeSource.Shell, - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source, - fillData: { poolAddress }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromShell, - params: [poolAddress, takerToken, makerToken, makerFillAmounts], - }); - } - - public getDODOSellQuotes( - opts: { registry: string; helper: string }, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Dodo, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromDODO, - params: [opts, takerToken, makerToken, takerFillAmounts], - callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { - const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< - [boolean, string, BigNumber[]] - >('sampleSellsFromDODO', callResults); - fillData.isSellBase = isSellBase; - fillData.poolAddress = pool; - fillData.helperAddress = opts.helper; - return samples; - }, - }); - } - - public getDODOBuyQuotes( - opts: { registry: string; helper: string }, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Dodo, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromDODO, - params: [opts, takerToken, makerToken, makerFillAmounts], - callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { - const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< - [boolean, string, BigNumber[]] - >('sampleBuysFromDODO', callResults); - fillData.isSellBase = isSellBase; - fillData.poolAddress = pool; - fillData.helperAddress = opts.helper; - return samples; - }, - }); - } - - public getDODOV2SellQuotes( - registry: string, - offset: BigNumber, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.DodoV2, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromDODOV2, - params: [registry, offset, takerToken, makerToken, takerFillAmounts], - callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { - const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< - [boolean, string, BigNumber[]] - >('sampleSellsFromDODOV2', callResults); - fillData.isSellBase = isSellBase; - fillData.poolAddress = pool; - return samples; - }, - }); - } - - public getDODOV2BuyQuotes( - registry: string, - offset: BigNumber, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.DodoV2, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromDODOV2, - params: [registry, offset, takerToken, makerToken, makerFillAmounts], - callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { - const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< - [boolean, string, BigNumber[]] - >('sampleSellsFromDODOV2', callResults); - fillData.isSellBase = isSellBase; - fillData.poolAddress = pool; - return samples; - }, - }); - } - - public getMakerPsmSellQuotes( - psmInfo: PsmInfo, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.MakerPsm, - fillData: { - isSellOperation: true, - takerToken, - makerToken, - ...psmInfo, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromMakerPsm, - params: [psmInfo, takerToken, makerToken, takerFillAmounts], - }); - } - - public getMakerPsmBuyQuotes( - psmInfo: PsmInfo, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.MakerPsm, - fillData: { - isSellOperation: false, - takerToken, - makerToken, - ...psmInfo, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromMakerPsm, - params: [psmInfo, takerToken, makerToken, makerFillAmounts], - }); - } - - public getLidoSellQuotes( - lidoInfo: LidoInfo, - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Lido, - fillData: { - takerToken, - stEthTokenAddress: lidoInfo.stEthToken, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromLido, - params: [lidoInfo, takerToken, makerToken, takerFillAmounts], - }); - } - - public getLidoBuyQuotes( - lidoInfo: LidoInfo, - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.Lido, - fillData: { - takerToken, - stEthTokenAddress: lidoInfo.stEthToken, - }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromLido, - params: [lidoInfo, takerToken, makerToken, makerFillAmounts], - }); - } - - public getMedianSellRate( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - takerFillAmount: BigNumber, - ): BatchedOperation { - if (makerToken.toLowerCase() === takerToken.toLowerCase()) { - return SamplerOperations.constant(new BigNumber(1)); - } - const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, [takerFillAmount], { - default: [], - }); - return this._createBatch( - subOps, - (samples: BigNumber[][]) => { - if (samples.length === 0) { - return ZERO_AMOUNT; - } - const flatSortedSamples = samples - .reduce((acc, v) => acc.concat(...v)) - .filter(v => !v.isZero()) - .sort((a, b) => a.comparedTo(b)); - if (flatSortedSamples.length === 0) { - return ZERO_AMOUNT; - } - const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)]; - return medianSample.div(takerFillAmount); - }, - () => ZERO_AMOUNT, - ); - } - - public getSellQuotes( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - ): BatchedOperation { - const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, takerFillAmounts); - return this._createBatch( - subOps, - (samples: BigNumber[][]) => { - return subOps.map((op, i) => { - return samples[i].map((output, j) => ({ - source: op.source, - output, - input: takerFillAmounts[j], - fillData: op.fillData, - })); - }); - }, - () => [], - ); - } - - public getBuyQuotes( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): BatchedOperation { - const subOps = this._getBuyQuoteOperations(sources, makerToken, takerToken, makerFillAmounts); - return this._createBatch( - subOps, - (samples: BigNumber[][]) => { - return subOps.map((op, i) => { - return samples[i].map((output, j) => ({ - source: op.source, - output, - input: makerFillAmounts[j], - fillData: op.fillData, - })); - }); - }, - () => [], - ); - } - - private _getSellQuoteOperations( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - takerFillAmounts: BigNumber[], - tokenAdjacencyGraph: TokenAdjacencyGraph = this.tokenAdjacencyGraph, - ): SourceQuoteOperation[] { - // Find the adjacent tokens in the provided tooken adjacency graph, - // e.g if this is DAI->USDC we may check for DAI->WETH->USDC - const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph); - // Drop out MultiHop and Native as we do not query those here. - const _sources = SELL_SOURCE_FILTER_BY_CHAIN_ID[this.chainId] - .exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]) - .getAllowed(sources); - const allOps = _.flatten( - _sources.map((source): SourceQuoteOperation | SourceQuoteOperation[] => { - if (isBadTokenForSource(makerToken, source) || isBadTokenForSource(takerToken, source)) { - return []; - } - switch (source) { - case ERC20BridgeSource.Eth2Dai: - return []; - case ERC20BridgeSource.Uniswap: - return isValidAddress(UNISWAPV1_ROUTER_BY_CHAIN_ID[this.chainId]) - ? this.getUniswapSellQuotes( - UNISWAPV1_ROUTER_BY_CHAIN_ID[this.chainId], - makerToken, - takerToken, - takerFillAmounts, - ) - : []; - case ERC20BridgeSource.UniswapV2: - case ERC20BridgeSource.SushiSwap: - case ERC20BridgeSource.CryptoCom: - case ERC20BridgeSource.PancakeSwap: - case ERC20BridgeSource.PancakeSwapV2: - case ERC20BridgeSource.BakerySwap: - case ERC20BridgeSource.ApeSwap: - case ERC20BridgeSource.CafeSwap: - case ERC20BridgeSource.CheeseSwap: - case ERC20BridgeSource.JulSwap: - case ERC20BridgeSource.QuickSwap: - case ERC20BridgeSource.ComethSwap: - case ERC20BridgeSource.Dfyn: - case ERC20BridgeSource.WaultSwap: - case ERC20BridgeSource.Polydex: - case ERC20BridgeSource.ShibaSwap: - case ERC20BridgeSource.JetSwap: - case ERC20BridgeSource.Pangolin: - case ERC20BridgeSource.TraderJoe: - case ERC20BridgeSource.SpiritSwap: - case ERC20BridgeSource.SpookySwap: - const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source); - if (!isValidAddress(uniLikeRouter)) { - return []; - } - return [ - [takerToken, makerToken], - ...intermediateTokens.map(t => [takerToken, t, makerToken]), - ].map(path => this.getUniswapV2SellQuotes(uniLikeRouter, path, takerFillAmounts, source)); - case ERC20BridgeSource.KyberDmm: - const kyberDmmRouter = KYBER_DMM_ROUTER_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(kyberDmmRouter)) { - return []; - } - return this.getKyberDmmSellQuotes(kyberDmmRouter, [takerToken, makerToken], takerFillAmounts); - case ERC20BridgeSource.Kyber: - return getKyberOffsets().map(offset => - this.getKyberSellQuotes( - KYBER_CONFIG_BY_CHAIN_ID[this.chainId], - offset, - makerToken, - takerToken, - takerFillAmounts, - ), - ); - case ERC20BridgeSource.Curve: - case ERC20BridgeSource.CurveV2: - case ERC20BridgeSource.Swerve: - case ERC20BridgeSource.SnowSwap: - case ERC20BridgeSource.Nerve: - case ERC20BridgeSource.Belt: - case ERC20BridgeSource.Ellipsis: - case ERC20BridgeSource.Saddle: - case ERC20BridgeSource.XSigma: - case ERC20BridgeSource.FirebirdOneSwap: - case ERC20BridgeSource.IronSwap: - case ERC20BridgeSource.ACryptos: - return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getCurveSellQuotes( - pool, - pool.takerTokenIdx, - pool.makerTokenIdx, - takerFillAmounts, - source, - ), - ); - case ERC20BridgeSource.Smoothy: - return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getSmoothySellQuotes( - pool, - pool.tokens.indexOf(takerToken), - pool.tokens.indexOf(makerToken), - takerFillAmounts, - ), - ); - case ERC20BridgeSource.Shell: - case ERC20BridgeSource.Component: - return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getShellSellQuotes(pool, makerToken, takerToken, takerFillAmounts, source), - ); - case ERC20BridgeSource.LiquidityProvider: - return getLiquidityProvidersForPair( - this.liquidityProviderRegistry, - takerToken, - makerToken, - ).map(({ providerAddress, gasCost }) => - this.getLiquidityProviderSellQuotes( - providerAddress, - makerToken, - takerToken, - takerFillAmounts, - gasCost, - ), - ); - case ERC20BridgeSource.MStable: - return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getMStableSellQuotes(pool, makerToken, takerToken, takerFillAmounts), - ); - case ERC20BridgeSource.Mooniswap: - return [ - ...MOONISWAP_REGISTRIES_BY_CHAIN_ID[this.chainId] - .filter(r => isValidAddress(r)) - .map(registry => - this.getMooniswapSellQuotes(registry, makerToken, takerToken, takerFillAmounts), - ), - ]; - case ERC20BridgeSource.Balancer: - return ( - this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || [] - ).map(balancerPool => - this.getBalancerSellQuotes( - balancerPool, - makerToken, - takerToken, - takerFillAmounts, - ERC20BridgeSource.Balancer, - ), - ); - case ERC20BridgeSource.BalancerV2: - const poolIds = - this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || []; - - const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]; - if (vault === NULL_ADDRESS) { - return []; - } - return poolIds.map(poolId => - this.getBalancerV2SellQuotes( - { poolId, vault }, - makerToken, - takerToken, - takerFillAmounts, - ERC20BridgeSource.BalancerV2, - ), - ); - - case ERC20BridgeSource.Cream: - return ( - this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || [] - ).map(creamPool => - this.getBalancerSellQuotes( - creamPool, - makerToken, - takerToken, - takerFillAmounts, - ERC20BridgeSource.Cream, - ), - ); - case ERC20BridgeSource.Dodo: - if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) { - return []; - } - return this.getDODOSellQuotes( - DODOV1_CONFIG_BY_CHAIN_ID[this.chainId], - makerToken, - takerToken, - takerFillAmounts, - ); - case ERC20BridgeSource.DodoV2: - return _.flatten( - DODOV2_FACTORIES_BY_CHAIN_ID[this.chainId] - .filter(factory => isValidAddress(factory)) - .map(factory => - getDodoV2Offsets().map(offset => - this.getDODOV2SellQuotes( - factory, - offset, - makerToken, - takerToken, - takerFillAmounts, - ), - ), - ), - ); - case ERC20BridgeSource.Bancor: - if (!isValidAddress(BANCOR_REGISTRY_BY_CHAIN_ID[this.chainId])) { - return []; - } - return this.getBancorSellQuotes( - BANCOR_REGISTRY_BY_CHAIN_ID[this.chainId], - makerToken, - takerToken, - takerFillAmounts, - ); - case ERC20BridgeSource.Linkswap: - if (!isValidAddress(LINKSWAP_ROUTER_BY_CHAIN_ID[this.chainId])) { - return []; - } - return [ - [takerToken, makerToken], - ...getIntermediateTokens(makerToken, takerToken, { - default: [MAINNET_TOKENS.LINK, MAINNET_TOKENS.WETH], - }).map(t => [takerToken, t, makerToken]), - ].map(path => - this.getUniswapV2SellQuotes( - LINKSWAP_ROUTER_BY_CHAIN_ID[this.chainId], - path, - takerFillAmounts, - ERC20BridgeSource.Linkswap, - ), - ); - case ERC20BridgeSource.MakerPsm: - const psmInfo = MAKER_PSM_INFO_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(psmInfo.psmAddress)) { - return []; - } - return this.getMakerPsmSellQuotes(psmInfo, makerToken, takerToken, takerFillAmounts); - case ERC20BridgeSource.UniswapV3: { - const { quoter, router } = UNISWAPV3_CONFIG_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(router) || !isValidAddress(quoter)) { - return []; - } - return [ - [takerToken, makerToken], - ...intermediateTokens.map(t => [takerToken, t, makerToken]), - ].map(path => this.getUniswapV3SellQuotes(router, quoter, path, takerFillAmounts)); - } - case ERC20BridgeSource.Lido: { - const lidoInfo = LIDO_INFO_BY_CHAIN[this.chainId]; - if ( - lidoInfo.stEthToken === NULL_ADDRESS || - lidoInfo.wethToken === NULL_ADDRESS || - takerToken.toLowerCase() !== lidoInfo.wethToken.toLowerCase() || - makerToken.toLowerCase() !== lidoInfo.stEthToken.toLowerCase() - ) { - return []; - } - - return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts); - } - default: - throw new Error(`Unsupported sell sample source: ${source}`); - } - }), - ); - return allOps; - } - - private _getBuyQuoteOperations( - sources: ERC20BridgeSource[], - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation[] { - // Find the adjacent tokens in the provided tooken adjacency graph, - // e.g if this is DAI->USDC we may check for DAI->WETH->USDC - const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph); - const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources); - return _.flatten( - _sources.map((source): SourceQuoteOperation | SourceQuoteOperation[] => { - switch (source) { - case ERC20BridgeSource.Eth2Dai: - return []; - case ERC20BridgeSource.Uniswap: - return isValidAddress(UNISWAPV1_ROUTER_BY_CHAIN_ID[this.chainId]) - ? this.getUniswapBuyQuotes( - UNISWAPV1_ROUTER_BY_CHAIN_ID[this.chainId], - makerToken, - takerToken, - makerFillAmounts, - ) - : []; - case ERC20BridgeSource.UniswapV2: - case ERC20BridgeSource.SushiSwap: - case ERC20BridgeSource.CryptoCom: - case ERC20BridgeSource.PancakeSwap: - case ERC20BridgeSource.PancakeSwapV2: - case ERC20BridgeSource.BakerySwap: - case ERC20BridgeSource.ApeSwap: - case ERC20BridgeSource.CafeSwap: - case ERC20BridgeSource.CheeseSwap: - case ERC20BridgeSource.JulSwap: - case ERC20BridgeSource.QuickSwap: - case ERC20BridgeSource.ComethSwap: - case ERC20BridgeSource.Dfyn: - case ERC20BridgeSource.WaultSwap: - case ERC20BridgeSource.Polydex: - case ERC20BridgeSource.ShibaSwap: - case ERC20BridgeSource.JetSwap: - case ERC20BridgeSource.Pangolin: - case ERC20BridgeSource.TraderJoe: - case ERC20BridgeSource.SpiritSwap: - case ERC20BridgeSource.SpookySwap: - const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source); - if (!isValidAddress(uniLikeRouter)) { - return []; - } - return [ - [takerToken, makerToken], - ...intermediateTokens.map(t => [takerToken, t, makerToken]), - ].map(path => this.getUniswapV2BuyQuotes(uniLikeRouter, path, makerFillAmounts, source)); - case ERC20BridgeSource.KyberDmm: - const kyberDmmRouter = KYBER_DMM_ROUTER_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(kyberDmmRouter)) { - return []; - } - return this.getKyberDmmBuyQuotes(kyberDmmRouter, [takerToken, makerToken], makerFillAmounts); - case ERC20BridgeSource.Kyber: - return getKyberOffsets().map(offset => - this.getKyberBuyQuotes( - KYBER_CONFIG_BY_CHAIN_ID[this.chainId], - offset, - makerToken, - takerToken, - makerFillAmounts, - ), - ); - case ERC20BridgeSource.Curve: - case ERC20BridgeSource.CurveV2: - case ERC20BridgeSource.Swerve: - case ERC20BridgeSource.SnowSwap: - case ERC20BridgeSource.Nerve: - case ERC20BridgeSource.Belt: - case ERC20BridgeSource.Ellipsis: - case ERC20BridgeSource.Saddle: - case ERC20BridgeSource.XSigma: - case ERC20BridgeSource.FirebirdOneSwap: - case ERC20BridgeSource.IronSwap: - case ERC20BridgeSource.ACryptos: - return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getCurveBuyQuotes( - pool, - pool.takerTokenIdx, - pool.makerTokenIdx, - makerFillAmounts, - source, - ), - ); - case ERC20BridgeSource.Smoothy: - return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getSmoothyBuyQuotes( - pool, - pool.tokens.indexOf(takerToken), - pool.tokens.indexOf(makerToken), - makerFillAmounts, - ), - ); - case ERC20BridgeSource.Shell: - case ERC20BridgeSource.Component: - return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getShellBuyQuotes(pool, makerToken, takerToken, makerFillAmounts, source), - ); - case ERC20BridgeSource.LiquidityProvider: - return getLiquidityProvidersForPair( - this.liquidityProviderRegistry, - takerToken, - makerToken, - ).map(({ providerAddress, gasCost }) => - this.getLiquidityProviderBuyQuotes( - providerAddress, - makerToken, - takerToken, - makerFillAmounts, - gasCost, - ), - ); - case ERC20BridgeSource.MStable: - return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool => - this.getMStableBuyQuotes(pool, makerToken, takerToken, makerFillAmounts), - ); - case ERC20BridgeSource.Mooniswap: - return [ - ...MOONISWAP_REGISTRIES_BY_CHAIN_ID[this.chainId] - .filter(r => isValidAddress(r)) - .map(registry => - this.getMooniswapBuyQuotes(registry, makerToken, takerToken, makerFillAmounts), - ), - ]; - case ERC20BridgeSource.Balancer: - return ( - this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || [] - ).map(poolAddress => - this.getBalancerBuyQuotes( - poolAddress, - makerToken, - takerToken, - makerFillAmounts, - ERC20BridgeSource.Balancer, - ), - ); - case ERC20BridgeSource.BalancerV2: - const poolIds = - this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || []; - - const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]; - if (vault === NULL_ADDRESS) { - return []; - } - return poolIds.map(poolId => - this.getBalancerV2BuyQuotes( - { poolId, vault }, - makerToken, - takerToken, - makerFillAmounts, - ERC20BridgeSource.BalancerV2, - ), - ); - case ERC20BridgeSource.Cream: - return ( - this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair( - takerToken, - makerToken, - ) || [] - ).map(poolAddress => - this.getBalancerBuyQuotes( - poolAddress, - makerToken, - takerToken, - makerFillAmounts, - ERC20BridgeSource.Cream, - ), - ); - case ERC20BridgeSource.Dodo: - if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) { - return []; - } - return this.getDODOBuyQuotes( - DODOV1_CONFIG_BY_CHAIN_ID[this.chainId], - makerToken, - takerToken, - makerFillAmounts, - ); - case ERC20BridgeSource.DodoV2: - return _.flatten( - DODOV2_FACTORIES_BY_CHAIN_ID[this.chainId] - .filter(factory => isValidAddress(factory)) - .map(factory => - getDodoV2Offsets().map(offset => - this.getDODOV2BuyQuotes( - factory, - offset, - makerToken, - takerToken, - makerFillAmounts, - ), - ), - ), - ); - case ERC20BridgeSource.Bancor: - // Unimplemented - // return this.getBancorBuyQuotes(makerToken, takerToken, makerFillAmounts); - return []; - case ERC20BridgeSource.Linkswap: - if (!isValidAddress(LINKSWAP_ROUTER_BY_CHAIN_ID[this.chainId])) { - return []; - } - return [ - [takerToken, makerToken], - // LINK is the base asset in many of the pools on Linkswap - ...getIntermediateTokens(makerToken, takerToken, { - default: [MAINNET_TOKENS.LINK, MAINNET_TOKENS.WETH], - }).map(t => [takerToken, t, makerToken]), - ].map(path => - this.getUniswapV2BuyQuotes( - LINKSWAP_ROUTER_BY_CHAIN_ID[this.chainId], - path, - makerFillAmounts, - ERC20BridgeSource.Linkswap, - ), - ); - case ERC20BridgeSource.MakerPsm: - const psmInfo = MAKER_PSM_INFO_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(psmInfo.psmAddress)) { - return []; - } - return this.getMakerPsmBuyQuotes(psmInfo, makerToken, takerToken, makerFillAmounts); - case ERC20BridgeSource.UniswapV3: { - const { quoter, router } = UNISWAPV3_CONFIG_BY_CHAIN_ID[this.chainId]; - if (!isValidAddress(router) || !isValidAddress(quoter)) { - return []; - } - return [ - [takerToken, makerToken], - ...intermediateTokens.map(t => [takerToken, t, makerToken]), - ].map(path => this.getUniswapV3BuyQuotes(router, quoter, path, makerFillAmounts)); - } - case ERC20BridgeSource.Lido: { - const lidoInfo = LIDO_INFO_BY_CHAIN[this.chainId]; - - if ( - lidoInfo.stEthToken === NULL_ADDRESS || - lidoInfo.wethToken === NULL_ADDRESS || - takerToken.toLowerCase() !== lidoInfo.wethToken.toLowerCase() || - makerToken.toLowerCase() !== lidoInfo.stEthToken.toLowerCase() - ) { - return []; - } - - return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, makerFillAmounts); - } - default: - throw new Error(`Unsupported buy sample source: ${source}`); - } - }), - ); - } - - /** - * Wraps `subOps` operations into a batch call to the sampler - * @param subOps An array of Sampler operations - * @param resultHandler The handler of the parsed batch results - * @param revertHandler The handle for when the batch operation reverts. The result data is provided as an argument - */ - private _createBatch( - subOps: Array>, - resultHandler: (results: TResult[]) => T, - revertHandler: (result: string) => T, - ): BatchedOperation { - return { - encodeCall: () => { - const subCalls = subOps.map(op => op.encodeCall()); - return this._samplerContract.batchCall(subCalls).getABIEncodedTransactionData(); - }, - handleCallResults: callResults => { - const rawSubCallResults = this._samplerContract.getABIDecodedReturnData( - 'batchCall', - callResults, - ); - const results = subOps.map((op, i) => - rawSubCallResults[i].success - ? op.handleCallResults(rawSubCallResults[i].data) - : op.handleRevert(rawSubCallResults[i].data), - ); - return resultHandler(results); - }, - handleRevert: revertHandler, - }; - } -} -// tslint:disable max-file-line-count diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_service_rpc_client.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_service_rpc_client.ts new file mode 100644 index 0000000000..d4aac06649 --- /dev/null +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_service_rpc_client.ts @@ -0,0 +1,128 @@ +import { BigNumber } from '@0x/utils'; +import { Client as OpenRpcClient, HTTPTransport, RequestManager } from '@open-rpc/client-js'; + +import { Address, Bytes } from '../../types'; + +type DecimalString = string; + +export interface LiquidityCurvePoint { + sellAmount: BigNumber; + buyAmount: BigNumber; + encodedFillData: Bytes; + gasCost: number; +} + +type RpcLiquidityCurvePoint = Omit, 'buyAmount'> & { + sellAmount: DecimalString; + buyAmount: DecimalString; +} + +export interface LiquidityRequest { + tokenPath: Address[]; + inputAmount: BigNumber; + source: string; + demand?: boolean; +} + +type RpcLiquidityRequest = Omit & { + inputAmount: string; +} + +export interface PriceRequest { + tokenPath: Address[]; + sources?: string[]; + demand?: boolean; +} + +type RpcPriceRequest = PriceRequest; + +export interface LiquidityResponse { + source: string; + liquidityCurves: LiquidityCurvePoint[][]; +} + +type RpcLiquidityResponse = & Omit & { + source: string; + liquidityCurves: RpcLiquidityCurvePoint[][]; +} + +export interface TokenResponse { + address: Address; + symbol: string; + decimals: number; + gasCost: number; +} + +type RpcTokenResponse = TokenResponse; + +export class SamplerServiceRpcClient { + private _rpcClient: OpenRpcClient; + + public constructor(url: string) { + const transport = new HTTPTransport(url); + this._rpcClient = new OpenRpcClient(new RequestManager([transport])); + } + + private async _requestAsync(method: string, params: TArgs[] = []): Promise { + return this._rpcClient.request({ method, params }) as Promise; + } + + public async getChainIdAsync(): Promise { + return this._requestAsync('get_chain_id'); + } + + public async getSellLiquidityAsync(reqs: LiquidityRequest[]): Promise { + const resp = await this._requestAsync( + 'get_sell_liquidity', + [ + reqs.map(r => ({ + ...r, + inputAmount: r.inputAmount.toString(10), + })), + ], + ); + return resp.map(r => ({ + ...r, + liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({ + ...c, + buyAmount: new BigNumber(c.buyAmount), + sellAmount: new BigNumber(c.sellAmount), + }))), + })); + } + + public async getBuyLiquidityAsync(reqs: LiquidityRequest[]): Promise { + const resp = await this._requestAsync( + 'get_buy_liquidity', + [ + reqs.map(r => ({ + ...r, + inputAmount: r.inputAmount.toString(10), + })), + ], + ); + return resp.map(r => ({ + ...r, + liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({ + ...c, + buyAmount: new BigNumber(c.buyAmount), + sellAmount: new BigNumber(c.sellAmount), + }))), + })); + } + + public async getPricesAsync(reqs: PriceRequest[]): Promise { + const resp = await this._requestAsync( + 'get_prices', + [ reqs ], + ); + return resp.map(r => new BigNumber(r)); + } + + public async getTokensAsync(addresses: Address[]): Promise { + return this._requestAsync( + 'get_tokens', + [ addresses ], + ); + } +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 84b5a323a7..16296bd907 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -7,7 +7,7 @@ import { V4RFQIndicativeQuote } from '@0x/quote-server'; import { MarketOperation } from '@0x/types'; import { BigNumber } from '@0x/utils'; -import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types'; +import { Bytes, NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types'; import { QuoteRequestor } from '../../utils/quote_requestor'; import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator'; @@ -172,12 +172,14 @@ export type NativeLimitOrderFillData = FillQuoteTransformerLimitOrderInfo; export type NativeFillData = NativeRfqOrderFillData | NativeLimitOrderFillData; // Represents an individual DEX sample from the sampler contract -export interface DexSample { +export interface DexSample { source: ERC20BridgeSource; - fillData: TFillData; + encodedFillData: Bytes; input: BigNumber; output: BigNumber; + gasCost: number; } + export interface CurveFillData extends FillData { fromTokenIdx: number; toTokenIdx: number; @@ -273,12 +275,12 @@ export interface LidoFillData extends FillData { /** * Represents a node on a fill path. */ -export interface Fill { +export interface Fill { // basic data for every fill source: ERC20BridgeSource; // TODO jacob people seem to agree that orderType here is more readable type: FillQuoteTransformerOrderType; // should correspond with TFillData - fillData: TFillData; + encodedFillData: Bytes; // Unique ID of the original source path this fill belongs to. // This is generated when the path is generated and is useful to distinguish // paths that have the same `source` IDs but are distinct (e.g., Curves). @@ -300,10 +302,10 @@ export interface Fill { /** * Represents continguous fills on a path that have been merged together. */ -export interface CollapsedFill { +export interface CollapsedFill { source: ERC20BridgeSource; type: FillQuoteTransformerOrderType; // should correspond with TFillData - fillData: TFillData; + encodedFillData: Bytes; // Unique ID of the original source path this fill belongs to. // This is generated when the path is generated and is useful to distinguish // paths that have the same `source` IDs but are distinct (e.g., Curves). @@ -328,7 +330,7 @@ export interface CollapsedFill { /** * A `CollapsedFill` wrapping a native order. */ -export interface NativeCollapsedFill extends CollapsedFill {} +export interface NativeCollapsedFill extends CollapsedFill {} export interface OptimizedMarketOrderBase { source: ERC20BridgeSource; @@ -481,7 +483,7 @@ export interface SourceQuoteOperation ext export interface OptimizerResult { optimizedOrders: OptimizedMarketOrder[]; sourceFlags: bigint; - liquidityDelivered: CollapsedFill[] | DexSample; + // liquidityDelivered: CollapsedFill[] | DexSample; marketSideLiquidity: MarketSideLiquidity; adjustedRate: BigNumber; unoptimizedPath?: CollapsedPath; @@ -494,7 +496,7 @@ export interface OptimizerResultWithReport extends OptimizerResult { priceComparisonsReport?: PriceComparisonsReport; } -export type MarketDepthSide = Array>>; +export type MarketDepthSide = Array>; export interface MarketDepth { bids: MarketDepthSide; @@ -520,8 +522,8 @@ export interface MarketSideLiquidity { export interface RawQuotes { nativeOrders: NativeOrderWithFillableAmounts[]; rfqtIndicativeQuotes: V4RFQIndicativeQuote[]; - twoHopQuotes: Array>; - dexQuotes: Array>>; + // twoHopQuotes: Array>; + dexQuotes: Array>; } export interface TokenAdjacencyGraph { diff --git a/packages/asset-swapper/src/utils/protocol_fee_utils.ts b/packages/asset-swapper/src/utils/protocol_fee_utils.ts index c7bccb5827..8654b28a14 100644 --- a/packages/asset-swapper/src/utils/protocol_fee_utils.ts +++ b/packages/asset-swapper/src/utils/protocol_fee_utils.ts @@ -1,5 +1,6 @@ import { BigNumber } from '@0x/utils'; import * as heartbeats from 'heartbeats'; +import fetch from 'axios'; import { constants } from '../constants'; import { SwapQuoterError } from '../types'; @@ -61,7 +62,7 @@ export class ProtocolFeeUtils { private async _getGasPriceFromGasStationOrThrowAsync(): Promise { try { const res = await fetch(this._ethGasStationUrl); - const gasInfo = await res.json(); + const gasInfo = res.data; // Eth Gas Station result is gwei * 10 // tslint:disable-next-line:custom-no-magic-numbers const BASE_TEN = 10; diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index e7f2863e32..c7b04186c9 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -73,47 +73,48 @@ export interface PriceComparisonsReport { export function generateQuoteReport( marketOperation: MarketOperation, nativeOrders: NativeOrderWithFillableAmounts[], - liquidityDelivered: ReadonlyArray | DexSample, + // liquidityDelivered: ReadonlyArray | DexSample, comparisonPrice?: BigNumber | undefined, quoteRequestor?: QuoteRequestor, ): QuoteReport { - const nativeOrderSourcesConsidered = nativeOrders.map(order => - nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor), - ); - const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)]; - - let sourcesDelivered; - if (Array.isArray(liquidityDelivered)) { - // create easy way to look up fillable amounts - const nativeOrderSignaturesToFillableAmounts = _.fromPairs( - nativeOrders.map(o => { - return [_nativeDataToId(o), o.fillableTakerAmount]; - }), - ); - // map sources delivered - sourcesDelivered = liquidityDelivered.map(collapsedFill => { - if (_isNativeOrderFromCollapsedFill(collapsedFill)) { - return nativeOrderToReportEntry( - collapsedFill.type, - collapsedFill.fillData, - nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)], - comparisonPrice, - quoteRequestor, - ); - } else { - return dexSampleToReportSource(collapsedFill, marketOperation); - } - }); - } else { - sourcesDelivered = [ - // tslint:disable-next-line: no-unnecessary-type-assertion - multiHopSampleToReportSource(liquidityDelivered as DexSample, marketOperation), - ]; - } - return { - sourcesConsidered, - sourcesDelivered, - }; + throw new Error(`Not implemented`); + // const nativeOrderSourcesConsidered = nativeOrders.map(order => + // nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor), + // ); + // const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)]; + // + // let sourcesDelivered; + // if (Array.isArray(liquidityDelivered)) { + // // create easy way to look up fillable amounts + // const nativeOrderSignaturesToFillableAmounts = _.fromPairs( + // nativeOrders.map(o => { + // return [_nativeDataToId(o), o.fillableTakerAmount]; + // }), + // ); + // // map sources delivered + // sourcesDelivered = liquidityDelivered.map(collapsedFill => { + // if (_isNativeOrderFromCollapsedFill(collapsedFill)) { + // return nativeOrderToReportEntry( + // collapsedFill.type, + // collapsedFill.fillData, + // nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)], + // comparisonPrice, + // quoteRequestor, + // ); + // } else { + // return dexSampleToReportSource(collapsedFill, marketOperation); + // } + // }); + // } else { + // sourcesDelivered = [ + // // tslint:disable-next-line: no-unnecessary-type-assertion + // multiHopSampleToReportSource(liquidityDelivered as DexSample, marketOperation), + // ]; + // } + // return { + // sourcesConsidered, + // sourcesDelivered, + // }; } function _nativeDataToId(data: { signature: Signature }): string { @@ -126,31 +127,32 @@ function _nativeDataToId(data: { signature: Signature }): string { * NOTE: this is used for the QuoteReport and quote price comparison data */ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeQuoteReportEntry { - const liquiditySource = ds.source; - - if (liquiditySource === ERC20BridgeSource.Native) { - throw new Error(`Unexpected liquidity source Native`); - } - - // input and output map to different values - // based on the market operation - if (marketOperation === MarketOperation.Buy) { - return { - makerAmount: ds.input, - takerAmount: ds.output, - liquiditySource, - fillData: ds.fillData, - }; - } else if (marketOperation === MarketOperation.Sell) { - return { - makerAmount: ds.output, - takerAmount: ds.input, - liquiditySource, - fillData: ds.fillData, - }; - } else { - throw new Error(`Unexpected marketOperation ${marketOperation}`); - } + throw new Error(`Not implemented`); + // const liquiditySource = ds.source; + // + // if (liquiditySource === ERC20BridgeSource.Native) { + // throw new Error(`Unexpected liquidity source Native`); + // } + // + // // input and output map to different values + // // based on the market operation + // if (marketOperation === MarketOperation.Buy) { + // return { + // makerAmount: ds.input, + // takerAmount: ds.output, + // liquiditySource, + // fillData: ds.fillData, + // }; + // } else if (marketOperation === MarketOperation.Sell) { + // return { + // makerAmount: ds.output, + // takerAmount: ds.input, + // liquiditySource, + // fillData: ds.fillData, + // }; + // } else { + // throw new Error(`Unexpected marketOperation ${marketOperation}`); + // } } /** @@ -158,31 +160,32 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp * NOTE: this is used for the QuoteReport and quote price comparison data */ export function multiHopSampleToReportSource( - ds: DexSample, + ds: DexSample, marketOperation: MarketOperation, ): MultiHopQuoteReportEntry { - const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData; - // input and output map to different values - // based on the market operation - if (marketOperation === MarketOperation.Buy) { - return { - liquiditySource: ERC20BridgeSource.MultiHop, - makerAmount: ds.input, - takerAmount: ds.output, - fillData: ds.fillData, - hopSources: [firstHop.source, secondHop.source], - }; - } else if (marketOperation === MarketOperation.Sell) { - return { - liquiditySource: ERC20BridgeSource.MultiHop, - makerAmount: ds.output, - takerAmount: ds.input, - fillData: ds.fillData, - hopSources: [firstHop.source, secondHop.source], - }; - } else { - throw new Error(`Unexpected marketOperation ${marketOperation}`); - } + throw new Error(`Not implemented`); + // const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData; + // // input and output map to different values + // // based on the market operation + // if (marketOperation === MarketOperation.Buy) { + // return { + // liquiditySource: ERC20BridgeSource.MultiHop, + // makerAmount: ds.input, + // takerAmount: ds.output, + // fillData: ds.fillData, + // hopSources: [firstHop.source, secondHop.source], + // }; + // } else if (marketOperation === MarketOperation.Sell) { + // return { + // liquiditySource: ERC20BridgeSource.MultiHop, + // makerAmount: ds.output, + // takerAmount: ds.input, + // fillData: ds.fillData, + // hopSources: [firstHop.source, secondHop.source], + // }; + // } else { + // throw new Error(`Unexpected marketOperation ${marketOperation}`); + // } } function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollapsedFill { diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index d203670ad9..6a1a8a81bd 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -282,7 +282,7 @@ export class QuoteRequestor { private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string }, private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER, private readonly _infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER, - private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs, + private readonly _expiryBufferMs: number = 120e3, private readonly _metrics?: MetricsProxy, ) { rfqMakerBlacklist.infoLogger = this._infoLogger; diff --git a/packages/asset-swapper/src/utils/quote_simulation.ts b/packages/asset-swapper/src/utils/quote_simulation.ts index 231ea2e832..764537d256 100644 --- a/packages/asset-swapper/src/utils/quote_simulation.ts +++ b/packages/asset-swapper/src/utils/quote_simulation.ts @@ -155,8 +155,8 @@ export function fillQuoteOrders( if (remainingInput.lte(0)) { break; } - const { source, fillData } = fill; - const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData); + const { source, encodedFillData } = fill; + const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(encodedFillData); result.gas += new BigNumber(gas).toNumber(); result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 83a7c21009..021c98cf76 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig", - "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, - "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true, "lib": ["es2019"] }, + "include": ["./src/**/*", "./generated-wrappers/**/*"], "files": [ "generated-artifacts/BalanceChecker.json", "generated-artifacts/ERC20BridgeSampler.json", diff --git a/yarn.lock b/yarn.lock index bbd1eb8fec..6e20d177f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2607,6 +2607,16 @@ dependencies: "@types/node" ">= 8" +"@open-rpc/client-js@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@open-rpc/client-js/-/client-js-1.7.1.tgz#763d75c046a40f57428b861e16a9a69aaa630cb1" + integrity sha512-DycSYZUGSUwFl+k9T8wLBSGA8f2hYkvS5A9AB94tBOuU8QlP468NS5ZtAxy72dF4g2WW0genwNJdfeFnHnaxXQ== + dependencies: + isomorphic-fetch "^3.0.0" + isomorphic-ws "^4.0.1" + strict-event-emitter-types "^2.0.0" + ws "^7.0.0" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -7969,6 +7979,11 @@ isomorphic-fetch@^3.0.0: node-fetch "^2.6.1" whatwg-fetch "^3.4.1" +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + isstream@0.1.x, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -11657,6 +11672,11 @@ stream-to-pull-stream@^1.7.1: looper "^3.0.0" pull-stream "^3.2.3" +strict-event-emitter-types@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f" + integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -13565,6 +13585,11 @@ ws@^5.1.1: dependencies: async-limiter "~1.0.0" +ws@^7.0.0: + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + wsrun@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/wsrun/-/wsrun-5.2.4.tgz#6eb6c3ccd3327721a8df073a5e3578fb0dea494e"