Added initial unit tests and implementation

This commit is contained in:
Daniel Pyrathon 2020-10-02 15:04:50 -07:00
parent 9e42dce5c4
commit 1588c1f362
2 changed files with 117 additions and 2 deletions

View File

@ -57,6 +57,7 @@ export async function getRfqtIndicativeQuotesAsync(
takerAssetData: string, takerAssetData: string,
marketOperation: MarketOperation, marketOperation: MarketOperation,
assetFillAmount: BigNumber, assetFillAmount: BigNumber,
comparisonPrice: BigNumber | undefined,
opts: Partial<GetMarketOrdersOpts>, opts: Partial<GetMarketOrdersOpts>,
): Promise<RFQTIndicativeQuote[]> { ): Promise<RFQTIndicativeQuote[]> {
if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) { if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
@ -65,7 +66,7 @@ export async function getRfqtIndicativeQuotesAsync(
takerAssetData, takerAssetData,
assetFillAmount, assetFillAmount,
marketOperation, marketOperation,
undefined, comparisonPrice,
opts.rfqt, opts.rfqt,
); );
} else { } else {
@ -589,6 +590,17 @@ 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 (rfqt && rfqt.quoteRequestor && marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)) { if (rfqt && rfqt.quoteRequestor && marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)) {
// Calculate a suggested price. For now, this is simply the overall price of the aggregation.
let comparisonPrice: BigNumber | undefined;
if (optimizerResult) {
const totalMakerAmount = BigNumber.sum(...optimizerResult.optimizedOrders.map(order => order.makerAssetAmount));
const totalTakerAmount = BigNumber.sum(...optimizerResult.optimizedOrders.map(order => order.takerAssetAmount));
if (totalTakerAmount.gt(0)) {
comparisonPrice = totalMakerAmount.div(totalTakerAmount);
}
}
// If we are making an indicative quote, make the RFQT request and then re-run the sampler if new orders come back. // If we are making an indicative quote, make the RFQT request and then re-run the sampler if new orders come back.
if (rfqt.isIndicative) { if (rfqt.isIndicative) {
const indicativeQuotes = await getRfqtIndicativeQuotesAsync( const indicativeQuotes = await getRfqtIndicativeQuotesAsync(
@ -596,6 +608,7 @@ export class MarketOperationUtils {
nativeOrders[0].takerAssetData, nativeOrders[0].takerAssetData,
side, side,
amount, amount,
comparisonPrice,
_opts, _opts,
); );
// Re-run optimizer with the new indicative quote // Re-run optimizer with the new indicative quote
@ -621,7 +634,7 @@ export class MarketOperationUtils {
nativeOrders[0].takerAssetData, nativeOrders[0].takerAssetData,
amount, amount,
side, side,
undefined, comparisonPrice,
rfqt, rfqt,
); );
if (firmQuotes.length > 0) { if (firmQuotes.length > 0) {

View File

@ -28,6 +28,7 @@ import {
import { createFillPaths } from '../src/utils/market_operation_utils/fills'; import { createFillPaths } from '../src/utils/market_operation_utils/fills';
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations'; import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
import { SourceFilters } from '../src/utils/market_operation_utils/source_filters';
import { import {
AggregationError, AggregationError,
DexSample, DexSample,
@ -471,6 +472,7 @@ describe('MarketOperationUtils tests', () => {
TAKER_ASSET_DATA, TAKER_ASSET_DATA,
MarketOperation.Sell, MarketOperation.Sell,
new BigNumber('100e18'), new BigNumber('100e18'),
undefined,
{ {
rfqt: { quoteRequestor: requestor.object, ...partialRfqt }, rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
}, },
@ -699,6 +701,106 @@ describe('MarketOperationUtils tests', () => {
mockedMarketOpUtils.verifyAll(); mockedMarketOpUtils.verifyAll();
}); });
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, 18),
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, 18),
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, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
}),
],
orderFillableAmounts: [Web3Wrapper.toBaseUnitAmount(1, 18)],
rfqtIndicativeQuotes: [],
side: MarketOperation.Sell,
twoHopQuotes: [],
quoteSourceFilters: new SourceFilters(),
};
})
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);
expect(requestedComparisonPrice!.toString()).to.eql("320");
expect(result.optimizedOrders[0].makerAssetAmount.toString()).to.eql('321000000000000000000');
expect(result.optimizedOrders[0].takerAssetAmount.toString()).to.eql('1000000000000000000');
});
it('getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', async () => { it('getMarketSellOrdersAsync() will not rerun the optimizer if no orders are returned', async () => {
// Ensure that `_generateOptimizedOrdersAsync` is only called once // Ensure that `_generateOptimizedOrdersAsync` is only called once
const mockedMarketOpUtils = TypeMoq.Mock.ofType( const mockedMarketOpUtils = TypeMoq.Mock.ofType(