Merge pull request #2627 from 0xProject/feature/new-order-reporter
asset-swapper: QuoteReport response
This commit is contained in:
commit
72c869649a
@ -2,6 +2,10 @@
|
|||||||
{
|
{
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Return quoteReport from SwapQuoter functions",
|
||||||
|
"pr": 2627
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"note": "Allow an empty override for sampler overrides",
|
"note": "Allow an empty override for sampler overrides",
|
||||||
"pr": 2637
|
"pr": 2637
|
||||||
|
@ -64,9 +64,11 @@ export {
|
|||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
SwapQuoteOrdersBreakdown,
|
SwapQuoteOrdersBreakdown,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
|
SwapQuoterRfqtOpts,
|
||||||
SwapQuoterError,
|
SwapQuoterError,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||||
export {
|
export {
|
||||||
BalancerFillData,
|
BalancerFillData,
|
||||||
@ -86,3 +88,11 @@ export {
|
|||||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
export { QuoteRequestor } from './utils/quote_requestor';
|
export { QuoteRequestor } from './utils/quote_requestor';
|
||||||
export { rfqtMocker } from './utils/rfqt_mocker';
|
export { rfqtMocker } from './utils/rfqt_mocker';
|
||||||
|
export {
|
||||||
|
BridgeReportSource,
|
||||||
|
NativeOrderbookReportSource,
|
||||||
|
NativeRFQTReportSource,
|
||||||
|
QuoteReport,
|
||||||
|
QuoteReportSource,
|
||||||
|
} from './utils/quote_report_generator';
|
||||||
|
export type Native = ERC20BridgeSource.Native;
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
|
SwapQuoterRfqtOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { assert } from './utils/assert';
|
import { assert } from './utils/assert';
|
||||||
import { calculateLiquidity } from './utils/calculate_liquidity';
|
import { calculateLiquidity } from './utils/calculate_liquidity';
|
||||||
@ -46,8 +47,7 @@ export class SwapQuoter {
|
|||||||
private readonly _devUtilsContract: DevUtilsContract;
|
private readonly _devUtilsContract: DevUtilsContract;
|
||||||
private readonly _marketOperationUtils: MarketOperationUtils;
|
private readonly _marketOperationUtils: MarketOperationUtils;
|
||||||
private readonly _orderStateUtils: OrderStateUtils;
|
private readonly _orderStateUtils: OrderStateUtils;
|
||||||
private readonly _quoteRequestor: QuoteRequestor;
|
private readonly _rfqtOptions?: SwapQuoterRfqtOpts;
|
||||||
private readonly _rfqtTakerApiKeyWhitelist: string[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
||||||
@ -168,7 +168,8 @@ export class SwapQuoter {
|
|||||||
this.orderbook = orderbook;
|
this.orderbook = orderbook;
|
||||||
this.expiryBufferMs = expiryBufferMs;
|
this.expiryBufferMs = expiryBufferMs;
|
||||||
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||||
this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : [];
|
|
||||||
|
this._rfqtOptions = rfqt;
|
||||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||||
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||||
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
||||||
@ -176,12 +177,6 @@ export class SwapQuoter {
|
|||||||
options.ethGasStationUrl,
|
options.ethGasStationUrl,
|
||||||
);
|
);
|
||||||
this._orderStateUtils = new OrderStateUtils(this._devUtilsContract);
|
this._orderStateUtils = new OrderStateUtils(this._devUtilsContract);
|
||||||
this._quoteRequestor = new QuoteRequestor(
|
|
||||||
rfqt ? rfqt.makerAssetOfferings || {} : {},
|
|
||||||
rfqt ? rfqt.warningLogger : undefined,
|
|
||||||
rfqt ? rfqt.infoLogger : undefined,
|
|
||||||
expiryBufferMs,
|
|
||||||
);
|
|
||||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
// Allow the sampler bytecode to be overwritten using geths override functionality
|
||||||
const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
||||||
const defaultCodeOverrides = samplerBytecode
|
const defaultCodeOverrides = samplerBytecode
|
||||||
@ -569,24 +564,34 @@ export class SwapQuoter {
|
|||||||
|
|
||||||
// get batches of orders from different sources, awaiting sources in parallel
|
// get batches of orders from different sources, awaiting sources in parallel
|
||||||
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
||||||
orderBatchPromises.push(
|
|
||||||
// Don't fetch Open Orderbook orders from the DB if Native has been excluded, or if `nativeExclusivelyRFQT` has been set.
|
const skipOpenOrderbook =
|
||||||
opts.excludedSources.includes(ERC20BridgeSource.Native) ||
|
opts.excludedSources.includes(ERC20BridgeSource.Native) ||
|
||||||
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true)
|
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true);
|
||||||
? Promise.resolve([])
|
if (!skipOpenOrderbook) {
|
||||||
: this._getSignedOrdersAsync(makerAssetData, takerAssetData),
|
orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book
|
||||||
|
}
|
||||||
|
|
||||||
|
const rfqtOptions = this._rfqtOptions;
|
||||||
|
const quoteRequestor = new QuoteRequestor(
|
||||||
|
rfqtOptions ? rfqtOptions.makerAssetOfferings || {} : {},
|
||||||
|
rfqtOptions ? rfqtOptions.warningLogger : undefined,
|
||||||
|
rfqtOptions ? rfqtOptions.infoLogger : undefined,
|
||||||
|
this.expiryBufferMs,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
opts.rfqt && // This is an RFQT-enabled API request
|
opts.rfqt && // This is an RFQT-enabled API request
|
||||||
opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
|
opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
|
||||||
!opts.excludedSources.includes(ERC20BridgeSource.Native) && // Native liquidity is not excluded
|
opts.rfqt.apiKey &&
|
||||||
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey) // A valid API key was provided
|
this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided
|
||||||
|
!opts.excludedSources.includes(ERC20BridgeSource.Native) // Native liquidity is not excluded
|
||||||
) {
|
) {
|
||||||
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
||||||
throw new Error('RFQ-T requests must specify a taker address');
|
throw new Error('RFQ-T requests must specify a taker address');
|
||||||
}
|
}
|
||||||
orderBatchPromises.push(
|
orderBatchPromises.push(
|
||||||
this._quoteRequestor
|
quoteRequestor
|
||||||
.requestRfqtFirmQuotesAsync(
|
.requestRfqtFirmQuotesAsync(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
@ -600,7 +605,7 @@ export class SwapQuoter {
|
|||||||
|
|
||||||
const orderBatches: SignedOrder[][] = await Promise.all(orderBatchPromises);
|
const orderBatches: SignedOrder[][] = await Promise.all(orderBatchPromises);
|
||||||
|
|
||||||
const unsortedOrders: SignedOrder[] = orderBatches.reduce((_orders, batch) => _orders.concat(...batch));
|
const unsortedOrders: SignedOrder[] = orderBatches.reduce((_orders, batch) => _orders.concat(...batch), []);
|
||||||
|
|
||||||
const orders = sortingUtils.sortOrders(unsortedOrders);
|
const orders = sortingUtils.sortOrders(unsortedOrders);
|
||||||
|
|
||||||
@ -615,8 +620,8 @@ export class SwapQuoter {
|
|||||||
|
|
||||||
const calcOpts: CalculateSwapQuoteOpts = opts;
|
const calcOpts: CalculateSwapQuoteOpts = opts;
|
||||||
|
|
||||||
if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt, marketOperation)) {
|
if (calcOpts.rfqt !== undefined) {
|
||||||
calcOpts.rfqt.quoteRequestor = this._quoteRequestor;
|
calcOpts.rfqt.quoteRequestor = quoteRequestor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
if (marketOperation === MarketOperation.Buy) {
|
||||||
@ -637,13 +642,9 @@ export class SwapQuoter {
|
|||||||
|
|
||||||
return swapQuote;
|
return swapQuote;
|
||||||
}
|
}
|
||||||
private _shouldEnableIndicativeRfqt(opts: CalculateSwapQuoteOpts['rfqt'], op: MarketOperation): boolean {
|
private _isApiKeyWhitelisted(apiKey: string): boolean {
|
||||||
return (
|
const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : [];
|
||||||
opts !== undefined &&
|
return whitelistedApiKeys.includes(apiKey);
|
||||||
opts.isIndicative !== undefined &&
|
|
||||||
opts.isIndicative &&
|
|
||||||
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// tslint:disable-next-line: max-file-line-count
|
// tslint:disable-next-line: max-file-line-count
|
||||||
|
@ -3,6 +3,7 @@ import { SignedOrder } from '@0x/types';
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { GetMarketOrdersOpts, OptimizedMarketOrder } from './utils/market_operation_utils/types';
|
import { GetMarketOrdersOpts, OptimizedMarketOrder } from './utils/market_operation_utils/types';
|
||||||
|
import { QuoteReport } from './utils/quote_report_generator';
|
||||||
import { LogFunction } from './utils/quote_requestor';
|
import { LogFunction } from './utils/quote_requestor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,6 +156,7 @@ export interface SwapQuoteBase {
|
|||||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||||
|
quoteReport?: QuoteReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,6 +238,13 @@ export interface RfqtMakerAssetOfferings {
|
|||||||
|
|
||||||
export { LogFunction } from './utils/quote_requestor';
|
export { LogFunction } from './utils/quote_requestor';
|
||||||
|
|
||||||
|
export interface SwapQuoterRfqtOpts {
|
||||||
|
takerApiKeyWhitelist: string[];
|
||||||
|
makerAssetOfferings: RfqtMakerAssetOfferings;
|
||||||
|
warningLogger?: LogFunction;
|
||||||
|
infoLogger?: LogFunction;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* chainId: The ethereum chain id. Defaults to 1 (mainnet).
|
* chainId: The ethereum chain id. Defaults to 1 (mainnet).
|
||||||
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
||||||
@ -252,12 +261,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
|||||||
liquidityProviderRegistryAddress?: string;
|
liquidityProviderRegistryAddress?: string;
|
||||||
multiBridgeAddress?: string;
|
multiBridgeAddress?: string;
|
||||||
ethGasStationUrl?: string;
|
ethGasStationUrl?: string;
|
||||||
rfqt?: {
|
rfqt?: SwapQuoterRfqtOpts;
|
||||||
takerApiKeyWhitelist: string[];
|
|
||||||
makerAssetOfferings: RfqtMakerAssetOfferings;
|
|
||||||
warningLogger?: LogFunction;
|
|
||||||
infoLogger?: LogFunction;
|
|
||||||
};
|
|
||||||
samplerOverrides?: SamplerOverrides;
|
samplerOverrides?: SamplerOverrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,13 @@ import { ContractAddresses } from '@0x/contract-addresses';
|
|||||||
import { RFQTIndicativeQuote } from '@0x/quote-server';
|
import { RFQTIndicativeQuote } from '@0x/quote-server';
|
||||||
import { SignedOrder } from '@0x/types';
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { MarketOperation } from '../../types';
|
||||||
|
import { QuoteRequestor } from '../quote_requestor';
|
||||||
import { difference } from '../utils';
|
import { difference } from '../utils';
|
||||||
|
|
||||||
|
import { QuoteReportGenerator } from './../quote_report_generator';
|
||||||
import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants';
|
import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants';
|
||||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
||||||
import {
|
import {
|
||||||
@ -23,6 +26,7 @@ import {
|
|||||||
FeeSchedule,
|
FeeSchedule,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
|
OptimizedOrdersAndQuoteReport,
|
||||||
OrderDomain,
|
OrderDomain,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@ -81,7 +85,7 @@ export class MarketOperationUtils {
|
|||||||
nativeOrders: SignedOrder[],
|
nativeOrders: SignedOrder[],
|
||||||
takerAmount: BigNumber,
|
takerAmount: BigNumber,
|
||||||
opts?: Partial<GetMarketOrdersOpts>,
|
opts?: Partial<GetMarketOrdersOpts>,
|
||||||
): Promise<OptimizedMarketOrder[]> {
|
): Promise<OptimizedOrdersAndQuoteReport> {
|
||||||
if (nativeOrders.length === 0) {
|
if (nativeOrders.length === 0) {
|
||||||
throw new Error(AggregationError.EmptyOrders);
|
throw new Error(AggregationError.EmptyOrders);
|
||||||
}
|
}
|
||||||
@ -170,6 +174,7 @@ export class MarketOperationUtils {
|
|||||||
feeSchedule: _opts.feeSchedule,
|
feeSchedule: _opts.feeSchedule,
|
||||||
allowFallback: _opts.allowFallback,
|
allowFallback: _opts.allowFallback,
|
||||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||||
|
quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,13 +184,13 @@ export class MarketOperationUtils {
|
|||||||
* @param nativeOrders Native orders.
|
* @param nativeOrders Native orders.
|
||||||
* @param makerAmount Amount of maker asset to buy.
|
* @param makerAmount Amount of maker asset to buy.
|
||||||
* @param opts Options object.
|
* @param opts Options object.
|
||||||
* @return orders.
|
* @return object with optimized orders and a QuoteReport
|
||||||
*/
|
*/
|
||||||
public async getMarketBuyOrdersAsync(
|
public async getMarketBuyOrdersAsync(
|
||||||
nativeOrders: SignedOrder[],
|
nativeOrders: SignedOrder[],
|
||||||
makerAmount: BigNumber,
|
makerAmount: BigNumber,
|
||||||
opts?: Partial<GetMarketOrdersOpts>,
|
opts?: Partial<GetMarketOrdersOpts>,
|
||||||
): Promise<OptimizedMarketOrder[]> {
|
): Promise<OptimizedOrdersAndQuoteReport> {
|
||||||
if (nativeOrders.length === 0) {
|
if (nativeOrders.length === 0) {
|
||||||
throw new Error(AggregationError.EmptyOrders);
|
throw new Error(AggregationError.EmptyOrders);
|
||||||
}
|
}
|
||||||
@ -274,6 +279,7 @@ export class MarketOperationUtils {
|
|||||||
feeSchedule: _opts.feeSchedule,
|
feeSchedule: _opts.feeSchedule,
|
||||||
allowFallback: _opts.allowFallback,
|
allowFallback: _opts.allowFallback,
|
||||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||||
|
quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +351,7 @@ export class MarketOperationUtils {
|
|||||||
const dexQuotes = batchDexQuotes[i];
|
const dexQuotes = batchDexQuotes[i];
|
||||||
const makerAmount = makerAmounts[i];
|
const makerAmount = makerAmounts[i];
|
||||||
try {
|
try {
|
||||||
return await this._generateOptimizedOrdersAsync({
|
return (await this._generateOptimizedOrdersAsync({
|
||||||
orderFillableAmounts,
|
orderFillableAmounts,
|
||||||
nativeOrders,
|
nativeOrders,
|
||||||
dexQuotes,
|
dexQuotes,
|
||||||
@ -361,7 +367,7 @@ export class MarketOperationUtils {
|
|||||||
feeSchedule: _opts.feeSchedule,
|
feeSchedule: _opts.feeSchedule,
|
||||||
allowFallback: _opts.allowFallback,
|
allowFallback: _opts.allowFallback,
|
||||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||||
});
|
})).optimizedOrders;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// It's possible for one of the pairs to have no path
|
// It's possible for one of the pairs to have no path
|
||||||
// rather than throw NO_OPTIMAL_PATH we return undefined
|
// rather than throw NO_OPTIMAL_PATH we return undefined
|
||||||
@ -390,7 +396,8 @@ export class MarketOperationUtils {
|
|||||||
shouldBatchBridgeOrders?: boolean;
|
shouldBatchBridgeOrders?: boolean;
|
||||||
liquidityProviderAddress?: string;
|
liquidityProviderAddress?: string;
|
||||||
multiBridgeAddress?: string;
|
multiBridgeAddress?: string;
|
||||||
}): Promise<OptimizedMarketOrder[]> {
|
quoteRequestor?: QuoteRequestor;
|
||||||
|
}): Promise<OptimizedOrdersAndQuoteReport> {
|
||||||
const { inputToken, outputToken, side, inputAmount } = opts;
|
const { inputToken, outputToken, side, inputAmount } = opts;
|
||||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
||||||
// Convert native orders and dex quotes into fill paths.
|
// Convert native orders and dex quotes into fill paths.
|
||||||
@ -444,7 +451,7 @@ export class MarketOperationUtils {
|
|||||||
optimalPath = [...nativeSubPath.filter(f => f !== lastNativeFillIfExists), ...nonNativeOptimalPath];
|
optimalPath = [...nativeSubPath.filter(f => f !== lastNativeFillIfExists), ...nonNativeOptimalPath];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createOrdersFromPath(optimalPath, {
|
const optimizedOrders = createOrdersFromPath(optimalPath, {
|
||||||
side,
|
side,
|
||||||
inputToken,
|
inputToken,
|
||||||
outputToken,
|
outputToken,
|
||||||
@ -455,6 +462,15 @@ export class MarketOperationUtils {
|
|||||||
multiBridgeAddress: opts.multiBridgeAddress,
|
multiBridgeAddress: opts.multiBridgeAddress,
|
||||||
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
|
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
|
||||||
});
|
});
|
||||||
|
const quoteReport = new QuoteReportGenerator(
|
||||||
|
opts.side,
|
||||||
|
_.flatten(opts.dexQuotes),
|
||||||
|
opts.nativeOrders,
|
||||||
|
opts.orderFillableAmounts,
|
||||||
|
_.flatten(optimizedOrders.map(o => o.fills)),
|
||||||
|
opts.quoteRequestor,
|
||||||
|
).generateReport();
|
||||||
|
return { optimizedOrders, quoteReport };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _optionalSources(): ERC20BridgeSource[] {
|
private _optionalSources(): ERC20BridgeSource[] {
|
||||||
|
@ -3,6 +3,7 @@ import { BigNumber } from '@0x/utils';
|
|||||||
|
|
||||||
import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types';
|
import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types';
|
||||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||||
|
import { QuoteReport } from '../quote_report_generator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order domain keys: chainId and exchange
|
* Order domain keys: chainId and exchange
|
||||||
@ -254,3 +255,17 @@ export interface SourceQuoteOperation<TFillData extends FillData = FillData> ext
|
|||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
fillData?: TFillData;
|
fillData?: TFillData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in the ERC20BridgeSampler when a source does not natively
|
||||||
|
* support sampling via a specific buy amount.
|
||||||
|
*/
|
||||||
|
export interface FakeBuyOpts {
|
||||||
|
targetSlippageBps: BigNumber;
|
||||||
|
maxIterations: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptimizedOrdersAndQuoteReport {
|
||||||
|
optimizedOrders: OptimizedMarketOrder[];
|
||||||
|
quoteReport: QuoteReport;
|
||||||
|
}
|
||||||
|
161
packages/asset-swapper/src/utils/quote_report_generator.ts
Normal file
161
packages/asset-swapper/src/utils/quote_report_generator.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { orderHashUtils } from '@0x/order-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { ERC20BridgeSource, SignedOrder } from '..';
|
||||||
|
import { MarketOperation } from '../types';
|
||||||
|
|
||||||
|
import { CollapsedFill, DexSample, NativeCollapsedFill } from './market_operation_utils/types';
|
||||||
|
import { QuoteRequestor } from './quote_requestor';
|
||||||
|
|
||||||
|
export interface BridgeReportSource {
|
||||||
|
liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NativeReportSourceBase {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
orderHash: string;
|
||||||
|
nativeOrder: SignedOrder;
|
||||||
|
fillableTakerAmount: BigNumber;
|
||||||
|
}
|
||||||
|
export interface NativeOrderbookReportSource extends NativeReportSourceBase {
|
||||||
|
isRfqt: false;
|
||||||
|
}
|
||||||
|
export interface NativeRFQTReportSource extends NativeReportSourceBase {
|
||||||
|
isRfqt: true;
|
||||||
|
makerUri: string;
|
||||||
|
}
|
||||||
|
export type QuoteReportSource = BridgeReportSource | NativeOrderbookReportSource | NativeRFQTReportSource;
|
||||||
|
|
||||||
|
export interface QuoteReport {
|
||||||
|
sourcesConsidered: QuoteReportSource[];
|
||||||
|
sourcesDelivered: QuoteReportSource[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativeOrderFromCollapsedFill = (cf: CollapsedFill): SignedOrder | undefined => {
|
||||||
|
// Cast as NativeCollapsedFill and then check
|
||||||
|
// if it really is a NativeCollapsedFill
|
||||||
|
const possibleNativeCollapsedFill = cf as NativeCollapsedFill;
|
||||||
|
if (possibleNativeCollapsedFill.fillData && possibleNativeCollapsedFill.fillData.order) {
|
||||||
|
return possibleNativeCollapsedFill.fillData.order;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class QuoteReportGenerator {
|
||||||
|
private readonly _dexQuotes: DexSample[];
|
||||||
|
private readonly _nativeOrders: SignedOrder[];
|
||||||
|
private readonly _orderHashesToFillableAmounts: { [orderHash: string]: BigNumber };
|
||||||
|
private readonly _marketOperation: MarketOperation;
|
||||||
|
private readonly _collapsedFills: CollapsedFill[];
|
||||||
|
private readonly _quoteRequestor?: QuoteRequestor;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
marketOperation: MarketOperation,
|
||||||
|
dexQuotes: DexSample[],
|
||||||
|
nativeOrders: SignedOrder[],
|
||||||
|
orderFillableAmounts: BigNumber[],
|
||||||
|
collapsedFills: CollapsedFill[],
|
||||||
|
quoteRequestor?: QuoteRequestor,
|
||||||
|
) {
|
||||||
|
this._dexQuotes = dexQuotes;
|
||||||
|
this._nativeOrders = nativeOrders;
|
||||||
|
this._marketOperation = marketOperation;
|
||||||
|
this._quoteRequestor = quoteRequestor;
|
||||||
|
this._collapsedFills = collapsedFills;
|
||||||
|
|
||||||
|
// convert order fillable amount array to easy to look up hash
|
||||||
|
if (orderFillableAmounts.length !== nativeOrders.length) {
|
||||||
|
// length mismatch, abort
|
||||||
|
this._orderHashesToFillableAmounts = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const orderHashesToFillableAmounts: { [orderHash: string]: BigNumber } = {};
|
||||||
|
nativeOrders.forEach((nativeOrder, idx) => {
|
||||||
|
orderHashesToFillableAmounts[orderHashUtils.getOrderHash(nativeOrder)] = orderFillableAmounts[idx];
|
||||||
|
});
|
||||||
|
this._orderHashesToFillableAmounts = orderHashesToFillableAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateReport(): QuoteReport {
|
||||||
|
const dexReportSourcesConsidered = this._dexQuotes.map(dq => this._dexSampleToReportSource(dq));
|
||||||
|
const nativeOrderSourcesConsidered = this._nativeOrders.map(no => this._nativeOrderToReportSource(no));
|
||||||
|
|
||||||
|
const sourcesConsidered = [...dexReportSourcesConsidered, ...nativeOrderSourcesConsidered];
|
||||||
|
const sourcesDelivered = this._collapsedFills.map(collapsedFill => {
|
||||||
|
const foundNativeOrder = nativeOrderFromCollapsedFill(collapsedFill);
|
||||||
|
if (foundNativeOrder) {
|
||||||
|
return this._nativeOrderToReportSource(foundNativeOrder);
|
||||||
|
} else {
|
||||||
|
return this._dexSampleToReportSource(collapsedFill);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourcesConsidered,
|
||||||
|
sourcesDelivered,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dexSampleToReportSource(ds: DexSample): BridgeReportSource {
|
||||||
|
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 (this._marketOperation === MarketOperation.Buy) {
|
||||||
|
return {
|
||||||
|
makerAmount: ds.input,
|
||||||
|
takerAmount: ds.output,
|
||||||
|
liquiditySource,
|
||||||
|
};
|
||||||
|
} else if (this._marketOperation === MarketOperation.Sell) {
|
||||||
|
return {
|
||||||
|
makerAmount: ds.output,
|
||||||
|
takerAmount: ds.input,
|
||||||
|
liquiditySource,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected marketOperation ${this._marketOperation}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _nativeOrderToReportSource(nativeOrder: SignedOrder): NativeRFQTReportSource | NativeOrderbookReportSource {
|
||||||
|
const orderHash = orderHashUtils.getOrderHash(nativeOrder);
|
||||||
|
|
||||||
|
const nativeOrderBase: NativeReportSourceBase = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: nativeOrder.makerAssetAmount,
|
||||||
|
takerAmount: nativeOrder.takerAssetAmount,
|
||||||
|
fillableTakerAmount: this._orderHashesToFillableAmounts[orderHash],
|
||||||
|
nativeOrder,
|
||||||
|
orderHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||||
|
const foundRfqtMakerUri = this._quoteRequestor && this._quoteRequestor.getMakerUriForOrderHash(orderHash);
|
||||||
|
if (foundRfqtMakerUri) {
|
||||||
|
const rfqtSource: NativeRFQTReportSource = {
|
||||||
|
...nativeOrderBase,
|
||||||
|
isRfqt: true,
|
||||||
|
makerUri: foundRfqtMakerUri,
|
||||||
|
};
|
||||||
|
return rfqtSource;
|
||||||
|
} else {
|
||||||
|
// if it's not an rfqt order, treat as normal
|
||||||
|
const regularNativeOrder: NativeOrderbookReportSource = {
|
||||||
|
...nativeOrderBase,
|
||||||
|
isRfqt: false,
|
||||||
|
};
|
||||||
|
return regularNativeOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { schemas, SchemaValidator } from '@0x/json-schemas';
|
import { schemas, SchemaValidator } from '@0x/json-schemas';
|
||||||
import { assetDataUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
|
import { assetDataUtils, orderCalculationUtils, orderHashUtils, SignedOrder } from '@0x/order-utils';
|
||||||
import { RFQTFirmQuote, RFQTIndicativeQuote, TakerRequest } from '@0x/quote-server';
|
import { RFQTFirmQuote, RFQTIndicativeQuote, TakerRequest } from '@0x/quote-server';
|
||||||
import { ERC20AssetData } from '@0x/types';
|
import { ERC20AssetData } from '@0x/types';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber, logUtils } from '@0x/utils';
|
||||||
import Axios, { AxiosResponse } from 'axios';
|
import Axios from 'axios';
|
||||||
|
|
||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
|
import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
|
||||||
@ -85,6 +85,7 @@ export type LogFunction = (obj: object, msg?: string, ...args: any[]) => void;
|
|||||||
|
|
||||||
export class QuoteRequestor {
|
export class QuoteRequestor {
|
||||||
private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
|
private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
|
||||||
|
private readonly _orderHashToMakerUri: { [orderHash: string]: string } = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings,
|
private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings,
|
||||||
@ -105,7 +106,7 @@ export class QuoteRequestor {
|
|||||||
const _opts: RfqtRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
const _opts: RfqtRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||||
assertTakerAddressOrThrow(_opts.takerAddress);
|
assertTakerAddressOrThrow(_opts.takerAddress);
|
||||||
|
|
||||||
const firmQuotes = await this._getQuotesAsync<RFQTFirmQuote>( // not yet BigNumber
|
const firmQuoteResponses = await this._getQuotesAsync<RFQTFirmQuote>( // not yet BigNumber
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
@ -114,41 +115,38 @@ export class QuoteRequestor {
|
|||||||
'firm',
|
'firm',
|
||||||
);
|
);
|
||||||
|
|
||||||
const ordersWithStringInts = firmQuotes.map(quote => quote.signedOrder);
|
const result: RFQTFirmQuote[] = [];
|
||||||
|
firmQuoteResponses.forEach(firmQuoteResponse => {
|
||||||
|
const orderWithStringInts = firmQuoteResponse.response.signedOrder;
|
||||||
|
|
||||||
const validatedOrdersWithStringInts = ordersWithStringInts.filter(order => {
|
|
||||||
try {
|
try {
|
||||||
const hasValidSchema = this._schemaValidator.isValid(order, schemas.signedOrderSchema);
|
const hasValidSchema = this._schemaValidator.isValid(orderWithStringInts, schemas.signedOrderSchema);
|
||||||
if (!hasValidSchema) {
|
if (!hasValidSchema) {
|
||||||
throw new Error('order not valid');
|
throw new Error('Order not valid');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._warningLogger(order, `Invalid RFQ-t order received, filtering out. ${err.message}`);
|
this._warningLogger(orderWithStringInts, `Invalid RFQ-t order received, filtering out. ${err.message}`);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasExpectedAssetData(
|
!hasExpectedAssetData(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
order.makerAssetData.toLowerCase(),
|
orderWithStringInts.makerAssetData.toLowerCase(),
|
||||||
order.takerAssetData.toLowerCase(),
|
orderWithStringInts.takerAssetData.toLowerCase(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this._warningLogger(order, 'Unexpected asset data in RFQ-T order, filtering out');
|
this._warningLogger(orderWithStringInts, 'Unexpected asset data in RFQ-T order, filtering out');
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order.takerAddress.toLowerCase() !== _opts.takerAddress.toLowerCase()) {
|
if (orderWithStringInts.takerAddress.toLowerCase() !== _opts.takerAddress.toLowerCase()) {
|
||||||
this._warningLogger(order, 'Unexpected takerAddress in RFQ-T order, filtering out');
|
this._warningLogger(orderWithStringInts, 'Unexpected takerAddress in RFQ-T order, filtering out');
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
const orderWithBigNumberInts: SignedOrder = {
|
||||||
});
|
|
||||||
|
|
||||||
const validatedOrders: SignedOrder[] = validatedOrdersWithStringInts.map(orderWithStringInts => {
|
|
||||||
return {
|
|
||||||
...orderWithStringInts,
|
...orderWithStringInts,
|
||||||
makerAssetAmount: new BigNumber(orderWithStringInts.makerAssetAmount),
|
makerAssetAmount: new BigNumber(orderWithStringInts.makerAssetAmount),
|
||||||
takerAssetAmount: new BigNumber(orderWithStringInts.takerAssetAmount),
|
takerAssetAmount: new BigNumber(orderWithStringInts.takerAssetAmount),
|
||||||
@ -157,17 +155,25 @@ export class QuoteRequestor {
|
|||||||
expirationTimeSeconds: new BigNumber(orderWithStringInts.expirationTimeSeconds),
|
expirationTimeSeconds: new BigNumber(orderWithStringInts.expirationTimeSeconds),
|
||||||
salt: new BigNumber(orderWithStringInts.salt),
|
salt: new BigNumber(orderWithStringInts.salt),
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const orders = validatedOrders.filter(order => {
|
if (
|
||||||
if (orderCalculationUtils.willOrderExpire(order, this._expiryBufferMs / constants.ONE_SECOND_MS)) {
|
orderCalculationUtils.willOrderExpire(
|
||||||
this._warningLogger(order, 'Expiry too soon in RFQ-T order, filtering out');
|
orderWithBigNumberInts,
|
||||||
return false;
|
this._expiryBufferMs / constants.ONE_SECOND_MS,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this._warningLogger(orderWithBigNumberInts, 'Expiry too soon in RFQ-T order, filtering out');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return orders.map(order => ({ signedOrder: order }));
|
// Store makerUri for looking up later
|
||||||
|
this._orderHashToMakerUri[orderHashUtils.getOrderHash(orderWithBigNumberInts)] = firmQuoteResponse.makerUri;
|
||||||
|
|
||||||
|
// Passed all validation, add it to result
|
||||||
|
result.push({ signedOrder: orderWithBigNumberInts });
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async requestRfqtIndicativeQuotesAsync(
|
public async requestRfqtIndicativeQuotesAsync(
|
||||||
@ -189,7 +195,8 @@ export class QuoteRequestor {
|
|||||||
'indicative',
|
'indicative',
|
||||||
);
|
);
|
||||||
|
|
||||||
const validResponsesWithStringInts = responsesWithStringInts.filter(response => {
|
const validResponsesWithStringInts = responsesWithStringInts.filter(result => {
|
||||||
|
const response = result.response;
|
||||||
if (!this._isValidRfqtIndicativeQuoteResponse(response)) {
|
if (!this._isValidRfqtIndicativeQuoteResponse(response)) {
|
||||||
this._warningLogger(response, 'Invalid RFQ-T indicative quote received, filtering out');
|
this._warningLogger(response, 'Invalid RFQ-T indicative quote received, filtering out');
|
||||||
return false;
|
return false;
|
||||||
@ -203,7 +210,8 @@ export class QuoteRequestor {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const validResponses = validResponsesWithStringInts.map(response => {
|
const validResponses = validResponsesWithStringInts.map(result => {
|
||||||
|
const response = result.response;
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
makerAssetAmount: new BigNumber(response.makerAssetAmount),
|
makerAssetAmount: new BigNumber(response.makerAssetAmount),
|
||||||
@ -223,6 +231,13 @@ export class QuoteRequestor {
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an order hash, returns the makerUri that the order originated from
|
||||||
|
*/
|
||||||
|
public getMakerUriForOrderHash(orderHash: string): string | undefined {
|
||||||
|
return this._orderHashToMakerUri[orderHash];
|
||||||
|
}
|
||||||
|
|
||||||
private _isValidRfqtIndicativeQuoteResponse(response: RFQTIndicativeQuote): boolean {
|
private _isValidRfqtIndicativeQuoteResponse(response: RFQTIndicativeQuote): boolean {
|
||||||
const hasValidMakerAssetAmount =
|
const hasValidMakerAssetAmount =
|
||||||
response.makerAssetAmount !== undefined &&
|
response.makerAssetAmount !== undefined &&
|
||||||
@ -278,10 +293,9 @@ export class QuoteRequestor {
|
|||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
options: RfqtRequestOpts,
|
options: RfqtRequestOpts,
|
||||||
quoteType: 'firm' | 'indicative',
|
quoteType: 'firm' | 'indicative',
|
||||||
): Promise<ResponseT[]> {
|
): Promise<Array<{ response: ResponseT; makerUri: string }>> {
|
||||||
// create an array of promises for quote responses, using "undefined"
|
const result: Array<{ response: ResponseT; makerUri: string }> = [];
|
||||||
// as a placeholder for failed requests.
|
await Promise.all(
|
||||||
const responsesIfDefined: Array<undefined | AxiosResponse<ResponseT>> = await Promise.all(
|
|
||||||
Object.keys(this._rfqtAssetOfferings).map(async url => {
|
Object.keys(this._rfqtAssetOfferings).map(async url => {
|
||||||
if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
|
if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
|
||||||
const requestParamsWithBigNumbers = {
|
const requestParamsWithBigNumbers = {
|
||||||
@ -330,7 +344,7 @@ export class QuoteRequestor {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return response;
|
result.push({ response: response.data, makerUri: url });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._infoLogger({
|
this._infoLogger({
|
||||||
rfqtMakerInteraction: {
|
rfqtMakerInteraction: {
|
||||||
@ -347,17 +361,10 @@ export class QuoteRequestor {
|
|||||||
options.apiKey
|
options.apiKey
|
||||||
} for taker address ${options.takerAddress}`,
|
} for taker address ${options.takerAddress}`,
|
||||||
);
|
);
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
const responses = responsesIfDefined.filter(
|
|
||||||
(respIfDefd): respIfDefd is AxiosResponse<ResponseT> => respIfDefd !== undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
return responses.map(response => response.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
MarketOperation,
|
MarketOperation,
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteBase,
|
|
||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
SwapQuoteOrdersBreakdown,
|
SwapQuoteOrdersBreakdown,
|
||||||
SwapQuoterError,
|
SwapQuoterError,
|
||||||
@ -17,9 +16,16 @@ import {
|
|||||||
|
|
||||||
import { MarketOperationUtils } from './market_operation_utils';
|
import { MarketOperationUtils } from './market_operation_utils';
|
||||||
import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders';
|
import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders';
|
||||||
import { FeeSchedule, FillData, GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types';
|
import {
|
||||||
|
FeeSchedule,
|
||||||
|
FillData,
|
||||||
|
GetMarketOrdersOpts,
|
||||||
|
OptimizedMarketOrder,
|
||||||
|
OptimizedOrdersAndQuoteReport,
|
||||||
|
} from './market_operation_utils/types';
|
||||||
import { isSupportedAssetDataInOrders } from './utils';
|
import { isSupportedAssetDataInOrders } from './utils';
|
||||||
|
|
||||||
|
import { QuoteReport } from './quote_report_generator';
|
||||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation';
|
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation';
|
||||||
|
|
||||||
// TODO(dave4506) How do we want to reintroduce InsufficientAssetLiquidityError?
|
// TODO(dave4506) How do we want to reintroduce InsufficientAssetLiquidityError?
|
||||||
@ -87,6 +93,7 @@ export class SwapQuoteCalculator {
|
|||||||
assetFillAmounts,
|
assetFillAmounts,
|
||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
|
|
||||||
const batchSwapQuotes = await Promise.all(
|
const batchSwapQuotes = await Promise.all(
|
||||||
batchSignedOrders.map(async (orders, i) => {
|
batchSignedOrders.map(async (orders, i) => {
|
||||||
if (orders) {
|
if (orders) {
|
||||||
@ -120,7 +127,8 @@ export class SwapQuoteCalculator {
|
|||||||
}
|
}
|
||||||
// since prunedOrders do not have fillState, we will add a buffer of fillable orders to consider that some native are orders are partially filled
|
// since prunedOrders do not have fillState, we will add a buffer of fillable orders to consider that some native are orders are partially filled
|
||||||
|
|
||||||
let resultOrders: OptimizedMarketOrder[] = [];
|
let optimizedOrders: OptimizedMarketOrder[] | undefined;
|
||||||
|
let quoteReport: QuoteReport | undefined;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Scale fees by gas price.
|
// Scale fees by gas price.
|
||||||
@ -137,20 +145,24 @@ export class SwapQuoteCalculator {
|
|||||||
|
|
||||||
if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) {
|
if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) {
|
||||||
// HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable
|
// HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable
|
||||||
resultOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o));
|
optimizedOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o));
|
||||||
} else {
|
} else {
|
||||||
if (operation === MarketOperation.Buy) {
|
if (operation === MarketOperation.Buy) {
|
||||||
resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync(
|
const buyResult = await this._marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
prunedOrders,
|
prunedOrders,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
_opts,
|
_opts,
|
||||||
);
|
);
|
||||||
|
optimizedOrders = buyResult.optimizedOrders;
|
||||||
|
quoteReport = buyResult.quoteReport;
|
||||||
} else {
|
} else {
|
||||||
resultOrders = await this._marketOperationUtils.getMarketSellOrdersAsync(
|
const sellResult = await this._marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
prunedOrders,
|
prunedOrders,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
_opts,
|
_opts,
|
||||||
);
|
);
|
||||||
|
optimizedOrders = sellResult.optimizedOrders;
|
||||||
|
quoteReport = sellResult.quoteReport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,11 +172,12 @@ export class SwapQuoteCalculator {
|
|||||||
return createSwapQuote(
|
return createSwapQuote(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
resultOrders,
|
optimizedOrders,
|
||||||
operation,
|
operation,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
opts.gasSchedule,
|
opts.gasSchedule,
|
||||||
|
quoteReport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,15 +185,16 @@ export class SwapQuoteCalculator {
|
|||||||
function createSwapQuote(
|
function createSwapQuote(
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
resultOrders: OptimizedMarketOrder[],
|
optimizedOrders: OptimizedMarketOrder[],
|
||||||
operation: MarketOperation,
|
operation: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
gasPrice: BigNumber,
|
gasPrice: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
gasSchedule: FeeSchedule,
|
||||||
|
quoteReport?: QuoteReport,
|
||||||
): SwapQuote {
|
): SwapQuote {
|
||||||
const bestCaseFillResult = simulateBestCaseFill({
|
const bestCaseFillResult = simulateBestCaseFill({
|
||||||
gasPrice,
|
gasPrice,
|
||||||
orders: resultOrders,
|
orders: optimizedOrders,
|
||||||
side: operation,
|
side: operation,
|
||||||
fillAmount: assetFillAmount,
|
fillAmount: assetFillAmount,
|
||||||
opts: { gasSchedule },
|
opts: { gasSchedule },
|
||||||
@ -188,20 +202,21 @@ function createSwapQuote(
|
|||||||
|
|
||||||
const worstCaseFillResult = simulateWorstCaseFill({
|
const worstCaseFillResult = simulateWorstCaseFill({
|
||||||
gasPrice,
|
gasPrice,
|
||||||
orders: resultOrders,
|
orders: optimizedOrders,
|
||||||
side: operation,
|
side: operation,
|
||||||
fillAmount: assetFillAmount,
|
fillAmount: assetFillAmount,
|
||||||
opts: { gasSchedule },
|
opts: { gasSchedule },
|
||||||
});
|
});
|
||||||
|
|
||||||
const quoteBase: SwapQuoteBase = {
|
const quoteBase = {
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult),
|
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult),
|
||||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult),
|
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult),
|
||||||
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
|
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
|
||||||
orders: resultOrders,
|
orders: optimizedOrders,
|
||||||
|
quoteReport,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (operation === MarketOperation.Buy) {
|
if (operation === MarketOperation.Buy) {
|
||||||
@ -209,12 +224,14 @@ function createSwapQuote(
|
|||||||
...quoteBase,
|
...quoteBase,
|
||||||
type: MarketOperation.Buy,
|
type: MarketOperation.Buy,
|
||||||
makerAssetFillAmount: assetFillAmount,
|
makerAssetFillAmount: assetFillAmount,
|
||||||
|
quoteReport,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...quoteBase,
|
...quoteBase,
|
||||||
type: MarketOperation.Sell,
|
type: MarketOperation.Sell,
|
||||||
takerAssetFillAmount: assetFillAmount,
|
takerAssetFillAmount: assetFillAmount,
|
||||||
|
quoteReport,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -507,12 +507,13 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct asset data', async () => {
|
it('generates bridge orders with correct asset data', async () => {
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
DEFAULT_OPTS,
|
DEFAULT_OPTS,
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
expect(getSourceFromAssetData(order.makerAssetData)).to.exist('');
|
expect(getSourceFromAssetData(order.makerAssetData)).to.exist('');
|
||||||
@ -531,24 +532,26 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct taker amount', async () => {
|
it('generates bridge orders with correct taker amount', async () => {
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
DEFAULT_OPTS,
|
DEFAULT_OPTS,
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const totalTakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.takerAssetAmount));
|
const totalTakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.takerAssetAmount));
|
||||||
expect(totalTakerAssetAmount).to.bignumber.gte(FILL_AMOUNT);
|
expect(totalTakerAssetAmount).to.bignumber.gte(FILL_AMOUNT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
||||||
const bridgeSlippage = _.random(0.1, true);
|
const bridgeSlippage = _.random(0.1, true);
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, bridgeSlippage },
|
{ ...DEFAULT_OPTS, bridgeSlippage },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
const expectedMakerAmount = order.fills[0].output;
|
const expectedMakerAmount = order.fills[0].output;
|
||||||
@ -566,11 +569,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Eth2Dai,
|
ERC20BridgeSource.Eth2Dai,
|
||||||
@ -604,11 +608,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
@ -641,11 +646,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
@ -666,11 +672,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Eth2Dai,
|
ERC20BridgeSource.Eth2Dai,
|
||||||
@ -689,11 +696,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const firstSources = [
|
const firstSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
@ -715,11 +723,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||||
const secondSources: ERC20BridgeSource[] = [];
|
const secondSources: ERC20BridgeSource[] = [];
|
||||||
@ -756,7 +765,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
ORDER_DOMAIN,
|
ORDER_DOMAIN,
|
||||||
registryAddress,
|
registryAddress,
|
||||||
);
|
);
|
||||||
const result = await sampler.getMarketSellOrdersAsync(
|
const ordersAndReport = await sampler.getMarketSellOrdersAsync(
|
||||||
[
|
[
|
||||||
createOrder({
|
createOrder({
|
||||||
makerAssetData: assetDataUtils.encodeERC20AssetData(xAsset),
|
makerAssetData: assetDataUtils.encodeERC20AssetData(xAsset),
|
||||||
@ -766,6 +775,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
Web3Wrapper.toBaseUnitAmount(10, 18),
|
Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||||
{ excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0, shouldBatchBridgeOrders: false },
|
{ excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0, shouldBatchBridgeOrders: false },
|
||||||
);
|
);
|
||||||
|
const result = ordersAndReport.optimizedOrders;
|
||||||
expect(result.length).to.eql(1);
|
expect(result.length).to.eql(1);
|
||||||
expect(result[0].makerAddress).to.eql(liquidityProviderAddress);
|
expect(result[0].makerAddress).to.eql(liquidityProviderAddress);
|
||||||
|
|
||||||
@ -792,7 +802,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{
|
{
|
||||||
@ -805,6 +815,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
shouldBatchBridgeOrders: true,
|
shouldBatchBridgeOrders: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.be.length(3);
|
expect(improvedOrders).to.be.length(3);
|
||||||
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
||||||
expect(orderFillSources).to.deep.eq([
|
expect(orderFillSources).to.deep.eq([
|
||||||
@ -913,12 +924,13 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct asset data', async () => {
|
it('generates bridge orders with correct asset data', async () => {
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
DEFAULT_OPTS,
|
DEFAULT_OPTS,
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
expect(getSourceFromAssetData(order.makerAssetData)).to.exist('');
|
expect(getSourceFromAssetData(order.makerAssetData)).to.exist('');
|
||||||
@ -937,24 +949,26 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct maker amount', async () => {
|
it('generates bridge orders with correct maker amount', async () => {
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
DEFAULT_OPTS,
|
DEFAULT_OPTS,
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const totalMakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.makerAssetAmount));
|
const totalMakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.makerAssetAmount));
|
||||||
expect(totalMakerAssetAmount).to.bignumber.gte(FILL_AMOUNT);
|
expect(totalMakerAssetAmount).to.bignumber.gte(FILL_AMOUNT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
||||||
const bridgeSlippage = _.random(0.1, true);
|
const bridgeSlippage = _.random(0.1, true);
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
// Pass in empty orders to prevent native orders from being used.
|
// Pass in empty orders to prevent native orders from being used.
|
||||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, bridgeSlippage },
|
{ ...DEFAULT_OPTS, bridgeSlippage },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
const expectedTakerAmount = order.fills[0].output;
|
const expectedTakerAmount = order.fills[0].output;
|
||||||
@ -971,11 +985,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Eth2Dai,
|
ERC20BridgeSource.Eth2Dai,
|
||||||
@ -1009,11 +1024,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
@ -1045,11 +1061,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
@ -1067,11 +1084,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const firstSources = [
|
const firstSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
@ -1092,11 +1110,12 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||||
const secondSources: ERC20BridgeSource[] = [];
|
const secondSources: ERC20BridgeSource[] = [];
|
||||||
@ -1112,7 +1131,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
});
|
});
|
||||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||||
FILL_AMOUNT,
|
FILL_AMOUNT,
|
||||||
{
|
{
|
||||||
@ -1121,6 +1140,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
shouldBatchBridgeOrders: true,
|
shouldBatchBridgeOrders: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.be.length(2);
|
expect(improvedOrders).to.be.length(2);
|
||||||
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
||||||
expect(orderFillSources).to.deep.eq([
|
expect(orderFillSources).to.deep.eq([
|
||||||
|
354
packages/asset-swapper/test/quote_report_generator_test.ts
Normal file
354
packages/asset-swapper/test/quote_report_generator_test.ts
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
// tslint:disable:custom-no-magic-numbers
|
||||||
|
import { orderHashUtils } from '@0x/order-utils';
|
||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as chai from 'chai';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import 'mocha';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
|
import { MarketOperation } from '../src/types';
|
||||||
|
import {
|
||||||
|
CollapsedFill,
|
||||||
|
DexSample,
|
||||||
|
ERC20BridgeSource,
|
||||||
|
NativeCollapsedFill,
|
||||||
|
} from '../src/utils/market_operation_utils/types';
|
||||||
|
import { QuoteRequestor } from '../src/utils/quote_requestor';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BridgeReportSource,
|
||||||
|
NativeOrderbookReportSource,
|
||||||
|
NativeRFQTReportSource,
|
||||||
|
QuoteReportGenerator,
|
||||||
|
QuoteReportSource,
|
||||||
|
} from './../src/utils/quote_report_generator';
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { testOrderFactory } from './utils/test_order_factory';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
const collapsedFillFromNativeOrder = (order: SignedOrder): NativeCollapsedFill => {
|
||||||
|
return {
|
||||||
|
source: ERC20BridgeSource.Native,
|
||||||
|
input: order.takerAssetAmount,
|
||||||
|
output: order.makerAssetAmount,
|
||||||
|
fillData: {
|
||||||
|
order: {
|
||||||
|
...order,
|
||||||
|
fillableMakerAssetAmount: new BigNumber(1),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(1),
|
||||||
|
fillableTakerFeeAmount: new BigNumber(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subFills: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('QuoteReportGenerator', async () => {
|
||||||
|
describe('generateReport', async () => {
|
||||||
|
it('should generate report properly for sell', () => {
|
||||||
|
const marketOperation: MarketOperation = MarketOperation.Sell;
|
||||||
|
|
||||||
|
const kyberSample1: DexSample = {
|
||||||
|
source: ERC20BridgeSource.Kyber,
|
||||||
|
input: new BigNumber(10000),
|
||||||
|
output: new BigNumber(10001),
|
||||||
|
};
|
||||||
|
const kyberSample2: DexSample = {
|
||||||
|
source: ERC20BridgeSource.Kyber,
|
||||||
|
input: new BigNumber(10003),
|
||||||
|
output: new BigNumber(10004),
|
||||||
|
};
|
||||||
|
const uniswapSample1: DexSample = {
|
||||||
|
source: ERC20BridgeSource.UniswapV2,
|
||||||
|
input: new BigNumber(10003),
|
||||||
|
output: new BigNumber(10004),
|
||||||
|
};
|
||||||
|
const uniswapSample2: DexSample = {
|
||||||
|
source: ERC20BridgeSource.UniswapV2,
|
||||||
|
input: new BigNumber(10005),
|
||||||
|
output: new BigNumber(10006),
|
||||||
|
};
|
||||||
|
const dexQuotes: DexSample[] = [kyberSample1, kyberSample2, uniswapSample1, uniswapSample2];
|
||||||
|
|
||||||
|
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||||
|
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'orderbookOrder1',
|
||||||
|
takerAssetAmount: orderbookOrder1FillableAmount,
|
||||||
|
});
|
||||||
|
const orderbookOrder2FillableAmount = new BigNumber(99);
|
||||||
|
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'orderbookOrder2',
|
||||||
|
takerAssetAmount: orderbookOrder2FillableAmount.plus(99),
|
||||||
|
});
|
||||||
|
const rfqtOrder1FillableAmount = new BigNumber(100);
|
||||||
|
const rfqtOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'rfqtOrder1',
|
||||||
|
takerAssetAmount: rfqtOrder1FillableAmount,
|
||||||
|
});
|
||||||
|
const rfqtOrder2FillableAmount = new BigNumber(1001);
|
||||||
|
const rfqtOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'rfqtOrder2',
|
||||||
|
takerAssetAmount: rfqtOrder2FillableAmount.plus(100),
|
||||||
|
});
|
||||||
|
const nativeOrders: SignedOrder[] = [orderbookOrder1, rfqtOrder1, rfqtOrder2, orderbookOrder2];
|
||||||
|
const orderFillableAmounts: BigNumber[] = [
|
||||||
|
orderbookOrder1FillableAmount,
|
||||||
|
rfqtOrder1FillableAmount,
|
||||||
|
rfqtOrder2FillableAmount,
|
||||||
|
orderbookOrder2FillableAmount,
|
||||||
|
];
|
||||||
|
|
||||||
|
// generate path
|
||||||
|
const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [] };
|
||||||
|
const kyber2Fill: CollapsedFill = { ...kyberSample2, subFills: [] };
|
||||||
|
const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2);
|
||||||
|
const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2);
|
||||||
|
const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, kyber2Fill];
|
||||||
|
|
||||||
|
// quote generator mock
|
||||||
|
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
||||||
|
quoteRequestor
|
||||||
|
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(orderbookOrder2)))
|
||||||
|
.returns(() => {
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||||
|
quoteRequestor
|
||||||
|
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder1)))
|
||||||
|
.returns(() => {
|
||||||
|
return 'https://rfqt1.provider.club';
|
||||||
|
})
|
||||||
|
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||||
|
quoteRequestor
|
||||||
|
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder2)))
|
||||||
|
.returns(() => {
|
||||||
|
return 'https://rfqt2.provider.club';
|
||||||
|
})
|
||||||
|
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||||
|
|
||||||
|
const orderReport = new QuoteReportGenerator(
|
||||||
|
marketOperation,
|
||||||
|
dexQuotes,
|
||||||
|
nativeOrders,
|
||||||
|
orderFillableAmounts,
|
||||||
|
pathGenerated,
|
||||||
|
quoteRequestor.object,
|
||||||
|
).generateReport();
|
||||||
|
|
||||||
|
const rfqtOrder1Source: NativeRFQTReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: rfqtOrder1.makerAssetAmount,
|
||||||
|
takerAmount: rfqtOrder1.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(rfqtOrder1),
|
||||||
|
nativeOrder: rfqtOrder1,
|
||||||
|
fillableTakerAmount: rfqtOrder1FillableAmount,
|
||||||
|
isRfqt: true,
|
||||||
|
makerUri: 'https://rfqt1.provider.club',
|
||||||
|
};
|
||||||
|
const rfqtOrder2Source: NativeRFQTReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: rfqtOrder2.makerAssetAmount,
|
||||||
|
takerAmount: rfqtOrder2.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(rfqtOrder2),
|
||||||
|
nativeOrder: rfqtOrder2,
|
||||||
|
fillableTakerAmount: rfqtOrder2FillableAmount,
|
||||||
|
isRfqt: true,
|
||||||
|
makerUri: 'https://rfqt2.provider.club',
|
||||||
|
};
|
||||||
|
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||||
|
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||||
|
nativeOrder: orderbookOrder1,
|
||||||
|
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||||
|
isRfqt: false,
|
||||||
|
};
|
||||||
|
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||||
|
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||||
|
nativeOrder: orderbookOrder2,
|
||||||
|
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||||
|
isRfqt: false,
|
||||||
|
};
|
||||||
|
const uniswap1Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||||
|
makerAmount: uniswapSample1.output,
|
||||||
|
takerAmount: uniswapSample1.input,
|
||||||
|
};
|
||||||
|
const uniswap2Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||||
|
makerAmount: uniswapSample2.output,
|
||||||
|
takerAmount: uniswapSample2.input,
|
||||||
|
};
|
||||||
|
const kyber1Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Kyber,
|
||||||
|
makerAmount: kyberSample1.output,
|
||||||
|
takerAmount: kyberSample1.input,
|
||||||
|
};
|
||||||
|
const kyber2Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Kyber,
|
||||||
|
makerAmount: kyberSample2.output,
|
||||||
|
takerAmount: kyberSample2.input,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||||
|
kyber1Source,
|
||||||
|
kyber2Source,
|
||||||
|
uniswap1Source,
|
||||||
|
uniswap2Source,
|
||||||
|
orderbookOrder1Source,
|
||||||
|
rfqtOrder1Source,
|
||||||
|
rfqtOrder2Source,
|
||||||
|
orderbookOrder2Source,
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||||
|
|
||||||
|
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||||
|
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||||
|
expect(actualSourcesConsidered).to.eql(
|
||||||
|
expectedSourceConsidered,
|
||||||
|
`sourceConsidered incorrect at index ${idx}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedSourcesDelivered: QuoteReportSource[] = [
|
||||||
|
rfqtOrder2Source,
|
||||||
|
orderbookOrder2Source,
|
||||||
|
uniswap2Source,
|
||||||
|
kyber2Source,
|
||||||
|
];
|
||||||
|
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||||
|
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||||
|
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||||
|
|
||||||
|
// remove fillable values
|
||||||
|
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||||
|
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||||
|
'fillableMakerAssetAmount',
|
||||||
|
'fillableTakerAssetAmount',
|
||||||
|
'fillableTakerFeeAmount',
|
||||||
|
]) as SignedOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(actualSourceDelivered).to.eql(
|
||||||
|
expectedSourceDelivered,
|
||||||
|
`sourceDelivered incorrect at index ${idx}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
quoteRequestor.verifyAll();
|
||||||
|
});
|
||||||
|
it('should handle properly for buy without quoteRequestor', () => {
|
||||||
|
const marketOperation: MarketOperation = MarketOperation.Buy;
|
||||||
|
const kyberSample1: DexSample = {
|
||||||
|
source: ERC20BridgeSource.Kyber,
|
||||||
|
input: new BigNumber(10000),
|
||||||
|
output: new BigNumber(10001),
|
||||||
|
};
|
||||||
|
const uniswapSample1: DexSample = {
|
||||||
|
source: ERC20BridgeSource.UniswapV2,
|
||||||
|
input: new BigNumber(10003),
|
||||||
|
output: new BigNumber(10004),
|
||||||
|
};
|
||||||
|
const dexQuotes: DexSample[] = [kyberSample1, uniswapSample1];
|
||||||
|
|
||||||
|
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||||
|
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'orderbookOrder1',
|
||||||
|
takerAssetAmount: orderbookOrder1FillableAmount.plus(101),
|
||||||
|
});
|
||||||
|
const orderbookOrder2FillableAmount = new BigNumber(5000);
|
||||||
|
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
signature: 'orderbookOrder2',
|
||||||
|
takerAssetAmount: orderbookOrder2FillableAmount.plus(101),
|
||||||
|
});
|
||||||
|
const nativeOrders: SignedOrder[] = [orderbookOrder1, orderbookOrder2];
|
||||||
|
const orderFillableAmounts: BigNumber[] = [orderbookOrder1FillableAmount, orderbookOrder2FillableAmount];
|
||||||
|
|
||||||
|
// generate path
|
||||||
|
const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1);
|
||||||
|
const uniswap1Fill: CollapsedFill = { ...uniswapSample1, subFills: [] };
|
||||||
|
const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [] };
|
||||||
|
const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, kyber1Fill];
|
||||||
|
|
||||||
|
const orderReport = new QuoteReportGenerator(
|
||||||
|
marketOperation,
|
||||||
|
dexQuotes,
|
||||||
|
nativeOrders,
|
||||||
|
orderFillableAmounts,
|
||||||
|
pathGenerated,
|
||||||
|
).generateReport();
|
||||||
|
|
||||||
|
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||||
|
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||||
|
nativeOrder: orderbookOrder1,
|
||||||
|
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||||
|
isRfqt: false,
|
||||||
|
};
|
||||||
|
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||||
|
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||||
|
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||||
|
nativeOrder: orderbookOrder2,
|
||||||
|
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||||
|
isRfqt: false,
|
||||||
|
};
|
||||||
|
const uniswap1Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||||
|
makerAmount: uniswapSample1.input,
|
||||||
|
takerAmount: uniswapSample1.output,
|
||||||
|
};
|
||||||
|
const kyber1Source: BridgeReportSource = {
|
||||||
|
liquiditySource: ERC20BridgeSource.Kyber,
|
||||||
|
makerAmount: kyberSample1.input,
|
||||||
|
takerAmount: kyberSample1.output,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||||
|
kyber1Source,
|
||||||
|
uniswap1Source,
|
||||||
|
orderbookOrder1Source,
|
||||||
|
orderbookOrder2Source,
|
||||||
|
];
|
||||||
|
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||||
|
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||||
|
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||||
|
expect(actualSourcesConsidered).to.eql(
|
||||||
|
expectedSourceConsidered,
|
||||||
|
`sourceConsidered incorrect at index ${idx}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedSourcesDelivered: QuoteReportSource[] = [orderbookOrder1Source, uniswap1Source, kyber1Source];
|
||||||
|
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||||
|
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||||
|
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||||
|
|
||||||
|
// remove fillable values
|
||||||
|
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||||
|
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||||
|
'fillableMakerAssetAmount',
|
||||||
|
'fillableTakerAssetAmount',
|
||||||
|
'fillableTakerFeeAmount',
|
||||||
|
]) as SignedOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(actualSourceDelivered).to.eql(
|
||||||
|
expectedSourceDelivered,
|
||||||
|
`sourceDelivered incorrect at index ${idx}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -28,6 +28,7 @@ export const docGenConfigs: DocGenConfigs = {
|
|||||||
TFillData: true,
|
TFillData: true,
|
||||||
IterableIterator: true,
|
IterableIterator: true,
|
||||||
Set: true,
|
Set: true,
|
||||||
|
Exclude: true,
|
||||||
},
|
},
|
||||||
// Some types are not explicitly part of the public interface like params, return values, etc... But we still
|
// Some types are not explicitly part of the public interface like params, return values, etc... But we still
|
||||||
// want them exported. E.g error enum types that can be thrown by methods. These must be manually added to this
|
// want them exported. E.g error enum types that can be thrown by methods. These must be manually added to this
|
||||||
|
Loading…
x
Reference in New Issue
Block a user