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 bcd82dcc55..e1be29f8d2 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -57,6 +57,7 @@ export async function getRfqtIndicativeQuotesAsync( takerAssetData: string, marketOperation: MarketOperation, assetFillAmount: BigNumber, + comparisonPrice: BigNumber | undefined, opts: Partial, ): Promise { if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) { @@ -65,7 +66,7 @@ export async function getRfqtIndicativeQuotesAsync( takerAssetData, assetFillAmount, marketOperation, - undefined, + comparisonPrice, opts.rfqt, ); } else { @@ -589,6 +590,17 @@ export class MarketOperationUtils { // If RFQ liquidity is enabled, make a request to check RFQ liquidity const { rfqt } = _opts; 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 (rfqt.isIndicative) { const indicativeQuotes = await getRfqtIndicativeQuotesAsync( @@ -596,6 +608,7 @@ export class MarketOperationUtils { nativeOrders[0].takerAssetData, side, amount, + comparisonPrice, _opts, ); // Re-run optimizer with the new indicative quote @@ -621,7 +634,7 @@ export class MarketOperationUtils { nativeOrders[0].takerAssetData, amount, side, - undefined, + comparisonPrice, rfqt, ); if (firmQuotes.length > 0) { diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 1d45ffe5d4..cb6c815021 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -28,6 +28,7 @@ import { import { createFillPaths } from '../src/utils/market_operation_utils/fills'; import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations'; +import { SourceFilters } from '../src/utils/market_operation_utils/source_filters'; import { AggregationError, DexSample, @@ -471,6 +472,7 @@ describe('MarketOperationUtils tests', () => { TAKER_ASSET_DATA, MarketOperation.Sell, new BigNumber('100e18'), + undefined, { rfqt: { quoteRequestor: requestor.object, ...partialRfqt }, }, @@ -699,6 +701,106 @@ describe('MarketOperationUtils tests', () => { 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 () => { // Ensure that `_generateOptimizedOrdersAsync` is only called once const mockedMarketOpUtils = TypeMoq.Mock.ofType(