From 689a8881c2fa7f3d448936e2547176ee38ce9767 Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Tue, 27 Oct 2020 12:56:26 -0700 Subject: [PATCH] fix: Changed price-aware RFQ flag to be a argument parameter (#13) * Changed price-aware RFQ flag to be a argument parameter * prettified tests * lint --- packages/asset-swapper/src/constants.ts | 1 + packages/asset-swapper/src/swap_quoter.ts | 4 +- packages/asset-swapper/src/types.ts | 11 + .../src/utils/market_operation_utils/index.ts | 9 +- .../test/market_operation_utils_test.ts | 653 ++++++++---------- 5 files changed, 321 insertions(+), 357 deletions(-) diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index 3fd5145b37..b2c7fbd98e 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -89,6 +89,7 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = { const DEFAULT_RFQT_REQUEST_OPTS: Partial = { makerEndpointMaxResponseTimeMs: 1000, + isPriceAwareRFQEnabled: false, }; export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) => diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 9e18f33265..21420ada9f 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -8,7 +8,7 @@ import { BlockParamLiteral, SupportedProvider, ZeroExProvider } from 'ethereum-t import * as _ from 'lodash'; import { artifacts } from './artifacts'; -import { BRIDGE_ADDRESSES_BY_CHAIN, constants, IS_PRICE_AWARE_RFQ_ENABLED } from './constants'; +import { BRIDGE_ADDRESSES_BY_CHAIN, constants } from './constants'; import { AssetSwapperContractAddresses, CalculateSwapQuoteOpts, @@ -701,8 +701,8 @@ export class SwapQuoter { } if ( - !IS_PRICE_AWARE_RFQ_ENABLED && // Price-aware RFQ is disabled. opts.rfqt && // This is an RFQT-enabled API request + !opts.rfqt.isPriceAwareRFQEnabled && // If Price-aware RFQ is enabled, firm quotes are requested later on in the process. opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote opts.rfqt.apiKey && this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index bb56d54c59..0ba490a9a7 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -251,6 +251,17 @@ export interface RfqtRequestOpts { isIndicative?: boolean; makerEndpointMaxResponseTimeMs?: number; nativeExclusivelyRFQT?: boolean; + + /** + * This feature flag allows us to merge the price-aware RFQ pricing + * project while still controlling when to activate the feature. We plan to do some + * data analysis work and address some of the issues with maker fillable amounts + * in later milestones. Once the feature is fully rolled out and is providing value + * and we have assessed that there is no user impact, we will proceed in cleaning up + * the feature flag. When that time comes, follow this PR to "undo" the feature flag: + * https://github.com/0xProject/0x-monorepo/pull/2735 + */ + isPriceAwareRFQEnabled?: boolean; } /** 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 c2f75f6152..a7eee55a98 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -4,7 +4,6 @@ import { BigNumber, NULL_ADDRESS } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { IS_PRICE_AWARE_RFQ_ENABLED } from '../../constants'; import { AssetSwapperContractAddresses, MarketOperation, Omit } from '../../types'; import { QuoteRequestor } from '../quote_requestor'; @@ -216,8 +215,9 @@ export class MarketOperationUtils { ), ); + const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled; const rfqtPromise = - !IS_PRICE_AWARE_RFQ_ENABLED && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) + !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) ? getRfqtIndicativeQuotesAsync( nativeOrders[0].makerAssetData, nativeOrders[0].takerAssetData, @@ -364,8 +364,9 @@ export class MarketOperationUtils { this._liquidityProviderRegistry, ), ); + const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled; const rfqtPromise = - !IS_PRICE_AWARE_RFQ_ENABLED && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) + !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) ? getRfqtIndicativeQuotesAsync( nativeOrders[0].makerAssetData, nativeOrders[0].takerAssetData, @@ -677,8 +678,8 @@ export class MarketOperationUtils { // If RFQ liquidity is enabled, make a request to check RFQ liquidity const { rfqt } = _opts; if ( - IS_PRICE_AWARE_RFQ_ENABLED && rfqt && + rfqt.isPriceAwareRFQEnabled && rfqt.quoteRequestor && marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) ) { diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 4c2ba7e68d..2eb39e041f 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -17,7 +17,6 @@ import * as _ from 'lodash'; import * as TypeMoq from 'typemoq'; import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src'; -import { IS_PRICE_AWARE_RFQ_ENABLED } from '../src/constants'; import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/'; import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils'; import { @@ -746,379 +745,331 @@ describe('MarketOperationUtils tests', () => { } }); - it( - 'getMarketSellOrdersAsync() optimizer will be called once only if RFQ if not defined', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; + it('getMarketSellOrdersAsync() optimizer will be called once only if price-aware RFQ is disabled', async () => { + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; - // Ensure that `_generateOptimizedOrdersAsync` is only called once - mockedMarketOpUtils - .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) - .verifiable(TypeMoq.Times.once()); + // Ensure that `_generateOptimizedOrdersAsync` is only called once + mockedMarketOpUtils + .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) + .verifiable(TypeMoq.Times.once()); - const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); - await mockedMarketOpUtils.object.getMarketSellOrdersAsync( - ORDERS, - totalAssetAmount, - DEFAULT_OPTS, - ); - mockedMarketOpUtils.verifyAll(); - } - : undefined, - ); + const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); + await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, DEFAULT_OPTS); + mockedMarketOpUtils.verifyAll(); + }); - it( - 'optimizer will send in a comparison price to RFQ providers', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - // Set up mocked quote requestor, will return an order that is better - // than the best of the orders. - const mockedQuoteRequestor = TypeMoq.Mock.ofType( - QuoteRequestor, - TypeMoq.MockBehavior.Loose, - false, - {}, - ); + it('optimizer will send in a comparison price to RFQ providers', async () => { + // Set up mocked quote requestor, will return an order that is better + // than the best of the orders. + const mockedQuoteRequestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose, false, {}); - let requestedComparisonPrice: BigNumber | undefined; - mockedQuoteRequestor - .setup(mqr => - mqr.requestRfqtFirmQuotesAsync( - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - ), - ) - .callback( - ( - _makerAssetData: string, - _takerAssetData: string, - _assetFillAmount: BigNumber, - _marketOperation: MarketOperation, - comparisonPrice: BigNumber | undefined, - _options: RfqtRequestOpts, - ) => { - requestedComparisonPrice = comparisonPrice; - }, - ) - .returns(async () => { - return [ - { - signedOrder: createOrder({ - makerAssetData: MAKER_ASSET_DATA, - takerAssetData: TAKER_ASSET_DATA, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 6), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), - }), - }, - ]; - }); + let requestedComparisonPrice: BigNumber | undefined; + mockedQuoteRequestor + .setup(mqr => + mqr.requestRfqtFirmQuotesAsync( + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + ), + ) + .callback( + ( + _makerAssetData: string, + _takerAssetData: string, + _assetFillAmount: BigNumber, + _marketOperation: MarketOperation, + comparisonPrice: BigNumber | undefined, + _options: RfqtRequestOpts, + ) => { + requestedComparisonPrice = comparisonPrice; + }, + ) + .returns(async () => { + return [ + { + signedOrder: createOrder({ + makerAssetData: MAKER_ASSET_DATA, + takerAssetData: TAKER_ASSET_DATA, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 6), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), + }), + }, + ]; + }); - // Set up sampler, will only return 1 on-chain order - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; - mockedMarketOpUtils - .setup(mou => - mou.getMarketSellLiquidityAsync( - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - ), - ) - .returns(async () => { - return { - dexQuotes: [], - ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18), - ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6), - inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18), - inputToken: MAKER_TOKEN, - outputToken: TAKER_TOKEN, - nativeOrders: [ - createOrder({ - makerAssetData: MAKER_ASSET_DATA, - takerAssetData: TAKER_ASSET_DATA, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 6), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), - }), - ], - orderFillableAmounts: [Web3Wrapper.toBaseUnitAmount(1, 18)], - rfqtIndicativeQuotes: [], - side: MarketOperation.Sell, - twoHopQuotes: [], - quoteSourceFilters: new SourceFilters(), - makerTokenDecimals: 6, - takerTokenDecimals: 18, - }; - }); - const result = await mockedMarketOpUtils.object.getMarketSellOrdersAsync( - ORDERS, - Web3Wrapper.toBaseUnitAmount(1, 18), - { - ...DEFAULT_OPTS, - rfqt: { - isIndicative: false, - apiKey: 'foo', - takerAddress: randomAddress(), - intentOnFilling: true, - quoteRequestor: { - requestRfqtFirmQuotesAsync: - mockedQuoteRequestor.object.requestRfqtFirmQuotesAsync, - } as any, - }, - }, - ); - expect(result.optimizedOrders.length).to.eql(1); - // tslint:disable-next-line:no-unnecessary-type-assertion - expect(requestedComparisonPrice!.toString()).to.eql('320'); - expect(result.optimizedOrders[0].makerAssetAmount.toString()).to.eql('321000000'); - expect(result.optimizedOrders[0].takerAssetAmount.toString()).to.eql('1000000000000000000'); - } - : undefined, - ); + // Set up sampler, will only return 1 on-chain order + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; + mockedMarketOpUtils + .setup(mou => + mou.getMarketSellLiquidityAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), + ) + .returns(async () => { + return { + dexQuotes: [], + ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18), + ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6), + inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18), + inputToken: MAKER_TOKEN, + outputToken: TAKER_TOKEN, + nativeOrders: [ + createOrder({ + makerAssetData: MAKER_ASSET_DATA, + takerAssetData: TAKER_ASSET_DATA, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 6), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), + }), + ], + orderFillableAmounts: [Web3Wrapper.toBaseUnitAmount(1, 18)], + rfqtIndicativeQuotes: [], + side: MarketOperation.Sell, + twoHopQuotes: [], + quoteSourceFilters: new SourceFilters(), + makerTokenDecimals: 6, + takerTokenDecimals: 18, + }; + }); + const result = await mockedMarketOpUtils.object.getMarketSellOrdersAsync( + ORDERS, + Web3Wrapper.toBaseUnitAmount(1, 18), + { + ...DEFAULT_OPTS, + rfqt: { + isIndicative: false, + apiKey: 'foo', + takerAddress: randomAddress(), + intentOnFilling: true, + isPriceAwareRFQEnabled: true, + quoteRequestor: { + requestRfqtFirmQuotesAsync: mockedQuoteRequestor.object.requestRfqtFirmQuotesAsync, + } as any, + }, + }, + ); + expect(result.optimizedOrders.length).to.eql(1); + // tslint:disable-next-line:no-unnecessary-type-assertion + expect(requestedComparisonPrice!.toString()).to.eql('320'); + expect(result.optimizedOrders[0].makerAssetAmount.toString()).to.eql('321000000'); + expect(result.optimizedOrders[0].takerAssetAmount.toString()).to.eql('1000000000000000000'); + }); - it( - 'getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - // Ensure that `_generateOptimizedOrdersAsync` is only called once - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; - mockedMarketOpUtils - .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) - .verifiable(TypeMoq.Times.once()); + it('getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', async () => { + // Ensure that `_generateOptimizedOrdersAsync` is only called once + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; + mockedMarketOpUtils + .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) + .verifiable(TypeMoq.Times.once()); - const requestor = getMockedQuoteRequestor('firm', [], TypeMoq.Times.once()); + const requestor = getMockedQuoteRequestor('firm', [], TypeMoq.Times.once()); - const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); - await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, { - ...DEFAULT_OPTS, - rfqt: { - isIndicative: false, - apiKey: 'foo', - takerAddress: randomAddress(), - intentOnFilling: true, - quoteRequestor: { - requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, - } as any, - }, - }); - mockedMarketOpUtils.verifyAll(); - requestor.verifyAll(); - } - : undefined, - ); + const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); + await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, { + ...DEFAULT_OPTS, + rfqt: { + isIndicative: false, + apiKey: 'foo', + takerAddress: randomAddress(), + intentOnFilling: true, + isPriceAwareRFQEnabled: true, + quoteRequestor: { + requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, + } as any, + }, + }); + mockedMarketOpUtils.verifyAll(); + requestor.verifyAll(); + }); - it( - 'getMarketSellOrdersAsync() will rerun the optimizer if one or more indicative are returned', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - const requestor = getMockedQuoteRequestor( - 'indicative', - [ORDERS[0], ORDERS[1]], - TypeMoq.Times.once(), - ); + it('getMarketSellOrdersAsync() will rerun the optimizer if one or more indicative are returned', async () => { + const requestor = getMockedQuoteRequestor('indicative', [ORDERS[0], ORDERS[1]], TypeMoq.Times.once()); - const numOrdersInCall: number[] = []; - const numIndicativeQuotesInCall: number[] = []; + const numOrdersInCall: number[] = []; + const numIndicativeQuotesInCall: number[] = []; - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; - mockedMarketOpUtils - .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { - numOrdersInCall.push(msl.nativeOrders.length); - numIndicativeQuotesInCall.push(msl.rfqtIndicativeQuotes.length); - }) - .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) - .verifiable(TypeMoq.Times.exactly(2)); + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; + mockedMarketOpUtils + .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { + numOrdersInCall.push(msl.nativeOrders.length); + numIndicativeQuotesInCall.push(msl.rfqtIndicativeQuotes.length); + }) + .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) + .verifiable(TypeMoq.Times.exactly(2)); - const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); - await mockedMarketOpUtils.object.getMarketSellOrdersAsync( - ORDERS.slice(2, ORDERS.length), - totalAssetAmount, - { - ...DEFAULT_OPTS, - rfqt: { - isIndicative: true, - apiKey: 'foo', - takerAddress: randomAddress(), - intentOnFilling: true, - quoteRequestor: { - requestRfqtIndicativeQuotesAsync: - requestor.object.requestRfqtIndicativeQuotesAsync, - } as any, - }, - }, - ); - mockedMarketOpUtils.verifyAll(); - requestor.verifyAll(); + const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); + await mockedMarketOpUtils.object.getMarketSellOrdersAsync( + ORDERS.slice(2, ORDERS.length), + totalAssetAmount, + { + ...DEFAULT_OPTS, + rfqt: { + isIndicative: true, + apiKey: 'foo', + isPriceAwareRFQEnabled: true, + takerAddress: randomAddress(), + intentOnFilling: true, + quoteRequestor: { + requestRfqtIndicativeQuotesAsync: requestor.object.requestRfqtIndicativeQuotesAsync, + } as any, + }, + }, + ); + mockedMarketOpUtils.verifyAll(); + requestor.verifyAll(); - // The first and second optimizer call contains same number of RFQ orders. - expect(numOrdersInCall.length).to.eql(2); - expect(numOrdersInCall[0]).to.eql(1); - expect(numOrdersInCall[1]).to.eql(1); + // The first and second optimizer call contains same number of RFQ orders. + expect(numOrdersInCall.length).to.eql(2); + expect(numOrdersInCall[0]).to.eql(1); + expect(numOrdersInCall[1]).to.eql(1); - // The first call to optimizer will have no RFQ indicative quotes. The second call will have - // two indicative quotes. - expect(numIndicativeQuotesInCall.length).to.eql(2); - expect(numIndicativeQuotesInCall[0]).to.eql(0); - expect(numIndicativeQuotesInCall[1]).to.eql(2); - } - : undefined, - ); + // The first call to optimizer will have no RFQ indicative quotes. The second call will have + // two indicative quotes. + expect(numIndicativeQuotesInCall.length).to.eql(2); + expect(numIndicativeQuotesInCall[0]).to.eql(0); + expect(numIndicativeQuotesInCall[1]).to.eql(2); + }); - it( - 'getMarketSellOrdersAsync() will rerun the optimizer if one or more RFQ orders are returned', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - const requestor = getMockedQuoteRequestor('firm', [ORDERS[0]], TypeMoq.Times.once()); + it('getMarketSellOrdersAsync() will rerun the optimizer if one or more RFQ orders are returned', async () => { + const requestor = getMockedQuoteRequestor('firm', [ORDERS[0]], TypeMoq.Times.once()); - // Ensure that `_generateOptimizedOrdersAsync` is only called once + // Ensure that `_generateOptimizedOrdersAsync` is only called once - // TODO: Ensure fillable amounts increase too - const numOrdersInCall: number[] = []; - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; - mockedMarketOpUtils - .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { - numOrdersInCall.push(msl.nativeOrders.length); - }) - .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) - .verifiable(TypeMoq.Times.exactly(2)); + // TODO: Ensure fillable amounts increase too + const numOrdersInCall: number[] = []; + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; + mockedMarketOpUtils + .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { + numOrdersInCall.push(msl.nativeOrders.length); + }) + .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) + .verifiable(TypeMoq.Times.exactly(2)); - const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); - await mockedMarketOpUtils.object.getMarketSellOrdersAsync( - ORDERS.slice(1, ORDERS.length), - totalAssetAmount, - { - ...DEFAULT_OPTS, - rfqt: { - isIndicative: false, - apiKey: 'foo', - takerAddress: randomAddress(), - intentOnFilling: true, - quoteRequestor: { - requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, - } as any, - }, - }, - ); - mockedMarketOpUtils.verifyAll(); - requestor.verifyAll(); - expect(numOrdersInCall.length).to.eql(2); + const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); + await mockedMarketOpUtils.object.getMarketSellOrdersAsync( + ORDERS.slice(1, ORDERS.length), + totalAssetAmount, + { + ...DEFAULT_OPTS, + rfqt: { + isIndicative: false, + apiKey: 'foo', + takerAddress: randomAddress(), + intentOnFilling: true, + isPriceAwareRFQEnabled: true, + quoteRequestor: { + requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, + } as any, + }, + }, + ); + mockedMarketOpUtils.verifyAll(); + requestor.verifyAll(); + expect(numOrdersInCall.length).to.eql(2); - // The first call to optimizer was without an RFQ order. - // The first call to optimizer was with an extra RFQ order. - expect(numOrdersInCall[0]).to.eql(2); - expect(numOrdersInCall[1]).to.eql(3); - } - : undefined, - ); + // The first call to optimizer was without an RFQ order. + // The first call to optimizer was with an extra RFQ order. + expect(numOrdersInCall[0]).to.eql(2); + expect(numOrdersInCall[1]).to.eql(3); + }); - it( - 'getMarketSellOrdersAsync() will not raise a NoOptimalPath error if no initial path was found during on-chain DEX optimization, but a path was found after RFQ optimization', - IS_PRICE_AWARE_RFQ_ENABLED - ? async () => { - let hasFirstOptimizationRun = false; - let hasSecondOptimizationRun = false; - const requestor = getMockedQuoteRequestor( - 'firm', - [ORDERS[0], ORDERS[1]], - TypeMoq.Times.once(), - ); + it('getMarketSellOrdersAsync() will not raise a NoOptimalPath error if no initial path was found during on-chain DEX optimization, but a path was found after RFQ optimization', async () => { + let hasFirstOptimizationRun = false; + let hasSecondOptimizationRun = false; + const requestor = getMockedQuoteRequestor('firm', [ORDERS[0], ORDERS[1]], TypeMoq.Times.once()); - const mockedMarketOpUtils = TypeMoq.Mock.ofType( - MarketOperationUtils, - TypeMoq.MockBehavior.Loose, - false, - MOCK_SAMPLER, - contractAddresses, - ORDER_DOMAIN, - ); - mockedMarketOpUtils.callBase = true; - mockedMarketOpUtils - .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { - if (msl.nativeOrders.length === 1) { - hasFirstOptimizationRun = true; - throw new Error(AggregationError.NoOptimalPath); - } else if (msl.nativeOrders.length === 3) { - hasSecondOptimizationRun = true; - return mockedMarketOpUtils.target._generateOptimizedOrdersAsync(msl, _opts); - } else { - throw new Error('Invalid path. this error message should never appear'); - } - }) - .verifiable(TypeMoq.Times.exactly(2)); + const mockedMarketOpUtils = TypeMoq.Mock.ofType( + MarketOperationUtils, + TypeMoq.MockBehavior.Loose, + false, + MOCK_SAMPLER, + contractAddresses, + ORDER_DOMAIN, + ); + mockedMarketOpUtils.callBase = true; + mockedMarketOpUtils + .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { + if (msl.nativeOrders.length === 1) { + hasFirstOptimizationRun = true; + throw new Error(AggregationError.NoOptimalPath); + } else if (msl.nativeOrders.length === 3) { + hasSecondOptimizationRun = true; + return mockedMarketOpUtils.target._generateOptimizedOrdersAsync(msl, _opts); + } else { + throw new Error('Invalid path. this error message should never appear'); + } + }) + .verifiable(TypeMoq.Times.exactly(2)); - const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); - await mockedMarketOpUtils.object.getMarketSellOrdersAsync( - ORDERS.slice(2, ORDERS.length), - totalAssetAmount, - { - ...DEFAULT_OPTS, - rfqt: { - isIndicative: false, - apiKey: 'foo', - takerAddress: randomAddress(), - intentOnFilling: true, - quoteRequestor: { - requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, - } as any, - }, - }, - ); - mockedMarketOpUtils.verifyAll(); - requestor.verifyAll(); + const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); + await mockedMarketOpUtils.object.getMarketSellOrdersAsync( + ORDERS.slice(2, ORDERS.length), + totalAssetAmount, + { + ...DEFAULT_OPTS, + rfqt: { + isIndicative: false, + apiKey: 'foo', + takerAddress: randomAddress(), + isPriceAwareRFQEnabled: true, + intentOnFilling: true, + quoteRequestor: { + requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, + } as any, + }, + }, + ); + mockedMarketOpUtils.verifyAll(); + requestor.verifyAll(); - expect(hasFirstOptimizationRun).to.eql(true); - expect(hasSecondOptimizationRun).to.eql(true); - } - : undefined, - ); + expect(hasFirstOptimizationRun).to.eql(true); + expect(hasSecondOptimizationRun).to.eql(true); + }); it('getMarketSellOrdersAsync() will raise a NoOptimalPath error if no path was found during on-chain DEX optimization and RFQ optimization', async () => { const mockedMarketOpUtils = TypeMoq.Mock.ofType(