From 36bd8f68c9b0075c54ba3ab1659c0399a81adb6a Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Thu, 5 Nov 2020 12:46:06 -0800 Subject: [PATCH] =?UTF-8?q?adds=20amendments=20to=20the=20rollout=20featur?= =?UTF-8?q?e=20flag.=20Rollout=20indicative=20quote=E2=80=A6=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adds amendments to the rollout feature flag. Rollout indicative quotes indipendently from firm quotes. * Updates based on Brandon's feedback --- packages/asset-swapper/src/constants.ts | 5 +++- packages/asset-swapper/src/swap_quoter.ts | 3 ++- packages/asset-swapper/src/types.ts | 7 ++++- .../src/utils/market_operation_utils/index.ts | 26 ++++++++++--------- packages/asset-swapper/src/utils/utils.ts | 18 +++++++++++++ .../test/market_operation_utils_test.ts | 15 +++++++---- 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index 73775b08a5..1f75d917aa 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -90,7 +90,10 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = { const DEFAULT_RFQT_REQUEST_OPTS: Partial = { makerEndpointMaxResponseTimeMs: 1000, - isPriceAwareRFQEnabled: false, + priceAwareRFQFlag: { + isFirmPriceAwareEnabled: false, + isIndicativePriceAwareEnabled: false, + }, }; export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) => diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 909a39908c..38c0131ccf 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -41,6 +41,7 @@ import { ProtocolFeeUtils } from './utils/protocol_fee_utils'; import { QuoteRequestor } from './utils/quote_requestor'; import { sortingUtils } from './utils/sorting_utils'; import { SwapQuoteCalculator } from './utils/swap_quote_calculator'; +import { getPriceAwareRFQRolloutFlags } from './utils/utils'; import { ERC20BridgeSamplerContract } from './wrappers'; export class SwapQuoter { @@ -700,7 +701,7 @@ export class SwapQuoter { if ( opts.rfqt && // This is an RFQT-enabled API request - !opts.rfqt.isPriceAwareRFQEnabled && // If Price-aware RFQ is enabled, firm quotes are requested later on in the process. + !getPriceAwareRFQRolloutFlags(opts.rfqt.priceAwareRFQFlag).isFirmPriceAwareEnabled && // If Price-aware RFQ is enabled, firm quotes are requested later on in the process. opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote opts.rfqt.apiKey && this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 283051be01..a31812d709 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -234,6 +234,11 @@ export type SwapQuoteOrdersBreakdown = Partial< } >; +export interface PriceAwareRFQFlags { + isIndicativePriceAwareEnabled: boolean; + isFirmPriceAwareEnabled: boolean; +} + /** * nativeExclusivelyRFQT: if set to `true`, Swap quote will exclude Open Orderbook liquidity. * If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources` @@ -256,7 +261,7 @@ export interface RfqtRequestOpts { * the feature flag. When that time comes, follow this PR to "undo" the feature flag: * https://github.com/0xProject/0x-monorepo/pull/2735 */ - isPriceAwareRFQEnabled?: boolean; + priceAwareRFQFlag?: PriceAwareRFQFlags; } /** 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 d151c9f486..d9b58e2cdc 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -6,6 +6,7 @@ import * as _ from 'lodash'; import { AssetSwapperContractAddresses, MarketOperation, Omit } from '../../types'; import { QuoteRequestor } from '../quote_requestor'; +import { getPriceAwareRFQRolloutFlags } from '../utils'; import { generateQuoteReport, QuoteReport } from './../quote_report_generator'; import { @@ -214,7 +215,8 @@ export class MarketOperationUtils { ), ); - const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled; + const isPriceAwareRfqEnabled = + _opts.rfqt && getPriceAwareRFQRolloutFlags(_opts.rfqt.priceAwareRFQFlag).isIndicativePriceAwareEnabled; const rfqtPromise = !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) ? getRfqtIndicativeQuotesAsync( @@ -362,7 +364,8 @@ export class MarketOperationUtils { this._liquidityProviderRegistry, ), ); - const isPriceAwareRfqEnabled = _opts.rfqt && _opts.rfqt.isPriceAwareRFQEnabled; + const isPriceAwareRfqEnabled = + _opts.rfqt && getPriceAwareRFQRolloutFlags(_opts.rfqt.priceAwareRFQFlag).isIndicativePriceAwareEnabled; const rfqtPromise = !isPriceAwareRfqEnabled && quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) ? getRfqtIndicativeQuotesAsync( @@ -677,12 +680,7 @@ export class MarketOperationUtils { // If RFQ liquidity is enabled, make a request to check RFQ liquidity let comparisonPrice: BigNumber | undefined; const { rfqt } = _opts; - if ( - rfqt && - rfqt.isPriceAwareRFQEnabled && - rfqt.quoteRequestor && - marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native) - ) { + if (rfqt && rfqt.quoteRequestor && marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)) { // Calculate a suggested price. For now, this is simply the overall price of the aggregation. if (optimizerResult) { const totalMakerAmount = BigNumber.sum( @@ -706,8 +704,12 @@ export class MarketOperationUtils { } } - // 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 { isFirmPriceAwareEnabled, isIndicativePriceAwareEnabled } = getPriceAwareRFQRolloutFlags( + rfqt.priceAwareRFQFlag, + ); + + if (rfqt.isIndicative && isIndicativePriceAwareEnabled) { + // An indicative quote is beingh requested, and indicative quotes price-aware enabled. Make the RFQT request and then re-run the sampler if new orders come back. const indicativeQuotes = await getRfqtIndicativeQuotesAsync( nativeOrders[0].makerAssetData, nativeOrders[0].takerAssetData, @@ -726,8 +728,8 @@ export class MarketOperationUtils { optimizerOpts, ); } - } else { - // A firm quote is being requested. Ensure that `intentOnFilling` is enabled. + } else if (!rfqt.isIndicative && isFirmPriceAwareEnabled) { + // A firm quote is being requested, and firm quotes price-aware enabled. Ensure that `intentOnFilling` is enabled. if (rfqt.intentOnFilling) { // Extra validation happens when requesting a firm quote, such as ensuring that the takerAddress // is indeed valid. diff --git a/packages/asset-swapper/src/utils/utils.ts b/packages/asset-swapper/src/utils/utils.ts index bea2e48a40..b27fd9ba46 100644 --- a/packages/asset-swapper/src/utils/utils.ts +++ b/packages/asset-swapper/src/utils/utils.ts @@ -4,9 +4,27 @@ import { BigNumber, NULL_BYTES } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { constants } from '../constants'; +import { PriceAwareRFQFlags } from '../types'; // tslint:disable: no-unnecessary-type-assertion completed-docs +/** + * Returns 2 flags (one for firm quotes and another for indicative quotes) that serve as rollout flags for the price-aware RFQ feature. + * By default, indicative quotes should *always* go through the new price-aware flow. This means that all indicative RFQ requests made to + * market makers will contain the new price-aware `suggestedPrice` field. + * The `isPriceAwareRFQEnabled` feature object that is passed in by the 0x API will then control whether firm quotes go through price-aware RFQ. + * + * @param isPriceAwareRFQEnabled the feature flag that is passed in by the 0x API. + */ +export function getPriceAwareRFQRolloutFlags(priceAwareRFQFlags?: PriceAwareRFQFlags): PriceAwareRFQFlags { + return priceAwareRFQFlags !== undefined + ? priceAwareRFQFlags + : { + isFirmPriceAwareEnabled: false, + isIndicativePriceAwareEnabled: false, + }; +} + export function isSupportedAssetDataInOrders(orders: SignedOrder[]): boolean { const firstOrderMakerAssetData = !!orders[0] ? assetDataUtils.decodeAssetDataOrThrow(orders[0].makerAssetData) diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 64afc8321b..a6bfd80bea 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -17,6 +17,7 @@ import * as _ from 'lodash'; import * as TypeMoq from 'typemoq'; import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src'; +import { PriceAwareRFQFlags } from '../src/types'; import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/'; import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils'; import { @@ -66,6 +67,10 @@ const DEFAULT_EXCLUDED = [ const BUY_SOURCES = BUY_SOURCE_FILTER.sources; const SELL_SOURCES = SELL_SOURCE_FILTER.sources; const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = {}; +const PRICE_AWARE_RFQ_ENABLED: PriceAwareRFQFlags = { + isFirmPriceAwareEnabled: true, + isIndicativePriceAwareEnabled: true, +}; // tslint:disable: custom-no-magic-numbers promise-function-async describe('MarketOperationUtils tests', () => { @@ -891,7 +896,7 @@ describe('MarketOperationUtils tests', () => { apiKey: 'foo', takerAddress: randomAddress(), intentOnFilling: true, - isPriceAwareRFQEnabled: true, + priceAwareRFQFlag: PRICE_AWARE_RFQ_ENABLED, quoteRequestor: { requestRfqtFirmQuotesAsync: mockedQuoteRequestor.object.requestRfqtFirmQuotesAsync, } as any, @@ -931,7 +936,7 @@ describe('MarketOperationUtils tests', () => { apiKey: 'foo', takerAddress: randomAddress(), intentOnFilling: true, - isPriceAwareRFQEnabled: true, + priceAwareRFQFlag: PRICE_AWARE_RFQ_ENABLED, quoteRequestor: { requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, } as any, @@ -974,7 +979,7 @@ describe('MarketOperationUtils tests', () => { rfqt: { isIndicative: true, apiKey: 'foo', - isPriceAwareRFQEnabled: true, + priceAwareRFQFlag: PRICE_AWARE_RFQ_ENABLED, takerAddress: randomAddress(), intentOnFilling: true, quoteRequestor: { @@ -1033,7 +1038,7 @@ describe('MarketOperationUtils tests', () => { apiKey: 'foo', takerAddress: randomAddress(), intentOnFilling: true, - isPriceAwareRFQEnabled: true, + priceAwareRFQFlag: PRICE_AWARE_RFQ_ENABLED, quoteRequestor: { requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync, } as any, @@ -1089,7 +1094,7 @@ describe('MarketOperationUtils tests', () => { isIndicative: false, apiKey: 'foo', takerAddress: randomAddress(), - isPriceAwareRFQEnabled: true, + priceAwareRFQFlag: PRICE_AWARE_RFQ_ENABLED, intentOnFilling: true, quoteRequestor: { requestRfqtFirmQuotesAsync: requestor.object.requestRfqtFirmQuotesAsync,