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
This commit is contained in:
Daniel Pyrathon 2020-10-27 12:56:26 -07:00 committed by GitHub
parent 99f5be8378
commit 689a8881c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 321 additions and 357 deletions

View File

@ -89,6 +89,7 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqtRequestOpts> = { const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqtRequestOpts> = {
makerEndpointMaxResponseTimeMs: 1000, makerEndpointMaxResponseTimeMs: 1000,
isPriceAwareRFQEnabled: false,
}; };
export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) => export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) =>

View File

@ -8,7 +8,7 @@ import { BlockParamLiteral, SupportedProvider, ZeroExProvider } from 'ethereum-t
import * as _ from 'lodash'; import * as _ from 'lodash';
import { artifacts } from './artifacts'; 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 { import {
AssetSwapperContractAddresses, AssetSwapperContractAddresses,
CalculateSwapQuoteOpts, CalculateSwapQuoteOpts,
@ -701,8 +701,8 @@ export class SwapQuoter {
} }
if ( if (
!IS_PRICE_AWARE_RFQ_ENABLED && // Price-aware RFQ is disabled.
opts.rfqt && // This is an RFQT-enabled API request 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.intentOnFilling && // The requestor is asking for a firm quote
opts.rfqt.apiKey && opts.rfqt.apiKey &&
this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided

View File

@ -251,6 +251,17 @@ export interface RfqtRequestOpts {
isIndicative?: boolean; isIndicative?: boolean;
makerEndpointMaxResponseTimeMs?: number; makerEndpointMaxResponseTimeMs?: number;
nativeExclusivelyRFQT?: boolean; 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;
} }
/** /**

View File

@ -4,7 +4,6 @@ import { BigNumber, NULL_ADDRESS } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper'; import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { IS_PRICE_AWARE_RFQ_ENABLED } from '../../constants';
import { AssetSwapperContractAddresses, MarketOperation, Omit } from '../../types'; import { AssetSwapperContractAddresses, MarketOperation, Omit } from '../../types';
import { QuoteRequestor } from '../quote_requestor'; import { QuoteRequestor } from '../quote_requestor';
@ -216,8 +215,9 @@ export class MarketOperationUtils {
), ),
); );
const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled;
const rfqtPromise = const rfqtPromise =
!IS_PRICE_AWARE_RFQ_ENABLED && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
? getRfqtIndicativeQuotesAsync( ? getRfqtIndicativeQuotesAsync(
nativeOrders[0].makerAssetData, nativeOrders[0].makerAssetData,
nativeOrders[0].takerAssetData, nativeOrders[0].takerAssetData,
@ -364,8 +364,9 @@ export class MarketOperationUtils {
this._liquidityProviderRegistry, this._liquidityProviderRegistry,
), ),
); );
const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled;
const rfqtPromise = const rfqtPromise =
!IS_PRICE_AWARE_RFQ_ENABLED && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
? getRfqtIndicativeQuotesAsync( ? getRfqtIndicativeQuotesAsync(
nativeOrders[0].makerAssetData, nativeOrders[0].makerAssetData,
nativeOrders[0].takerAssetData, nativeOrders[0].takerAssetData,
@ -677,8 +678,8 @@ export class MarketOperationUtils {
// If RFQ liquidity is enabled, make a request to check RFQ liquidity // If RFQ liquidity is enabled, make a request to check RFQ liquidity
const { rfqt } = _opts; const { rfqt } = _opts;
if ( if (
IS_PRICE_AWARE_RFQ_ENABLED &&
rfqt && rfqt &&
rfqt.isPriceAwareRFQEnabled &&
rfqt.quoteRequestor && rfqt.quoteRequestor &&
marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
) { ) {

View File

@ -17,7 +17,6 @@ import * as _ from 'lodash';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src'; 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 { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/';
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils'; import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
import { import {
@ -746,379 +745,331 @@ describe('MarketOperationUtils tests', () => {
} }
}); });
it( it('getMarketSellOrdersAsync() optimizer will be called once only if price-aware RFQ is disabled', async () => {
'getMarketSellOrdersAsync() optimizer will be called once only if RFQ if not defined', const mockedMarketOpUtils = TypeMoq.Mock.ofType(
IS_PRICE_AWARE_RFQ_ENABLED MarketOperationUtils,
? async () => { TypeMoq.MockBehavior.Loose,
const mockedMarketOpUtils = TypeMoq.Mock.ofType( false,
MarketOperationUtils, MOCK_SAMPLER,
TypeMoq.MockBehavior.Loose, contractAddresses,
false, ORDER_DOMAIN,
MOCK_SAMPLER, );
contractAddresses, mockedMarketOpUtils.callBase = true;
ORDER_DOMAIN,
);
mockedMarketOpUtils.callBase = true;
// Ensure that `_generateOptimizedOrdersAsync` is only called once // Ensure that `_generateOptimizedOrdersAsync` is only called once
mockedMarketOpUtils mockedMarketOpUtils
.setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b))
.verifiable(TypeMoq.Times.once()); .verifiable(TypeMoq.Times.once());
const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b));
await mockedMarketOpUtils.object.getMarketSellOrdersAsync( await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, DEFAULT_OPTS);
ORDERS, mockedMarketOpUtils.verifyAll();
totalAssetAmount, });
DEFAULT_OPTS,
);
mockedMarketOpUtils.verifyAll();
}
: undefined,
);
it( it('optimizer will send in a comparison price to RFQ providers', async () => {
'optimizer will send in a comparison price to RFQ providers', // Set up mocked quote requestor, will return an order that is better
IS_PRICE_AWARE_RFQ_ENABLED // than the best of the orders.
? async () => { const mockedQuoteRequestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose, false, {});
// 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; let requestedComparisonPrice: BigNumber | undefined;
mockedQuoteRequestor mockedQuoteRequestor
.setup(mqr => .setup(mqr =>
mqr.requestRfqtFirmQuotesAsync( mqr.requestRfqtFirmQuotesAsync(
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
TypeMoq.It.isAny(), TypeMoq.It.isAny(),
), ),
) )
.callback( .callback(
( (
_makerAssetData: string, _makerAssetData: string,
_takerAssetData: string, _takerAssetData: string,
_assetFillAmount: BigNumber, _assetFillAmount: BigNumber,
_marketOperation: MarketOperation, _marketOperation: MarketOperation,
comparisonPrice: BigNumber | undefined, comparisonPrice: BigNumber | undefined,
_options: RfqtRequestOpts, _options: RfqtRequestOpts,
) => { ) => {
requestedComparisonPrice = comparisonPrice; requestedComparisonPrice = comparisonPrice;
}, },
) )
.returns(async () => { .returns(async () => {
return [ return [
{ {
signedOrder: createOrder({ signedOrder: createOrder({
makerAssetData: MAKER_ASSET_DATA, makerAssetData: MAKER_ASSET_DATA,
takerAssetData: TAKER_ASSET_DATA, takerAssetData: TAKER_ASSET_DATA,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 6), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 6),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
}), }),
}, },
]; ];
}); });
// Set up sampler, will only return 1 on-chain order // Set up sampler, will only return 1 on-chain order
const mockedMarketOpUtils = TypeMoq.Mock.ofType( const mockedMarketOpUtils = TypeMoq.Mock.ofType(
MarketOperationUtils, MarketOperationUtils,
TypeMoq.MockBehavior.Loose, TypeMoq.MockBehavior.Loose,
false, false,
MOCK_SAMPLER, MOCK_SAMPLER,
contractAddresses, contractAddresses,
ORDER_DOMAIN, ORDER_DOMAIN,
); );
mockedMarketOpUtils.callBase = true; mockedMarketOpUtils.callBase = true;
mockedMarketOpUtils mockedMarketOpUtils
.setup(mou => .setup(mou =>
mou.getMarketSellLiquidityAsync( mou.getMarketSellLiquidityAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()),
TypeMoq.It.isAny(), )
TypeMoq.It.isAny(), .returns(async () => {
TypeMoq.It.isAny(), return {
), dexQuotes: [],
) ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18),
.returns(async () => { ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6),
return { inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
dexQuotes: [], inputToken: MAKER_TOKEN,
ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18), outputToken: TAKER_TOKEN,
ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6), nativeOrders: [
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18), createOrder({
inputToken: MAKER_TOKEN, makerAssetData: MAKER_ASSET_DATA,
outputToken: TAKER_TOKEN, takerAssetData: TAKER_ASSET_DATA,
nativeOrders: [ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 6),
createOrder({ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
makerAssetData: MAKER_ASSET_DATA, }),
takerAssetData: TAKER_ASSET_DATA, ],
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 6), orderFillableAmounts: [Web3Wrapper.toBaseUnitAmount(1, 18)],
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18), rfqtIndicativeQuotes: [],
}), side: MarketOperation.Sell,
], twoHopQuotes: [],
orderFillableAmounts: [Web3Wrapper.toBaseUnitAmount(1, 18)], quoteSourceFilters: new SourceFilters(),
rfqtIndicativeQuotes: [], makerTokenDecimals: 6,
side: MarketOperation.Sell, takerTokenDecimals: 18,
twoHopQuotes: [], };
quoteSourceFilters: new SourceFilters(), });
makerTokenDecimals: 6, const result = await mockedMarketOpUtils.object.getMarketSellOrdersAsync(
takerTokenDecimals: 18, ORDERS,
}; Web3Wrapper.toBaseUnitAmount(1, 18),
}); {
const result = await mockedMarketOpUtils.object.getMarketSellOrdersAsync( ...DEFAULT_OPTS,
ORDERS, rfqt: {
Web3Wrapper.toBaseUnitAmount(1, 18), isIndicative: false,
{ apiKey: 'foo',
...DEFAULT_OPTS, takerAddress: randomAddress(),
rfqt: { intentOnFilling: true,
isIndicative: false, isPriceAwareRFQEnabled: true,
apiKey: 'foo', quoteRequestor: {
takerAddress: randomAddress(), requestRfqtFirmQuotesAsync: mockedQuoteRequestor.object.requestRfqtFirmQuotesAsync,
intentOnFilling: true, } as any,
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.length).to.eql(1); expect(result.optimizedOrders[0].takerAssetAmount.toString()).to.eql('1000000000000000000');
// 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,
);
it( it('getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', async () => {
'getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', // Ensure that `_generateOptimizedOrdersAsync` is only called once
IS_PRICE_AWARE_RFQ_ENABLED const mockedMarketOpUtils = TypeMoq.Mock.ofType(
? async () => { MarketOperationUtils,
// Ensure that `_generateOptimizedOrdersAsync` is only called once TypeMoq.MockBehavior.Loose,
const mockedMarketOpUtils = TypeMoq.Mock.ofType( false,
MarketOperationUtils, MOCK_SAMPLER,
TypeMoq.MockBehavior.Loose, contractAddresses,
false, ORDER_DOMAIN,
MOCK_SAMPLER, );
contractAddresses, mockedMarketOpUtils.callBase = true;
ORDER_DOMAIN, mockedMarketOpUtils
); .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
mockedMarketOpUtils.callBase = true; .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b))
mockedMarketOpUtils .verifiable(TypeMoq.Times.once());
.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)); const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b));
await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, { await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, totalAssetAmount, {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', apiKey: 'foo',
takerAddress: randomAddress(), takerAddress: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
quoteRequestor: { isPriceAwareRFQEnabled: true,
requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, quoteRequestor: {
} as any, requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync,
}, } as any,
}); },
mockedMarketOpUtils.verifyAll(); });
requestor.verifyAll(); mockedMarketOpUtils.verifyAll();
} requestor.verifyAll();
: undefined, });
);
it( it('getMarketSellOrdersAsync() will rerun the optimizer if one or more indicative are returned', async () => {
'getMarketSellOrdersAsync() will rerun the optimizer if one or more indicative are returned', const requestor = getMockedQuoteRequestor('indicative', [ORDERS[0], ORDERS[1]], TypeMoq.Times.once());
IS_PRICE_AWARE_RFQ_ENABLED
? async () => {
const requestor = getMockedQuoteRequestor(
'indicative',
[ORDERS[0], ORDERS[1]],
TypeMoq.Times.once(),
);
const numOrdersInCall: number[] = []; const numOrdersInCall: number[] = [];
const numIndicativeQuotesInCall: number[] = []; const numIndicativeQuotesInCall: number[] = [];
const mockedMarketOpUtils = TypeMoq.Mock.ofType( const mockedMarketOpUtils = TypeMoq.Mock.ofType(
MarketOperationUtils, MarketOperationUtils,
TypeMoq.MockBehavior.Loose, TypeMoq.MockBehavior.Loose,
false, false,
MOCK_SAMPLER, MOCK_SAMPLER,
contractAddresses, contractAddresses,
ORDER_DOMAIN, ORDER_DOMAIN,
); );
mockedMarketOpUtils.callBase = true; mockedMarketOpUtils.callBase = true;
mockedMarketOpUtils mockedMarketOpUtils
.setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => {
numOrdersInCall.push(msl.nativeOrders.length); numOrdersInCall.push(msl.nativeOrders.length);
numIndicativeQuotesInCall.push(msl.rfqtIndicativeQuotes.length); numIndicativeQuotesInCall.push(msl.rfqtIndicativeQuotes.length);
}) })
.returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b))
.verifiable(TypeMoq.Times.exactly(2)); .verifiable(TypeMoq.Times.exactly(2));
const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b));
await mockedMarketOpUtils.object.getMarketSellOrdersAsync( await mockedMarketOpUtils.object.getMarketSellOrdersAsync(
ORDERS.slice(2, ORDERS.length), ORDERS.slice(2, ORDERS.length),
totalAssetAmount, totalAssetAmount,
{ {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: true, isIndicative: true,
apiKey: 'foo', apiKey: 'foo',
takerAddress: randomAddress(), isPriceAwareRFQEnabled: true,
intentOnFilling: true, takerAddress: randomAddress(),
quoteRequestor: { intentOnFilling: true,
requestRfqtIndicativeQuotesAsync: quoteRequestor: {
requestor.object.requestRfqtIndicativeQuotesAsync, requestRfqtIndicativeQuotesAsync: requestor.object.requestRfqtIndicativeQuotesAsync,
} as any, } as any,
}, },
}, },
); );
mockedMarketOpUtils.verifyAll(); mockedMarketOpUtils.verifyAll();
requestor.verifyAll(); requestor.verifyAll();
// The first and second optimizer call contains same number of RFQ orders. // The first and second optimizer call contains same number of RFQ orders.
expect(numOrdersInCall.length).to.eql(2); expect(numOrdersInCall.length).to.eql(2);
expect(numOrdersInCall[0]).to.eql(1); expect(numOrdersInCall[0]).to.eql(1);
expect(numOrdersInCall[1]).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 // The first call to optimizer will have no RFQ indicative quotes. The second call will have
// two indicative quotes. // two indicative quotes.
expect(numIndicativeQuotesInCall.length).to.eql(2); expect(numIndicativeQuotesInCall.length).to.eql(2);
expect(numIndicativeQuotesInCall[0]).to.eql(0); expect(numIndicativeQuotesInCall[0]).to.eql(0);
expect(numIndicativeQuotesInCall[1]).to.eql(2); expect(numIndicativeQuotesInCall[1]).to.eql(2);
} });
: undefined,
);
it( it('getMarketSellOrdersAsync() will rerun the optimizer if one or more RFQ orders are returned', async () => {
'getMarketSellOrdersAsync() will rerun the optimizer if one or more RFQ orders are returned', const requestor = getMockedQuoteRequestor('firm', [ORDERS[0]], TypeMoq.Times.once());
IS_PRICE_AWARE_RFQ_ENABLED
? 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 // TODO: Ensure fillable amounts increase too
const numOrdersInCall: number[] = []; const numOrdersInCall: number[] = [];
const mockedMarketOpUtils = TypeMoq.Mock.ofType( const mockedMarketOpUtils = TypeMoq.Mock.ofType(
MarketOperationUtils, MarketOperationUtils,
TypeMoq.MockBehavior.Loose, TypeMoq.MockBehavior.Loose,
false, false,
MOCK_SAMPLER, MOCK_SAMPLER,
contractAddresses, contractAddresses,
ORDER_DOMAIN, ORDER_DOMAIN,
); );
mockedMarketOpUtils.callBase = true; mockedMarketOpUtils.callBase = true;
mockedMarketOpUtils mockedMarketOpUtils
.setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { .callback(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => {
numOrdersInCall.push(msl.nativeOrders.length); numOrdersInCall.push(msl.nativeOrders.length);
}) })
.returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b)) .returns(async (a, b) => mockedMarketOpUtils.target._generateOptimizedOrdersAsync(a, b))
.verifiable(TypeMoq.Times.exactly(2)); .verifiable(TypeMoq.Times.exactly(2));
const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b));
await mockedMarketOpUtils.object.getMarketSellOrdersAsync( await mockedMarketOpUtils.object.getMarketSellOrdersAsync(
ORDERS.slice(1, ORDERS.length), ORDERS.slice(1, ORDERS.length),
totalAssetAmount, totalAssetAmount,
{ {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', apiKey: 'foo',
takerAddress: randomAddress(), takerAddress: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
quoteRequestor: { isPriceAwareRFQEnabled: true,
requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, quoteRequestor: {
} as any, requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync,
}, } as any,
}, },
); },
mockedMarketOpUtils.verifyAll(); );
requestor.verifyAll(); mockedMarketOpUtils.verifyAll();
expect(numOrdersInCall.length).to.eql(2); requestor.verifyAll();
expect(numOrdersInCall.length).to.eql(2);
// The first call to optimizer was without an RFQ order. // The first call to optimizer was without an RFQ order.
// The first call to optimizer was with an extra RFQ order. // The first call to optimizer was with an extra RFQ order.
expect(numOrdersInCall[0]).to.eql(2); expect(numOrdersInCall[0]).to.eql(2);
expect(numOrdersInCall[1]).to.eql(3); expect(numOrdersInCall[1]).to.eql(3);
} });
: undefined,
);
it( 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 () => {
'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', let hasFirstOptimizationRun = false;
IS_PRICE_AWARE_RFQ_ENABLED let hasSecondOptimizationRun = false;
? async () => { const requestor = getMockedQuoteRequestor('firm', [ORDERS[0], ORDERS[1]], TypeMoq.Times.once());
let hasFirstOptimizationRun = false;
let hasSecondOptimizationRun = false;
const requestor = getMockedQuoteRequestor(
'firm',
[ORDERS[0], ORDERS[1]],
TypeMoq.Times.once(),
);
const mockedMarketOpUtils = TypeMoq.Mock.ofType( const mockedMarketOpUtils = TypeMoq.Mock.ofType(
MarketOperationUtils, MarketOperationUtils,
TypeMoq.MockBehavior.Loose, TypeMoq.MockBehavior.Loose,
false, false,
MOCK_SAMPLER, MOCK_SAMPLER,
contractAddresses, contractAddresses,
ORDER_DOMAIN, ORDER_DOMAIN,
); );
mockedMarketOpUtils.callBase = true; mockedMarketOpUtils.callBase = true;
mockedMarketOpUtils mockedMarketOpUtils
.setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .setup(m => m._generateOptimizedOrdersAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => { .returns(async (msl: MarketSideLiquidity, _opts: GenerateOptimizedOrdersOpts) => {
if (msl.nativeOrders.length === 1) { if (msl.nativeOrders.length === 1) {
hasFirstOptimizationRun = true; hasFirstOptimizationRun = true;
throw new Error(AggregationError.NoOptimalPath); throw new Error(AggregationError.NoOptimalPath);
} else if (msl.nativeOrders.length === 3) { } else if (msl.nativeOrders.length === 3) {
hasSecondOptimizationRun = true; hasSecondOptimizationRun = true;
return mockedMarketOpUtils.target._generateOptimizedOrdersAsync(msl, _opts); return mockedMarketOpUtils.target._generateOptimizedOrdersAsync(msl, _opts);
} else { } else {
throw new Error('Invalid path. this error message should never appear'); throw new Error('Invalid path. this error message should never appear');
} }
}) })
.verifiable(TypeMoq.Times.exactly(2)); .verifiable(TypeMoq.Times.exactly(2));
const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b)); const totalAssetAmount = ORDERS.map(o => o.takerAssetAmount).reduce((a, b) => a.plus(b));
await mockedMarketOpUtils.object.getMarketSellOrdersAsync( await mockedMarketOpUtils.object.getMarketSellOrdersAsync(
ORDERS.slice(2, ORDERS.length), ORDERS.slice(2, ORDERS.length),
totalAssetAmount, totalAssetAmount,
{ {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', apiKey: 'foo',
takerAddress: randomAddress(), takerAddress: randomAddress(),
intentOnFilling: true, isPriceAwareRFQEnabled: true,
quoteRequestor: { intentOnFilling: true,
requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, quoteRequestor: {
} as any, requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync,
}, } as any,
}, },
); },
mockedMarketOpUtils.verifyAll(); );
requestor.verifyAll(); mockedMarketOpUtils.verifyAll();
requestor.verifyAll();
expect(hasFirstOptimizationRun).to.eql(true); expect(hasFirstOptimizationRun).to.eql(true);
expect(hasSecondOptimizationRun).to.eql(true); expect(hasSecondOptimizationRun).to.eql(true);
} });
: undefined,
);
it('getMarketSellOrdersAsync() will raise a NoOptimalPath error if no path was found during on-chain DEX optimization and RFQ optimization', async () => { 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( const mockedMarketOpUtils = TypeMoq.Mock.ofType(