From 8ec44b63b161e4b4e2a7274c289843cf532aa448 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 6 Jul 2020 21:06:43 -0700 Subject: [PATCH 01/50] WIP: add quoteReport response --- packages/asset-swapper/src/types.ts | 3 ++- .../src/utils/market_operation_utils/index.ts | 17 ++++++++------ .../src/utils/market_operation_utils/types.ts | 7 +++++- .../src/utils/quote_requestor.ts | 4 ++-- .../src/utils/swap_quote_calculator.ts | 22 +++++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index df145e9b19..5a9cd309c5 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -155,6 +155,7 @@ export interface SwapQuoteBase { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown; + quoteReport: string; } /** @@ -218,7 +219,7 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts { /** * Opts required to generate a SwapQuote with SwapQuoteCalculator */ -export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {} +export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts { } /** * A mapping from RFQ-T quote provider URLs to the trading pairs they support. 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 6f550a6310..2d458ae0db 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -22,6 +22,7 @@ import { ERC20BridgeSource, GetMarketOrdersOpts, OptimizedMarketOrder, + OptimizedOrdersAndQuoteReport, OrderDomain, } from './types'; @@ -71,7 +72,7 @@ export class MarketOperationUtils { nativeOrders: SignedOrder[], takerAmount: BigNumber, opts?: Partial, - ): Promise { + ): Promise { if (nativeOrders.length === 0) { throw new Error(AggregationError.EmptyOrders); } @@ -153,7 +154,7 @@ export class MarketOperationUtils { nativeOrders: SignedOrder[], makerAmount: BigNumber, opts?: Partial, - ): Promise { + ): Promise { if (nativeOrders.length === 0) { throw new Error(AggregationError.EmptyOrders); } @@ -238,6 +239,7 @@ export class MarketOperationUtils { * @param opts Options object. * @return orders. */ + // TODO: can we delete? public async getBatchMarketBuyOrdersAsync( batchNativeOrders: SignedOrder[][], makerAmounts: BigNumber[], @@ -302,7 +304,7 @@ export class MarketOperationUtils { feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, - }); + }).optimizedOrders; } catch (e) { // It's possible for one of the pairs to have no path // rather than throw NO_OPTIMAL_PATH we return undefined @@ -330,7 +332,7 @@ export class MarketOperationUtils { shouldBatchBridgeOrders?: boolean; liquidityProviderAddress?: string; multiBridgeAddress?: string; - }): OptimizedMarketOrder[] { + }): OptimizedOrdersAndQuoteReport { const { inputToken, outputToken, side, inputAmount } = opts; const maxFallbackSlippage = opts.maxFallbackSlippage || 0; // Convert native orders and dex quotes into fill paths. @@ -373,8 +375,8 @@ export class MarketOperationUtils { const [last, penultimateIfExists] = optimalPath.slice().reverse(); const lastNativeFillIfExists = last.source === ERC20BridgeSource.Native && - penultimateIfExists && - penultimateIfExists.source !== ERC20BridgeSource.Native + penultimateIfExists && + penultimateIfExists.source !== ERC20BridgeSource.Native ? last : undefined; // By prepending native paths to the front they cannot split on-chain sources and incur @@ -383,7 +385,7 @@ export class MarketOperationUtils { optimalPath = [...nativeSubPath.filter(f => f !== lastNativeFillIfExists), ...nonNativeOptimalPath]; } } - return createOrdersFromPath(optimalPath, { + const optimizedOrders = createOrdersFromPath(optimalPath, { side, inputToken, outputToken, @@ -394,6 +396,7 @@ export class MarketOperationUtils { multiBridgeAddress: opts.multiBridgeAddress, shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders, }); + return { optimizedOrders, quoteReport: 'TODO' }; } private _optionalSources(): ERC20BridgeSource[] { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index c746f469a6..0e80f28510 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -42,7 +42,7 @@ export enum ERC20BridgeSource { } // Internal `fillData` field for `Fill` objects. -export interface FillData {} +export interface FillData { } // `FillData` for native fills. export interface NativeFillData extends FillData { @@ -218,3 +218,8 @@ export interface FakeBuyOpts { targetSlippageBps: BigNumber; maxIterations: BigNumber; } + +export interface OptimizedOrdersAndQuoteReport { + optimizedOrders: OptimizedMarketOrder[]; + quoteReport: string; +} diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index efc7378a86..02ffdaa321 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -93,7 +93,7 @@ export class QuoteRequestor { private readonly _infoLogger: LogFunction = (obj, msg) => logUtils.log(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`), private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs, - ) {} + ) { } public async requestRfqtFirmQuotesAsync( makerAssetData: string, @@ -344,7 +344,7 @@ export class QuoteRequestor { this._warningLogger( convertIfAxiosError(err), `Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${url} for API key ${ - options.apiKey + options.apiKey } for taker address ${options.takerAddress}`, ); return undefined; diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index ffa5d2aaa7..c0f8355970 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -17,7 +17,7 @@ import { import { MarketOperationUtils } from './market_operation_utils'; import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; -import { GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types'; +import { GetMarketOrdersOpts, OptimizedMarketOrder, OptimizedOrdersAndQuoteReport } from './market_operation_utils/types'; import { isSupportedAssetDataInOrders } from './utils'; import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation'; @@ -94,7 +94,7 @@ export class SwapQuoteCalculator { return createSwapQuote( makerAssetData, takerAssetData, - orders, + { optimizedOrders: orders, quoteReport: 'TODO' }, operation, assetFillAmounts[i], gasPrice, @@ -120,7 +120,7 @@ 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 - let resultOrders: OptimizedMarketOrder[] = []; + let result: OptimizedOrdersAndQuoteReport | undefined; { // Scale fees by gas price. @@ -135,16 +135,16 @@ export class SwapQuoteCalculator { if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) { // HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable - resultOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)); + result = { optimizedOrders: prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)), quoteReport: 'TODO' }; } else { if (operation === MarketOperation.Buy) { - resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync( + result = await this._marketOperationUtils.getMarketBuyOrdersAsync( prunedOrders, assetFillAmount, _opts, ); } else { - resultOrders = await this._marketOperationUtils.getMarketSellOrdersAsync( + result = await this._marketOperationUtils.getMarketSellOrdersAsync( prunedOrders, assetFillAmount, _opts, @@ -158,7 +158,7 @@ export class SwapQuoteCalculator { return createSwapQuote( makerAssetData, takerAssetData, - resultOrders, + result, operation, assetFillAmount, gasPrice, @@ -170,12 +170,14 @@ export class SwapQuoteCalculator { function createSwapQuote( makerAssetData: string, takerAssetData: string, - resultOrders: OptimizedMarketOrder[], + result: OptimizedOrdersAndQuoteReport, operation: MarketOperation, assetFillAmount: BigNumber, gasPrice: BigNumber, gasSchedule: { [source: string]: number }, ): SwapQuote { + const resultOrders = result.optimizedOrders; + const bestCaseFillResult = simulateBestCaseFill({ gasPrice, orders: resultOrders, @@ -192,7 +194,7 @@ function createSwapQuote( opts: { gasSchedule }, }); - const quoteBase: SwapQuoteBase = { + const quoteBase = { takerAssetData, makerAssetData, gasPrice, @@ -207,12 +209,14 @@ function createSwapQuote( ...quoteBase, type: MarketOperation.Buy, makerAssetFillAmount: assetFillAmount, + quoteReport: result.quoteReport, }; } else { return { ...quoteBase, type: MarketOperation.Sell, takerAssetFillAmount: assetFillAmount, + quoteReport: result.quoteReport, }; } } From e58c25aa1813df4e016e4d1c39a882c7f07f453b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 6 Jul 2020 21:27:20 -0700 Subject: [PATCH 02/50] hold onto maker uris when fetching responses --- .../src/utils/quote_requestor.ts | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index 02ffdaa321..0993530f5f 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -105,7 +105,7 @@ export class QuoteRequestor { const _opts: RfqtRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options }; assertTakerAddressOrThrow(_opts.takerAddress); - const firmQuotes = await this._getQuotesAsync( // not yet BigNumber + const firmQuoteResponses = await this._getQuotesAsync( // not yet BigNumber makerAssetData, takerAssetData, assetFillAmount, @@ -114,41 +114,38 @@ export class QuoteRequestor { '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 { - const hasValidSchema = this._schemaValidator.isValid(order, schemas.signedOrderSchema); + const hasValidSchema = this._schemaValidator.isValid(orderWithStringInts, schemas.signedOrderSchema); if (!hasValidSchema) { - throw new Error('order not valid'); + throw new Error('Order not valid'); } } catch (err) { - this._warningLogger(order, `Invalid RFQ-t order received, filtering out. ${err.message}`); - return false; + this._warningLogger(orderWithStringInts, `Invalid RFQ-t order received, filtering out. ${err.message}`); + return; } if ( !hasExpectedAssetData( makerAssetData, takerAssetData, - order.makerAssetData.toLowerCase(), - order.takerAssetData.toLowerCase(), + orderWithStringInts.makerAssetData.toLowerCase(), + orderWithStringInts.takerAssetData.toLowerCase(), ) ) { - this._warningLogger(order, 'Unexpected asset data in RFQ-T order, filtering out'); - return false; + this._warningLogger(orderWithStringInts, 'Unexpected asset data in RFQ-T order, filtering out'); + return; } - if (order.takerAddress.toLowerCase() !== _opts.takerAddress.toLowerCase()) { - this._warningLogger(order, 'Unexpected takerAddress in RFQ-T order, filtering out'); - return false; + if (orderWithStringInts.takerAddress.toLowerCase() !== _opts.takerAddress.toLowerCase()) { + this._warningLogger(orderWithStringInts, 'Unexpected takerAddress in RFQ-T order, filtering out'); + return; } - return true; - }); - - const validatedOrders: SignedOrder[] = validatedOrdersWithStringInts.map(orderWithStringInts => { - return { + const orderWithBigNumberInts: SignedOrder = { ...orderWithStringInts, makerAssetAmount: new BigNumber(orderWithStringInts.makerAssetAmount), takerAssetAmount: new BigNumber(orderWithStringInts.takerAssetAmount), @@ -157,17 +154,17 @@ export class QuoteRequestor { expirationTimeSeconds: new BigNumber(orderWithStringInts.expirationTimeSeconds), salt: new BigNumber(orderWithStringInts.salt), }; - }); - const orders = validatedOrders.filter(order => { - if (orderCalculationUtils.willOrderExpire(order, this._expiryBufferMs / constants.ONE_SECOND_MS)) { - this._warningLogger(order, 'Expiry too soon in RFQ-T order, filtering out'); - return false; + if (orderCalculationUtils.willOrderExpire(orderWithBigNumberInts, 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 })); + // Passed all validation, add it to result + result.push({ signedOrder: orderWithBigNumberInts }); + return; + }); + return result; } public async requestRfqtIndicativeQuotesAsync( @@ -189,7 +186,8 @@ export class QuoteRequestor { 'indicative', ); - const validResponsesWithStringInts = responsesWithStringInts.filter(response => { + const validResponsesWithStringInts = responsesWithStringInts.filter(result => { + const response = result.response; if (!this._isValidRfqtIndicativeQuoteResponse(response)) { this._warningLogger(response, 'Invalid RFQ-T indicative quote received, filtering out'); return false; @@ -203,7 +201,8 @@ export class QuoteRequestor { return true; }); - const validResponses = validResponsesWithStringInts.map(response => { + const validResponses = validResponsesWithStringInts.map(result => { + const response = result.response; return { ...response, makerAssetAmount: new BigNumber(response.makerAssetAmount), @@ -278,10 +277,9 @@ export class QuoteRequestor { marketOperation: MarketOperation, options: RfqtRequestOpts, quoteType: 'firm' | 'indicative', - ): Promise { - // create an array of promises for quote responses, using "undefined" - // as a placeholder for failed requests. - const responsesIfDefined: Array> = await Promise.all( + ): Promise> { + const result: Array<{ response: ResponseT; makerUri: string }> = []; + await Promise.all( Object.keys(this._rfqtAssetOfferings).map(async url => { if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) { const requestParamsWithBigNumbers = { @@ -330,7 +328,7 @@ export class QuoteRequestor { }, }, }); - return response; + result.push({ response: response.data, makerUri: url }); } catch (err) { this._infoLogger({ rfqtMakerInteraction: { @@ -347,17 +345,10 @@ export class QuoteRequestor { options.apiKey } for taker address ${options.takerAddress}`, ); - return undefined; } } - return undefined; }), ); - - const responses = responsesIfDefined.filter( - (respIfDefd): respIfDefd is AxiosResponse => respIfDefd !== undefined, - ); - - return responses.map(response => response.data); + return result; } } From e79db7de89fca79f6a1c1359f793a0b189f88bad Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 6 Jul 2020 22:07:33 -0700 Subject: [PATCH 03/50] new quotereporter type --- .../src/utils/quote_report_generator.ts | 147 ++++++++++++++++++ .../src/utils/quote_requestor.ts | 13 +- 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 packages/asset-swapper/src/utils/quote_report_generator.ts diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts new file mode 100644 index 0000000000..cffee43fa8 --- /dev/null +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -0,0 +1,147 @@ +import { orderHashUtils } from '@0x/order-utils'; +import { BigNumber, NULL_ADDRESS } 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; + 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[]; +} + +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(ds => this._dexSampleToReportSource(ds)); + const nativeOrderSourcesConsidered = this._nativeOrders.map(no => this._nativeOrderToReportSource(no)); + + const sourcesConsidered = [ + ...dexReportSourcesConsidered, + ...nativeOrderSourcesConsidered, + ]; + const sourcesDelivered = this._collapsedFills.map(collapsedFill => { + const foundNativeOrder = (collapsedFill as NativeCollapsedFill).nativeOrder; + 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; + } + } +} diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index 0993530f5f..6430064c8f 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -1,5 +1,5 @@ import { schemas, SchemaValidator } from '@0x/json-schemas'; -import { assetDataUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils'; +import { assetDataUtils, orderCalculationUtils, SignedOrder, orderHashUtils } from '@0x/order-utils'; import { RFQTFirmQuote, RFQTIndicativeQuote, TakerRequest } from '@0x/quote-server'; import { ERC20AssetData } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; @@ -85,6 +85,7 @@ export type LogFunction = (obj: object, msg?: string, ...args: any[]) => void; export class QuoteRequestor { private readonly _schemaValidator: SchemaValidator = new SchemaValidator(); + private readonly _orderHashToMakerUri: { [orderHash: string]: string } = {}; constructor( private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings, @@ -160,6 +161,9 @@ export class QuoteRequestor { return; } + // 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; @@ -222,6 +226,13 @@ export class QuoteRequestor { 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 { const hasValidMakerAssetAmount = response.makerAssetAmount !== undefined && From f64a42ebb5c47e5892970aef031bac217d3493de Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 6 Jul 2020 22:18:22 -0700 Subject: [PATCH 04/50] return quote report --- packages/asset-swapper/src/types.ts | 3 ++- .../src/utils/market_operation_utils/index.ts | 12 +++++++++++- .../src/utils/market_operation_utils/types.ts | 3 ++- .../src/utils/quote_report_generator.ts | 2 +- .../asset-swapper/src/utils/swap_quote_calculator.ts | 11 +++++++++-- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 5a9cd309c5..a09a074b07 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -3,6 +3,7 @@ import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { GetMarketOrdersOpts, OptimizedMarketOrder } from './utils/market_operation_utils/types'; +import { QuoteReport } from './utils/quote_report_generator'; import { LogFunction } from './utils/quote_requestor'; /** @@ -155,7 +156,7 @@ export interface SwapQuoteBase { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown; - quoteReport: string; + quoteReport: QuoteReport; } /** 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 2d458ae0db..0914cfccd9 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -2,10 +2,13 @@ import { ContractAddresses } from '@0x/contract-addresses'; import { RFQTIndicativeQuote } from '@0x/quote-server'; import { SignedOrder } from '@0x/types'; import { BigNumber, NULL_ADDRESS } from '@0x/utils'; +import * as _ from 'lodash'; import { MarketOperation } from '../../types'; +import { QuoteRequestor } from '../quote_requestor'; 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 { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills'; import { @@ -121,6 +124,7 @@ export class MarketOperationUtils { [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes], rfqtIndicativeQuotes, ] = await Promise.all([samplerPromise, rfqtPromise]); + return this._generateOptimizedOrders({ orderFillableAmounts, nativeOrders, @@ -139,6 +143,7 @@ export class MarketOperationUtils { feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, + quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, }); } @@ -225,6 +230,7 @@ export class MarketOperationUtils { feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, + quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, }); } @@ -332,6 +338,7 @@ export class MarketOperationUtils { shouldBatchBridgeOrders?: boolean; liquidityProviderAddress?: string; multiBridgeAddress?: string; + quoteRequestor?: QuoteRequestor; }): OptimizedOrdersAndQuoteReport { const { inputToken, outputToken, side, inputAmount } = opts; const maxFallbackSlippage = opts.maxFallbackSlippage || 0; @@ -396,7 +403,10 @@ export class MarketOperationUtils { multiBridgeAddress: opts.multiBridgeAddress, shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders, }); - return { optimizedOrders, quoteReport: 'TODO' }; + + const collapsedPaths = _.flatten(optimizedOrders.map(o => o.fills)); + const quoteReport = new QuoteReportGenerator(opts.side, _.flatten(opts.dexQuotes), opts.nativeOrders, opts.orderFillableAmounts, collapsedPaths, opts.quoteRequestor).generateReport(); + return { optimizedOrders, quoteReport }; } private _optionalSources(): ERC20BridgeSource[] { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 0e80f28510..d08756b94d 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -3,6 +3,7 @@ import { BigNumber } from '@0x/utils'; import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types'; import { QuoteRequestor } from '../../utils/quote_requestor'; +import { QuoteReport } from '../quote_report_generator'; /** * Order domain keys: chainId and exchange @@ -221,5 +222,5 @@ export interface FakeBuyOpts { export interface OptimizedOrdersAndQuoteReport { optimizedOrders: OptimizedMarketOrder[]; - quoteReport: string; + quoteReport: QuoteReport; } diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index cffee43fa8..bb201d8499 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -44,7 +44,7 @@ export class QuoteReportGenerator { private readonly _collapsedFills: CollapsedFill[]; private readonly _quoteRequestor?: QuoteRequestor; - constructor(marketOperation: MarketOperation, dexQuotes: DexSample[], nativeOrders: SignedOrder[], orderFillableAmounts: BigNumber[], collapsedFills: CollapsedFill[], quoteRequestor: QuoteRequestor) { + constructor(marketOperation: MarketOperation, dexQuotes: DexSample[], nativeOrders: SignedOrder[], orderFillableAmounts: BigNumber[], collapsedFills: CollapsedFill[], quoteRequestor?: QuoteRequestor) { this._dexQuotes = dexQuotes; this._nativeOrders = nativeOrders; this._marketOperation = marketOperation; diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index c0f8355970..283288504c 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -20,6 +20,7 @@ import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_opera import { GetMarketOrdersOpts, OptimizedMarketOrder, OptimizedOrdersAndQuoteReport } from './market_operation_utils/types'; import { isSupportedAssetDataInOrders } from './utils'; +import { QuoteReport } from './quote_report_generator'; import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation'; // TODO(dave4506) How do we want to reintroduce InsufficientAssetLiquidityError? @@ -87,6 +88,7 @@ export class SwapQuoteCalculator { assetFillAmounts, opts, ); + const blankQuoteReport = { sourcesConsidered: [], sourcesDelivered: [] }; // TODO: better solution here const batchSwapQuotes = await Promise.all( batchSignedOrders.map(async (orders, i) => { if (orders) { @@ -94,11 +96,12 @@ export class SwapQuoteCalculator { return createSwapQuote( makerAssetData, takerAssetData, - { optimizedOrders: orders, quoteReport: 'TODO' }, + { optimizedOrders: orders, quoteReport: blankQuoteReport }, operation, assetFillAmounts[i], gasPrice, opts.gasSchedule, + blankQuoteReport, ); } else { return undefined; @@ -134,8 +137,9 @@ export class SwapQuoteCalculator { : { assetProxyId: '' }; if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) { + const blankQuoteReport = { sourcesConsidered: [], sourcesDelivered: [] }; // TODO: better solution here // HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable - result = { optimizedOrders: prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)), quoteReport: 'TODO' }; + result = { optimizedOrders: prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)), quoteReport: blankQuoteReport }; } else { if (operation === MarketOperation.Buy) { result = await this._marketOperationUtils.getMarketBuyOrdersAsync( @@ -163,6 +167,7 @@ export class SwapQuoteCalculator { assetFillAmount, gasPrice, opts.gasSchedule, + result.quoteReport, ); } } @@ -175,6 +180,7 @@ function createSwapQuote( assetFillAmount: BigNumber, gasPrice: BigNumber, gasSchedule: { [source: string]: number }, + quoteReport: QuoteReport, ): SwapQuote { const resultOrders = result.optimizedOrders; @@ -202,6 +208,7 @@ function createSwapQuote( worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult), sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource), orders: resultOrders, + quoteReport, }; if (operation === MarketOperation.Buy) { From e78c8038c7f0e5dbfaaf92edde2b24928133f9d6 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 6 Jul 2020 22:24:02 -0700 Subject: [PATCH 05/50] take out delete note --- packages/asset-swapper/src/utils/market_operation_utils/index.ts | 1 - 1 file changed, 1 deletion(-) 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 0914cfccd9..13c7618e59 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -245,7 +245,6 @@ export class MarketOperationUtils { * @param opts Options object. * @return orders. */ - // TODO: can we delete? public async getBatchMarketBuyOrdersAsync( batchNativeOrders: SignedOrder[][], makerAmounts: BigNumber[], From 934fbca8604ec09135617e755834dd69eec88081 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 7 Jul 2020 11:00:03 -0700 Subject: [PATCH 06/50] make QuoteReport optional instead of forcing it for bash operaitons, fix tests so build works --- packages/asset-swapper/src/types.ts | 2 +- .../src/utils/market_operation_utils/index.ts | 6 +- .../src/utils/quote_report_generator.ts | 1 - .../src/utils/swap_quote_calculator.ts | 38 ++++++----- .../test/market_operation_utils_test.ts | 66 ++++++++++++------- 5 files changed, 66 insertions(+), 47 deletions(-) diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index a09a074b07..1ca417cb8f 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -156,7 +156,7 @@ export interface SwapQuoteBase { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown; - quoteReport: QuoteReport; + quoteReport?: QuoteReport; } /** 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 13c7618e59..6ed304ff85 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -153,7 +153,7 @@ export class MarketOperationUtils { * @param nativeOrders Native orders. * @param makerAmount Amount of maker asset to buy. * @param opts Options object. - * @return orders. + * @return object with optimized orders and a QuoteReport */ public async getMarketBuyOrdersAsync( nativeOrders: SignedOrder[], @@ -402,9 +402,7 @@ export class MarketOperationUtils { multiBridgeAddress: opts.multiBridgeAddress, shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders, }); - - const collapsedPaths = _.flatten(optimizedOrders.map(o => o.fills)); - const quoteReport = new QuoteReportGenerator(opts.side, _.flatten(opts.dexQuotes), opts.nativeOrders, opts.orderFillableAmounts, collapsedPaths, opts.quoteRequestor).generateReport(); + 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 }; } diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index bb201d8499..773916a8e4 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -140,7 +140,6 @@ export class QuoteReportGenerator { ...nativeOrderBase, isRfqt: false, }; - return regularNativeOrder; } } diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index 283288504c..c64fbad0a7 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -88,7 +88,7 @@ export class SwapQuoteCalculator { assetFillAmounts, opts, ); - const blankQuoteReport = { sourcesConsidered: [], sourcesDelivered: [] }; // TODO: better solution here + const batchSwapQuotes = await Promise.all( batchSignedOrders.map(async (orders, i) => { if (orders) { @@ -96,12 +96,11 @@ export class SwapQuoteCalculator { return createSwapQuote( makerAssetData, takerAssetData, - { optimizedOrders: orders, quoteReport: blankQuoteReport }, + orders, operation, assetFillAmounts[i], gasPrice, opts.gasSchedule, - blankQuoteReport, ); } else { return undefined; @@ -123,7 +122,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 - let result: OptimizedOrdersAndQuoteReport | undefined; + let optimizedOrders: OptimizedMarketOrder[] | undefined; + let quoteReport: QuoteReport | undefined; { // Scale fees by gas price. @@ -139,20 +139,24 @@ export class SwapQuoteCalculator { if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) { const blankQuoteReport = { sourcesConsidered: [], sourcesDelivered: [] }; // TODO: better solution here // HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable - result = { optimizedOrders: prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)), quoteReport: blankQuoteReport }; + optimizedOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)); } else { if (operation === MarketOperation.Buy) { - result = await this._marketOperationUtils.getMarketBuyOrdersAsync( + const buyResult = await this._marketOperationUtils.getMarketBuyOrdersAsync( prunedOrders, assetFillAmount, _opts, ); + optimizedOrders = buyResult.optimizedOrders; + quoteReport = buyResult.quoteReport; } else { - result = await this._marketOperationUtils.getMarketSellOrdersAsync( + const sellResult = await this._marketOperationUtils.getMarketSellOrdersAsync( prunedOrders, assetFillAmount, _opts, ); + optimizedOrders = sellResult.optimizedOrders; + quoteReport = sellResult.quoteReport; } } } @@ -162,12 +166,12 @@ export class SwapQuoteCalculator { return createSwapQuote( makerAssetData, takerAssetData, - result, + optimizedOrders, operation, assetFillAmount, gasPrice, opts.gasSchedule, - result.quoteReport, + quoteReport, ); } } @@ -175,18 +179,16 @@ export class SwapQuoteCalculator { function createSwapQuote( makerAssetData: string, takerAssetData: string, - result: OptimizedOrdersAndQuoteReport, + optimizedOrders: OptimizedMarketOrder[], operation: MarketOperation, assetFillAmount: BigNumber, gasPrice: BigNumber, gasSchedule: { [source: string]: number }, - quoteReport: QuoteReport, + quoteReport?: QuoteReport, ): SwapQuote { - const resultOrders = result.optimizedOrders; - const bestCaseFillResult = simulateBestCaseFill({ gasPrice, - orders: resultOrders, + orders: optimizedOrders, side: operation, fillAmount: assetFillAmount, opts: { gasSchedule }, @@ -194,7 +196,7 @@ function createSwapQuote( const worstCaseFillResult = simulateWorstCaseFill({ gasPrice, - orders: resultOrders, + orders: optimizedOrders, side: operation, fillAmount: assetFillAmount, opts: { gasSchedule }, @@ -207,7 +209,7 @@ function createSwapQuote( bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult), worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult), sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource), - orders: resultOrders, + orders: optimizedOrders, quoteReport, }; @@ -216,14 +218,14 @@ function createSwapQuote( ...quoteBase, type: MarketOperation.Buy, makerAssetFillAmount: assetFillAmount, - quoteReport: result.quoteReport, + quoteReport, }; } else { return { ...quoteBase, type: MarketOperation.Sell, takerAssetFillAmount: assetFillAmount, - quoteReport: result.quoteReport, + quoteReport, }; } } diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index d0a141d7ba..8a7b285282 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -248,9 +248,9 @@ describe('MarketOperationUtils tests', () => { function getLiquidityProviderFromRegistryAndReturnCallParameters( liquidityProviderAddress: string = NULL_ADDRESS, ): [ - { registryAddress?: string; takerToken?: string; makerToken?: string }, - GetLiquidityProviderFromRegistryOperation - ] { + { registryAddress?: string; takerToken?: string; makerToken?: string }, + GetLiquidityProviderFromRegistryOperation + ] { const callArgs: { registryAddress?: string; takerToken?: string; makerToken?: string } = { registryAddress: undefined, takerToken: undefined, @@ -432,12 +432,13 @@ describe('MarketOperationUtils tests', () => { }); 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, DEFAULT_OPTS, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.not.be.length(0); for (const order of improvedOrders) { expect(getSourceFromAssetData(order.makerAssetData)).to.exist(''); @@ -456,24 +457,26 @@ describe('MarketOperationUtils tests', () => { }); 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, DEFAULT_OPTS, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const totalTakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.takerAssetAmount)); expect(totalTakerAssetAmount).to.bignumber.gte(FILL_AMOUNT); }); it('generates bridge orders with max slippage of `bridgeSlippage`', async () => { 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, { ...DEFAULT_OPTS, bridgeSlippage }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.not.be.length(0); for (const order of improvedOrders) { const expectedMakerAmount = order.fills[0].output; @@ -491,11 +494,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4 }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Eth2Dai, @@ -527,11 +531,12 @@ describe('MarketOperationUtils tests', () => { getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Native, @@ -562,11 +567,12 @@ describe('MarketOperationUtils tests', () => { getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Native, @@ -587,11 +593,12 @@ describe('MarketOperationUtils tests', () => { getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4 }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Eth2Dai, @@ -610,11 +617,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const firstSources = [ ERC20BridgeSource.Native, @@ -636,11 +644,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2]; const secondSources: ERC20BridgeSource[] = []; @@ -677,7 +686,7 @@ describe('MarketOperationUtils tests', () => { ORDER_DOMAIN, registryAddress, ); - const result = await sampler.getMarketSellOrdersAsync( + const ordersAndReport = await sampler.getMarketSellOrdersAsync( [ createOrder({ makerAssetData: assetDataUtils.encodeERC20AssetData(xAsset), @@ -687,6 +696,7 @@ describe('MarketOperationUtils tests', () => { Web3Wrapper.toBaseUnitAmount(10, 18), { excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0, shouldBatchBridgeOrders: false }, ); + const result = ordersAndReport.optimizedOrders; expect(result.length).to.eql(1); expect(result[0].makerAddress).to.eql(liquidityProviderAddress); @@ -713,7 +723,7 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { @@ -726,6 +736,7 @@ describe('MarketOperationUtils tests', () => { shouldBatchBridgeOrders: true, }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.be.length(3); const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ @@ -834,12 +845,13 @@ describe('MarketOperationUtils tests', () => { }); 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, DEFAULT_OPTS, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.not.be.length(0); for (const order of improvedOrders) { expect(getSourceFromAssetData(order.makerAssetData)).to.exist(''); @@ -858,24 +870,26 @@ describe('MarketOperationUtils tests', () => { }); 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, DEFAULT_OPTS, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const totalMakerAssetAmount = BigNumber.sum(...improvedOrders.map(o => o.makerAssetAmount)); expect(totalMakerAssetAmount).to.bignumber.gte(FILL_AMOUNT); }); it('generates bridge orders with max slippage of `bridgeSlippage`', async () => { 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. ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })), FILL_AMOUNT, { ...DEFAULT_OPTS, bridgeSlippage }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.not.be.length(0); for (const order of improvedOrders) { const expectedTakerAmount = order.fills[0].output; @@ -892,11 +906,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4 }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Eth2Dai, @@ -928,11 +943,12 @@ describe('MarketOperationUtils tests', () => { getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.UniswapV2, @@ -962,11 +978,12 @@ describe('MarketOperationUtils tests', () => { getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const expectedSources = [ ERC20BridgeSource.Native, @@ -984,11 +1001,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const firstSources = [ ERC20BridgeSource.Native, @@ -1009,11 +1027,12 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2]; const secondSources: ERC20BridgeSource[] = []; @@ -1029,7 +1048,7 @@ describe('MarketOperationUtils tests', () => { replaceSamplerOps({ getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), }); - const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( + const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, { @@ -1038,6 +1057,7 @@ describe('MarketOperationUtils tests', () => { shouldBatchBridgeOrders: true, }, ); + const improvedOrders = improvedOrdersResponse.optimizedOrders; expect(improvedOrders).to.be.length(2); const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ From 8ba77e95a493249e551acb9a3aa395169c271a41 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:07:20 -0700 Subject: [PATCH 07/50] export swap quoter types into own object --- packages/asset-swapper/src/types.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 1ca417cb8f..a5dfb17a3d 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -232,6 +232,14 @@ export interface RfqtMakerAssetOfferings { export { LogFunction } from './utils/quote_requestor'; +export interface SwapQuoterRfqtOpts { + takerApiKeyWhitelist: string[]; + makerAssetOfferings: RfqtMakerAssetOfferings; + skipBuyRequests?: boolean; + warningLogger?: LogFunction; + infoLogger?: LogFunction; +} + /** * 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). @@ -248,13 +256,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts { liquidityProviderRegistryAddress?: string; multiBridgeAddress?: string; ethGasStationUrl?: string; - rfqt?: { - takerApiKeyWhitelist: string[]; - makerAssetOfferings: RfqtMakerAssetOfferings; - skipBuyRequests?: boolean; - warningLogger?: LogFunction; - infoLogger?: LogFunction; - }; + rfqt?: SwapQuoterRfqtOpts; } /** From 75689cee962a069d224a22c6c62ffae5652176ad Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:07:36 -0700 Subject: [PATCH 08/50] create new quote requestor each time --- packages/asset-swapper/src/swap_quoter.ts | 56 +++++++++++++++-------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 1d19f91736..f136c94faa 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -19,6 +19,7 @@ import { SwapQuote, SwapQuoteRequestOpts, SwapQuoterOpts, + SwapQuoterRfqtOpts, } from './types'; import { assert } from './utils/assert'; import { calculateLiquidity } from './utils/calculate_liquidity'; @@ -44,9 +45,7 @@ export class SwapQuoter { private readonly _devUtilsContract: DevUtilsContract; private readonly _marketOperationUtils: MarketOperationUtils; private readonly _orderStateUtils: OrderStateUtils; - private readonly _quoteRequestor: QuoteRequestor; - private readonly _rfqtTakerApiKeyWhitelist: string[]; - private readonly _rfqtSkipBuyRequests: boolean; + private readonly _rfqtOptions?: SwapQuoterRfqtOpts; /** * Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders. @@ -167,11 +166,8 @@ export class SwapQuoter { this.orderbook = orderbook; this.expiryBufferMs = expiryBufferMs; this.permittedOrderFeeTypes = permittedOrderFeeTypes; - this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : []; - this._rfqtSkipBuyRequests = - rfqt && rfqt.skipBuyRequests !== undefined - ? rfqt.skipBuyRequests - : (r => r !== undefined && r.skipBuyRequests === true)(constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt); + + this._rfqtOptions = rfqt; this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId); this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider); this._protocolFeeUtils = ProtocolFeeUtils.getInstance( @@ -179,12 +175,6 @@ export class SwapQuoter { options.ethGasStationUrl, ); this._orderStateUtils = new OrderStateUtils(this._devUtilsContract); - this._quoteRequestor = new QuoteRequestor( - rfqt ? rfqt.makerAssetOfferings || {} : {}, - rfqt ? rfqt.warningLogger : undefined, - rfqt ? rfqt.infoLogger : undefined, - expiryBufferMs, - ); const sampler = new DexOrderSampler( new IERC20BridgeSamplerContract(this._contractAddresses.erc20BridgeSampler, this.provider, { gas: samplerGasLimit, @@ -549,18 +539,27 @@ export class SwapQuoter { // get batches of orders from different sources, awaiting sources in parallel const orderBatchPromises: Array> = []; 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 ( opts.rfqt && opts.rfqt.intentOnFilling && opts.rfqt.apiKey && - this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey) && - !(marketOperation === MarketOperation.Buy && this._rfqtSkipBuyRequests) + this._rfqtTakerApiKeyWhitelist().includes(opts.rfqt.apiKey) && + !(marketOperation === MarketOperation.Buy && this._shouldSkipRfqtBuyRequests()) ) { if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) { throw new Error('RFQ-T requests must specify a taker address'); } orderBatchPromises.push( - this._quoteRequestor + quoteRequestor .requestRfqtFirmQuotesAsync( makerAssetData, takerAssetData, @@ -590,7 +589,7 @@ export class SwapQuoter { const calcOpts: CalculateSwapQuoteOpts = opts; if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt, marketOperation)) { - calcOpts.rfqt.quoteRequestor = this._quoteRequestor; + calcOpts.rfqt.quoteRequestor = quoteRequestor; } if (marketOperation === MarketOperation.Buy) { @@ -616,9 +615,26 @@ export class SwapQuoter { opts !== undefined && opts.isIndicative !== undefined && opts.isIndicative && - this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey) && - !(op === MarketOperation.Buy && this._rfqtSkipBuyRequests) + this._rfqtTakerApiKeyWhitelist().includes(opts.apiKey) && + !(op === MarketOperation.Buy && this._shouldSkipRfqtBuyRequests()) ); } + private _rfqtTakerApiKeyWhitelist(): string[] { + return this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; + } + private _shouldSkipRfqtBuyRequests(): boolean { + const rfqtOptions = this._rfqtOptions; + + if (rfqtOptions && rfqtOptions.skipBuyRequests !== undefined) { + return rfqtOptions.skipBuyRequests; + } + + const defaultRfqtOptions = constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt; + if (defaultRfqtOptions && defaultRfqtOptions.skipBuyRequests !== undefined) { + return defaultRfqtOptions.skipBuyRequests; + } + + return false; + } } // tslint:disable-next-line: max-file-line-count From 67b195c9429a7f62d4572c33f6602a52cf559be6 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:13:53 -0700 Subject: [PATCH 09/50] take out old todo comment --- packages/asset-swapper/src/utils/swap_quote_calculator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index c64fbad0a7..c498a6873c 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -137,7 +137,6 @@ export class SwapQuoteCalculator { : { assetProxyId: '' }; if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) { - const blankQuoteReport = { sourcesConsidered: [], sourcesDelivered: [] }; // TODO: better solution here // HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable optimizedOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o)); } else { From 615874d2ec68962bf91ec66aa69a7d0ce36e3c51 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:38:36 -0700 Subject: [PATCH 10/50] always pass through quoterequestor --- packages/asset-swapper/src/swap_quoter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index f136c94faa..4e76ef686b 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -588,7 +588,7 @@ export class SwapQuoter { const calcOpts: CalculateSwapQuoteOpts = opts; - if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt, marketOperation)) { + if (calcOpts.rfqt !== undefined) { calcOpts.rfqt.quoteRequestor = quoteRequestor; } From 120714ecfc9ed931e80bd86bae702ca5999163f7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:49:42 -0700 Subject: [PATCH 11/50] linting and remove unused function --- packages/asset-swapper/src/swap_quoter.ts | 9 --------- packages/asset-swapper/src/types.ts | 2 +- .../src/utils/market_operation_utils/index.ts | 13 ++++++++++--- .../src/utils/market_operation_utils/types.ts | 2 +- .../src/utils/quote_report_generator.ts | 16 ++++++++++------ .../asset-swapper/src/utils/quote_requestor.ts | 15 ++++++++++----- .../src/utils/swap_quote_calculator.ts | 7 +++++-- .../test/market_operation_utils_test.ts | 6 +++--- 8 files changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 4e76ef686b..cc82bfc307 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -610,15 +610,6 @@ export class SwapQuoter { return swapQuote; } - private _shouldEnableIndicativeRfqt(opts: CalculateSwapQuoteOpts['rfqt'], op: MarketOperation): boolean { - return ( - opts !== undefined && - opts.isIndicative !== undefined && - opts.isIndicative && - this._rfqtTakerApiKeyWhitelist().includes(opts.apiKey) && - !(op === MarketOperation.Buy && this._shouldSkipRfqtBuyRequests()) - ); - } private _rfqtTakerApiKeyWhitelist(): string[] { return this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; } diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index a5dfb17a3d..15975433dd 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -220,7 +220,7 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts { /** * Opts required to generate a SwapQuote with SwapQuoteCalculator */ -export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts { } +export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {} /** * A mapping from RFQ-T quote provider URLs to the trading pairs they support. 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 6ed304ff85..4238aa5021 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -381,8 +381,8 @@ export class MarketOperationUtils { const [last, penultimateIfExists] = optimalPath.slice().reverse(); const lastNativeFillIfExists = last.source === ERC20BridgeSource.Native && - penultimateIfExists && - penultimateIfExists.source !== ERC20BridgeSource.Native + penultimateIfExists && + penultimateIfExists.source !== ERC20BridgeSource.Native ? last : undefined; // By prepending native paths to the front they cannot split on-chain sources and incur @@ -402,7 +402,14 @@ export class MarketOperationUtils { multiBridgeAddress: opts.multiBridgeAddress, 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(); + 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 }; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index d08756b94d..1a05c8fad5 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -43,7 +43,7 @@ export enum ERC20BridgeSource { } // Internal `fillData` field for `Fill` objects. -export interface FillData { } +export interface FillData {} // `FillData` for native fills. export interface NativeFillData extends FillData { diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index 773916a8e4..88b8711a7e 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -1,5 +1,5 @@ import { orderHashUtils } from '@0x/order-utils'; -import { BigNumber, NULL_ADDRESS } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { ERC20BridgeSource, SignedOrder } from '..'; @@ -44,7 +44,14 @@ export class QuoteReportGenerator { private readonly _collapsedFills: CollapsedFill[]; private readonly _quoteRequestor?: QuoteRequestor; - constructor(marketOperation: MarketOperation, dexQuotes: DexSample[], nativeOrders: SignedOrder[], orderFillableAmounts: BigNumber[], collapsedFills: CollapsedFill[], quoteRequestor?: QuoteRequestor) { + constructor( + marketOperation: MarketOperation, + dexQuotes: DexSample[], + nativeOrders: SignedOrder[], + orderFillableAmounts: BigNumber[], + collapsedFills: CollapsedFill[], + quoteRequestor?: QuoteRequestor, + ) { this._dexQuotes = dexQuotes; this._nativeOrders = nativeOrders; this._marketOperation = marketOperation; @@ -68,10 +75,7 @@ export class QuoteReportGenerator { const dexReportSourcesConsidered = this._dexQuotes.map(ds => this._dexSampleToReportSource(ds)); const nativeOrderSourcesConsidered = this._nativeOrders.map(no => this._nativeOrderToReportSource(no)); - const sourcesConsidered = [ - ...dexReportSourcesConsidered, - ...nativeOrderSourcesConsidered, - ]; + const sourcesConsidered = [...dexReportSourcesConsidered, ...nativeOrderSourcesConsidered]; const sourcesDelivered = this._collapsedFills.map(collapsedFill => { const foundNativeOrder = (collapsedFill as NativeCollapsedFill).nativeOrder; if (foundNativeOrder) { diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index 6430064c8f..90dec24245 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -1,9 +1,9 @@ import { schemas, SchemaValidator } from '@0x/json-schemas'; -import { assetDataUtils, orderCalculationUtils, SignedOrder, orderHashUtils } from '@0x/order-utils'; +import { assetDataUtils, orderCalculationUtils, orderHashUtils, SignedOrder } from '@0x/order-utils'; import { RFQTFirmQuote, RFQTIndicativeQuote, TakerRequest } from '@0x/quote-server'; import { ERC20AssetData } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; -import Axios, { AxiosResponse } from 'axios'; +import Axios from 'axios'; import { constants } from '../constants'; import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types'; @@ -94,7 +94,7 @@ export class QuoteRequestor { private readonly _infoLogger: LogFunction = (obj, msg) => logUtils.log(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`), private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs, - ) { } + ) {} public async requestRfqtFirmQuotesAsync( makerAssetData: string, @@ -156,7 +156,12 @@ export class QuoteRequestor { salt: new BigNumber(orderWithStringInts.salt), }; - if (orderCalculationUtils.willOrderExpire(orderWithBigNumberInts, this._expiryBufferMs / constants.ONE_SECOND_MS)) { + if ( + orderCalculationUtils.willOrderExpire( + orderWithBigNumberInts, + this._expiryBufferMs / constants.ONE_SECOND_MS, + ) + ) { this._warningLogger(orderWithBigNumberInts, 'Expiry too soon in RFQ-T order, filtering out'); return; } @@ -353,7 +358,7 @@ export class QuoteRequestor { this._warningLogger( convertIfAxiosError(err), `Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${url} for API key ${ - options.apiKey + options.apiKey } for taker address ${options.takerAddress}`, ); } diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index c498a6873c..232dbb75d9 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -9,7 +9,6 @@ import { MarketOperation, MarketSellSwapQuote, SwapQuote, - SwapQuoteBase, SwapQuoteInfo, SwapQuoteOrdersBreakdown, SwapQuoterError, @@ -17,7 +16,11 @@ import { import { MarketOperationUtils } from './market_operation_utils'; import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; -import { GetMarketOrdersOpts, OptimizedMarketOrder, OptimizedOrdersAndQuoteReport } from './market_operation_utils/types'; +import { + GetMarketOrdersOpts, + OptimizedMarketOrder, + OptimizedOrdersAndQuoteReport, +} from './market_operation_utils/types'; import { isSupportedAssetDataInOrders } from './utils'; import { QuoteReport } from './quote_report_generator'; diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 8a7b285282..e0e3841f0d 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -248,9 +248,9 @@ describe('MarketOperationUtils tests', () => { function getLiquidityProviderFromRegistryAndReturnCallParameters( liquidityProviderAddress: string = NULL_ADDRESS, ): [ - { registryAddress?: string; takerToken?: string; makerToken?: string }, - GetLiquidityProviderFromRegistryOperation - ] { + { registryAddress?: string; takerToken?: string; makerToken?: string }, + GetLiquidityProviderFromRegistryOperation + ] { const callArgs: { registryAddress?: string; takerToken?: string; makerToken?: string } = { registryAddress: undefined, takerToken: undefined, From 31fa530845cc7c38a522cef36aeca310ce3f4cd7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 8 Jul 2020 15:54:08 -0700 Subject: [PATCH 12/50] add CHANGELOG entry --- packages/asset-swapper/CHANGELOG.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 8424fa6933..3127da8abd 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.7.0", + "changes": [ + { + "note": "Return quoteReport SwapQuoter functions", + "pr": 2627 + } + ] + }, { "version": "4.6.0", "changes": [ @@ -512,4 +521,4 @@ } ] } -] +] \ No newline at end of file From d8558f59563db380ae710be1854c80029fbe688b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 15:45:59 -0700 Subject: [PATCH 13/50] QuoteReportGenerator test --- .../test/quote_report_generator_test.ts | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 packages/asset-swapper/test/quote_report_generator_test.ts diff --git a/packages/asset-swapper/test/quote_report_generator_test.ts b/packages/asset-swapper/test/quote_report_generator_test.ts new file mode 100644 index 0000000000..38e35edc29 --- /dev/null +++ b/packages/asset-swapper/test/quote_report_generator_test.ts @@ -0,0 +1,199 @@ +// tslint:disable:custom-no-magic-numbers +import { tokenUtils } from '@0x/dev-utils'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { SignedOrder, StatusCodes } 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 { constants } from '../src/constants'; +import { MarketOperation, MockedRfqtFirmQuoteResponse, MockedRfqtIndicativeQuoteResponse } from '../src/types'; +import { CollapsedFill, DexSample, ERC20BridgeSource, NativeCollapsedFill } from '../src/utils/market_operation_utils/types'; +import { QuoteRequestor } from '../src/utils/quote_requestor'; +import { rfqtMocker } from '../src/utils/rfqt_mocker'; + +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, + nativeOrder: { + ...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.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]; + + // append bogus 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(); + }); + + }); +}); From 6554bf9f08601f0230947684df4f8784a1347214 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 15:57:06 -0700 Subject: [PATCH 14/50] test for buy amount and no quote requestor --- .../test/quote_report_generator_test.ts | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/asset-swapper/test/quote_report_generator_test.ts b/packages/asset-swapper/test/quote_report_generator_test.ts index 38e35edc29..bfc1d395ee 100644 --- a/packages/asset-swapper/test/quote_report_generator_test.ts +++ b/packages/asset-swapper/test/quote_report_generator_test.ts @@ -184,7 +184,7 @@ describe('QuoteReportGenerator', async () => { orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => { const expectedSourceDelivered = expectedSourcesDelivered[idx]; - // append bogus fillable values + // remove fillable values if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) { actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, ['fillableMakerAssetAmount', 'fillableTakerAssetAmount', 'fillableTakerFeeAmount']) as SignedOrder; } @@ -194,6 +194,94 @@ describe('QuoteReportGenerator', async () => { 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}`); + }); + + }); }); }); From 259aec52a91da131dc833721cf149f4fc7d2a714 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 15:58:06 -0700 Subject: [PATCH 15/50] linting --- .../test/quote_report_generator_test.ts | 131 +++++++++++++----- 1 file changed, 98 insertions(+), 33 deletions(-) diff --git a/packages/asset-swapper/test/quote_report_generator_test.ts b/packages/asset-swapper/test/quote_report_generator_test.ts index bfc1d395ee..85f1857fdc 100644 --- a/packages/asset-swapper/test/quote_report_generator_test.ts +++ b/packages/asset-swapper/test/quote_report_generator_test.ts @@ -1,20 +1,28 @@ // tslint:disable:custom-no-magic-numbers -import { tokenUtils } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { SignedOrder, StatusCodes } from '@0x/types'; +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 { constants } from '../src/constants'; -import { MarketOperation, MockedRfqtFirmQuoteResponse, MockedRfqtIndicativeQuoteResponse } from '../src/types'; -import { CollapsedFill, DexSample, ERC20BridgeSource, NativeCollapsedFill } from '../src/utils/market_operation_utils/types'; +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 { rfqtMocker } from '../src/utils/rfqt_mocker'; -import { BridgeReportSource, NativeOrderbookReportSource, NativeRFQTReportSource, QuoteReportGenerator, QuoteReportSource } from './../src/utils/quote_report_generator'; +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'; @@ -84,7 +92,12 @@ describe('QuoteReportGenerator', async () => { takerAssetAmount: rfqtOrder2FillableAmount.plus(100), }); const nativeOrders: SignedOrder[] = [orderbookOrder1, rfqtOrder1, rfqtOrder2, orderbookOrder2]; - const orderFillableAmounts: BigNumber[] = [orderbookOrder1FillableAmount, rfqtOrder1FillableAmount, rfqtOrder2FillableAmount, orderbookOrder2FillableAmount]; + const orderFillableAmounts: BigNumber[] = [ + orderbookOrder1FillableAmount, + rfqtOrder1FillableAmount, + rfqtOrder2FillableAmount, + orderbookOrder2FillableAmount, + ]; // generate path const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [] }; @@ -95,17 +108,33 @@ describe('QuoteReportGenerator', async () => { // quote generator mock const quoteRequestor = TypeMoq.Mock.ofType(); - 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()); + 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 orderReport = new QuoteReportGenerator( + marketOperation, + dexQuotes, + nativeOrders, + orderFillableAmounts, + pathGenerated, + quoteRequestor.object, + ).generateReport(); const rfqtOrder1Source: NativeRFQTReportSource = { liquiditySource: ERC20BridgeSource.Native, @@ -167,18 +196,31 @@ describe('QuoteReportGenerator', async () => { }; const expectedSourcesConsidered: QuoteReportSource[] = [ - kyber1Source, kyber2Source, uniswap1Source, uniswap2Source, orderbookOrder1Source, rfqtOrder1Source, rfqtOrder2Source, orderbookOrder2Source, + 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}`); + expect(actualSourcesConsidered).to.eql( + expectedSourceConsidered, + `sourceConsidered incorrect at index ${idx}`, + ); }); const expectedSourcesDelivered: QuoteReportSource[] = [ - rfqtOrder2Source, orderbookOrder2Source, uniswap2Source, kyber2Source, + rfqtOrder2Source, + orderbookOrder2Source, + uniswap2Source, + kyber2Source, ]; expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length); orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => { @@ -186,10 +228,17 @@ describe('QuoteReportGenerator', async () => { // remove fillable values if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) { - actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, ['fillableMakerAssetAmount', 'fillableTakerAssetAmount', 'fillableTakerFeeAmount']) as SignedOrder; + actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [ + 'fillableMakerAssetAmount', + 'fillableTakerAssetAmount', + 'fillableTakerFeeAmount', + ]) as SignedOrder; } - expect(actualSourceDelivered).to.eql(expectedSourceDelivered, `sourceDelivered incorrect at index ${idx}`); + expect(actualSourceDelivered).to.eql( + expectedSourceDelivered, + `sourceDelivered incorrect at index ${idx}`, + ); }); quoteRequestor.verifyAll(); @@ -227,7 +276,13 @@ describe('QuoteReportGenerator', async () => { const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [] }; const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, kyber1Fill]; - const orderReport = new QuoteReportGenerator(marketOperation, dexQuotes, nativeOrders, orderFillableAmounts, pathGenerated).generateReport(); + const orderReport = new QuoteReportGenerator( + marketOperation, + dexQuotes, + nativeOrders, + orderFillableAmounts, + pathGenerated, + ).generateReport(); const orderbookOrder1Source: NativeOrderbookReportSource = { liquiditySource: ERC20BridgeSource.Native, @@ -259,29 +314,39 @@ describe('QuoteReportGenerator', async () => { }; const expectedSourcesConsidered: QuoteReportSource[] = [ - kyber1Source, uniswap1Source, orderbookOrder1Source, orderbookOrder2Source, + 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}`); + expect(actualSourcesConsidered).to.eql( + expectedSourceConsidered, + `sourceConsidered incorrect at index ${idx}`, + ); }); - const expectedSourcesDelivered: QuoteReportSource[] = [ - orderbookOrder1Source, uniswap1Source, kyber1Source, - ]; + 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; + actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [ + 'fillableMakerAssetAmount', + 'fillableTakerAssetAmount', + 'fillableTakerFeeAmount', + ]) as SignedOrder; } - expect(actualSourceDelivered).to.eql(expectedSourceDelivered, `sourceDelivered incorrect at index ${idx}`); + expect(actualSourceDelivered).to.eql( + expectedSourceDelivered, + `sourceDelivered incorrect at index ${idx}`, + ); }); - }); }); }); From 10f29b66b8191618bddcb3c561c5608e04a27bd4 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 15:58:31 -0700 Subject: [PATCH 16/50] newline --- packages/asset-swapper/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 3127da8abd..02ad7f5b73 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -521,4 +521,4 @@ } ] } -] \ No newline at end of file +] From 9150d6bd2adb4138ce61d52ec0fae336b28f0d22 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 16:00:24 -0700 Subject: [PATCH 17/50] major version bump --- packages/asset-swapper/CHANGELOG.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 02ad7f5b73..efef28eafa 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,9 +1,9 @@ [ { - "version": "4.7.0", + "version": "5.0.0", "changes": [ { - "note": "Return quoteReport SwapQuoter functions", + "note": "Return quoteReport from SwapQuoter functions", "pr": 2627 } ] From 1c84709db357ee048e289df643bbc14f96a5b242 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 16:03:14 -0700 Subject: [PATCH 18/50] isApiKeyWhitelisted helper fn --- packages/asset-swapper/src/swap_quoter.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index cc82bfc307..ad195db066 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -552,7 +552,7 @@ export class SwapQuoter { opts.rfqt && opts.rfqt.intentOnFilling && opts.rfqt.apiKey && - this._rfqtTakerApiKeyWhitelist().includes(opts.rfqt.apiKey) && + this._isApiKeyWhitelisted(opts.rfqt.apiKey) && !(marketOperation === MarketOperation.Buy && this._shouldSkipRfqtBuyRequests()) ) { if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) { @@ -610,8 +610,9 @@ export class SwapQuoter { return swapQuote; } - private _rfqtTakerApiKeyWhitelist(): string[] { - return this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; + private _isApiKeyWhitelisted(apiKey: string): boolean { + const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; + return whitelistedApiKeys.includes(apiKey); } private _shouldSkipRfqtBuyRequests(): boolean { const rfqtOptions = this._rfqtOptions; From 3e823cc9e39783161b3e6e8ef7f16ff42d670028 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 10 Jul 2020 16:07:14 -0700 Subject: [PATCH 19/50] export new types --- packages/asset-swapper/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index d3000b8366..b34de842a9 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -59,6 +59,7 @@ export { SwapQuoterError, SwapQuoterOpts, SwapQuoteConsumerError, + SwapQuoterRfqtOpts, SignedOrderWithFillableAmounts, SwapQuoteOrdersBreakdown, ExchangeProxyContractOpts, @@ -74,3 +75,4 @@ export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; export { QuoteRequestor } from './utils/quote_requestor'; export { rfqtMocker } from './utils/rfqt_mocker'; +export { QuoteReport } from './utils/quote_report_generator'; From 87637135960613185df0606727ab95ce3d57beb0 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 13 Jul 2020 15:49:39 -0700 Subject: [PATCH 20/50] ds -> dq --- packages/asset-swapper/src/utils/quote_report_generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index 88b8711a7e..f524591ab2 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -72,7 +72,7 @@ export class QuoteReportGenerator { } public generateReport(): QuoteReport { - const dexReportSourcesConsidered = this._dexQuotes.map(ds => this._dexSampleToReportSource(ds)); + const dexReportSourcesConsidered = this._dexQuotes.map(dq => this._dexSampleToReportSource(dq)); const nativeOrderSourcesConsidered = this._nativeOrders.map(no => this._nativeOrderToReportSource(no)); const sourcesConsidered = [...dexReportSourcesConsidered, ...nativeOrderSourcesConsidered]; From b1c3c60def5ae679c15265ef5c6f0f856757791b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 13 Jul 2020 16:45:40 -0700 Subject: [PATCH 21/50] additional imports --- packages/asset-swapper/src/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index b34de842a9..18e8e05ed2 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -64,6 +64,7 @@ export { SwapQuoteOrdersBreakdown, ExchangeProxyContractOpts, } from './types'; +import { ERC20BridgeSource } from './utils/market_operation_utils/types'; export { ERC20BridgeSource, CollapsedFill, @@ -75,4 +76,11 @@ export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; export { QuoteRequestor } from './utils/quote_requestor'; export { rfqtMocker } from './utils/rfqt_mocker'; -export { QuoteReport } from './utils/quote_report_generator'; +export { + BridgeReportSource, + NativeOrderbookReportSource, + NativeRFQTReportSource, + QuoteReport, + QuoteReportSource, +} from './utils/quote_report_generator'; +export type Native = ERC20BridgeSource.Native; From 5c1ffe7fc8d8af2bcc54271f1e117ae741a65be2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 13 Jul 2020 17:11:53 -0700 Subject: [PATCH 22/50] add exclude to EXTERNAL_TYPE_MAP --- packages/monorepo-scripts/src/doc_gen_configs.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/monorepo-scripts/src/doc_gen_configs.ts b/packages/monorepo-scripts/src/doc_gen_configs.ts index a657c60693..33b1bd66e1 100644 --- a/packages/monorepo-scripts/src/doc_gen_configs.ts +++ b/packages/monorepo-scripts/src/doc_gen_configs.ts @@ -26,6 +26,7 @@ export const docGenConfigs: DocGenConfigs = { Sell: true, IterableIterator: true, Set: true, + Exclude: true, }, // 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 From 3e99c95791f5392a95f3f04f652cfec62a8eb467 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 22 Jul 2020 13:26:31 -0700 Subject: [PATCH 23/50] Linting --- packages/asset-swapper/src/swap_quoter.ts | 4 ++-- packages/asset-swapper/src/types.ts | 2 +- .../src/utils/market_operation_utils/index.ts | 4 ++-- .../asset-swapper/src/utils/quote_report_generator.ts | 2 +- packages/asset-swapper/src/utils/swap_quote_calculator.ts | 8 +++++++- .../asset-swapper/test/quote_report_generator_test.ts | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index e7a43141ce..7019ed7f85 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -180,8 +180,8 @@ export class SwapQuoter { const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); const defaultCodeOverrides = samplerBytecode ? { - [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, - } + [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, + } : {}; const samplerOverrides = _.merge( {}, diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 6447548fa2..7041b1ecfb 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -220,7 +220,7 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts { /** * Opts required to generate a SwapQuote with SwapQuoteCalculator */ -export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts { } +export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {} /** * A mapping from RFQ-T quote provider URLs to the trading pairs they support. 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 cdc3e5b9a6..1433c30b7d 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -429,8 +429,8 @@ export class MarketOperationUtils { const [last, penultimateIfExists] = optimalPath.slice().reverse(); const lastNativeFillIfExists = last.source === ERC20BridgeSource.Native && - penultimateIfExists && - penultimateIfExists.source !== ERC20BridgeSource.Native + penultimateIfExists && + penultimateIfExists.source !== ERC20BridgeSource.Native ? last : undefined; // By prepending native paths to the front they cannot split on-chain sources and incur diff --git a/packages/asset-swapper/src/utils/quote_report_generator.ts b/packages/asset-swapper/src/utils/quote_report_generator.ts index 8ffa085a86..3ea6f45e8a 100644 --- a/packages/asset-swapper/src/utils/quote_report_generator.ts +++ b/packages/asset-swapper/src/utils/quote_report_generator.ts @@ -39,7 +39,7 @@ export interface QuoteReport { const nativeOrderFromCollapsedFill = (cf: CollapsedFill): SignedOrder | undefined => { // Cast as NativeCollapsedFill and then check // if it really is a NativeCollapsedFill - const possibleNativeCollapsedFill = (cf as NativeCollapsedFill); + const possibleNativeCollapsedFill = cf as NativeCollapsedFill; if (possibleNativeCollapsedFill.fillData && possibleNativeCollapsedFill.fillData.order) { return possibleNativeCollapsedFill.fillData.order; } else { diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index 18b038df41..47646007bd 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -16,7 +16,13 @@ import { import { MarketOperationUtils } from './market_operation_utils'; import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; -import { FeeSchedule, FillData, GetMarketOrdersOpts, OptimizedMarketOrder, OptimizedOrdersAndQuoteReport } from './market_operation_utils/types'; +import { + FeeSchedule, + FillData, + GetMarketOrdersOpts, + OptimizedMarketOrder, + OptimizedOrdersAndQuoteReport, +} from './market_operation_utils/types'; import { isSupportedAssetDataInOrders } from './utils'; import { QuoteReport } from './quote_report_generator'; diff --git a/packages/asset-swapper/test/quote_report_generator_test.ts b/packages/asset-swapper/test/quote_report_generator_test.ts index d94144a3b6..a4a1a6783f 100644 --- a/packages/asset-swapper/test/quote_report_generator_test.ts +++ b/packages/asset-swapper/test/quote_report_generator_test.ts @@ -40,7 +40,7 @@ const collapsedFillFromNativeOrder = (order: SignedOrder): NativeCollapsedFill = fillableMakerAssetAmount: new BigNumber(1), fillableTakerAssetAmount: new BigNumber(1), fillableTakerFeeAmount: new BigNumber(1), - } + }, }, subFills: [], }; From 9edaa3a64ee4df89e70a1e032212f3f20dcfcd87 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 22 Jul 2020 23:50:55 -0700 Subject: [PATCH 24/50] fix --- packages/asset-swapper/CHANGELOG.json | 2 +- packages/asset-swapper/src/swap_quoter.ts | 4 ++-- .../asset-swapper/src/utils/market_operation_utils/index.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index bf55c7e7a5..8a9385893c 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -551,4 +551,4 @@ } ] } -] \ No newline at end of file +] diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index acbcab38c8..5bcb609f65 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -181,8 +181,8 @@ export class SwapQuoter { const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); const defaultCodeOverrides = samplerBytecode ? { - [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, - } + [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, + } : {}; const samplerOverrides = _.assign( { block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides }, 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 a94232a069..836f0f0862 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -432,8 +432,8 @@ export class MarketOperationUtils { const [last, penultimateIfExists] = optimalPath.slice().reverse(); const lastNativeFillIfExists = last.source === ERC20BridgeSource.Native && - penultimateIfExists && - penultimateIfExists.source !== ERC20BridgeSource.Native + penultimateIfExists && + penultimateIfExists.source !== ERC20BridgeSource.Native ? last : undefined; // By prepending native paths to the front they cannot split on-chain sources and incur From 391d501ce698b5a98ccd1dec9fbaaf83063f3ba1 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 22 Jul 2020 23:56:07 -0700 Subject: [PATCH 25/50] Export rfqt opts --- packages/asset-swapper/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index ddaa9e4bbb..c5634d0d9c 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -64,6 +64,7 @@ export { SwapQuoteInfo, SwapQuoteOrdersBreakdown, SwapQuoteRequestOpts, + SwapQuoterRfqtOpts, SwapQuoterError, SwapQuoterOpts, } from './types'; From dfa7e30e42b0ade71d2385f845f9735fabdbeea9 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 00:35:25 -0700 Subject: [PATCH 26/50] fix version number, this is just a minor bump --- packages/asset-swapper/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 8a9385893c..9cb0a8dcde 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "5.0.0", + "version": "4.7.0", "changes": [ { "note": "Return quoteReport from SwapQuoter functions", From ca2aa72e0f0e5e52322caa1258533db3bfd1e190 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 15:17:35 -0700 Subject: [PATCH 27/50] Merge development --- contracts/asset-proxy/CHANGELOG.json | 9 + .../contracts/src/bridges/CurveBridge.sol | 51 +- .../contracts/src/interfaces/ICurve.sol | 16 - contracts/erc20-bridge-sampler/CHANGELOG.json | 9 + .../contracts/src/ApproximateBuys.sol | 122 ++ .../contracts/src/CurveSampler.sol | 156 +++ .../contracts/src/ERC20BridgeSampler.sol | 1016 +---------------- .../contracts/src/Eth2DaiSampler.sol | 140 +++ .../contracts/src/ICurve.sol | 16 - .../contracts/src/IERC20BridgeSampler.sol | 293 ----- .../contracts/src/KyberSampler.sol | 218 ++++ .../src/LiquidityProviderSampler.sol | 168 +++ .../contracts/src/MultiBridgeSampler.sol | 79 ++ .../contracts/src/NativeOrderSampler.sol | 125 ++ .../contracts/src/SamplerUtils.sol | 56 + .../contracts/src/UniswapSampler.sol | 201 ++++ .../contracts/src/UniswapV2Sampler.sol | 99 ++ contracts/erc20-bridge-sampler/package.json | 4 +- .../erc20-bridge-sampler/src/artifacts.ts | 2 - .../erc20-bridge-sampler/src/wrappers.ts | 1 - .../erc20-bridge-sampler/test/artifacts.ts | 22 +- .../test/erc20-bridge-sampler.ts | 33 +- .../erc20-bridge-sampler/test/wrappers.ts | 11 +- contracts/erc20-bridge-sampler/tsconfig.json | 14 +- contracts/integrations/CHANGELOG.json | 9 + contracts/integrations/package.json | 1 + .../bridge_sampler_mainnet_test.ts | 34 +- .../test/bridges/curve_bridge_mainnet_test.ts | 133 ++- packages/asset-swapper/CHANGELOG.json | 6 +- packages/asset-swapper/src/constants.ts | 1 - packages/asset-swapper/src/index.ts | 2 + packages/asset-swapper/src/swap_quoter.ts | 42 +- packages/asset-swapper/src/types.ts | 9 +- .../utils/market_operation_utils/constants.ts | 109 +- .../market_operation_utils/curve_utils.ts | 9 +- .../src/utils/market_operation_utils/index.ts | 13 +- .../utils/market_operation_utils/orders.ts | 19 +- .../sampler_operations.ts | 68 +- .../src/utils/market_operation_utils/types.ts | 32 +- .../test/market_operation_utils_test.ts | 89 +- packages/contract-addresses/CHANGELOG.json | 9 + packages/contract-addresses/addresses.json | 8 +- packages/contract-artifacts/CHANGELOG.json | 4 + .../artifacts/ERC20BridgeSampler.json | 62 +- packages/contract-wrappers/CHANGELOG.json | 4 + .../erc20_bridge_sampler.ts | 97 +- 46 files changed, 1945 insertions(+), 1676 deletions(-) create mode 100644 contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol delete mode 100644 contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/LiquidityProviderSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/MultiBridgeSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/NativeOrderSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/SamplerUtils.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/UniswapV2Sampler.sol diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index 2828349ac6..37402629cf 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.5.0", + "changes": [ + { + "note": "Update `CurveBridge` to support more varied curves", + "pr": 2633 + } + ] + }, { "version": "3.4.0", "changes": [ diff --git a/contracts/asset-proxy/contracts/src/bridges/CurveBridge.sol b/contracts/asset-proxy/contracts/src/bridges/CurveBridge.sol index 05150f8bcc..9515fef349 100644 --- a/contracts/asset-proxy/contracts/src/bridges/CurveBridge.sol +++ b/contracts/asset-proxy/contracts/src/bridges/CurveBridge.sol @@ -26,7 +26,6 @@ import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol"; import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; import "../interfaces/IERC20Bridge.sol"; import "../interfaces/ICurve.sol"; -import "./MixinGasToken.sol"; // solhint-disable not-rely-on-time @@ -34,14 +33,14 @@ import "./MixinGasToken.sol"; contract CurveBridge is IERC20Bridge, IWallet, - DeploymentConstants, - MixinGasToken + DeploymentConstants { struct CurveBridgeData { address curveAddress; + bytes4 exchangeFunctionSelector; + address fromTokenAddress; int128 fromCoinIdx; int128 toCoinIdx; - int128 version; } /// @dev Callback for `ICurve`. Tries to buy `amount` of @@ -62,39 +61,31 @@ contract CurveBridge is bytes calldata bridgeData ) external - freesGasTokensFromCollector returns (bytes4 success) { // Decode the bridge data to get the Curve metadata. CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData)); - address fromTokenAddress = ICurve(data.curveAddress).underlying_coins(data.fromCoinIdx); - require(toTokenAddress != fromTokenAddress, "CurveBridge/INVALID_PAIR"); - uint256 fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this)); + require(toTokenAddress != data.fromTokenAddress, "CurveBridge/INVALID_PAIR"); + uint256 fromTokenBalance = IERC20Token(data.fromTokenAddress).balanceOf(address(this)); // Grant an allowance to the exchange to spend `fromTokenAddress` token. - LibERC20Token.approveIfBelow(fromTokenAddress, data.curveAddress, fromTokenBalance); + LibERC20Token.approveIfBelow(data.fromTokenAddress, data.curveAddress, fromTokenBalance); // Try to sell all of this contract's `fromTokenAddress` token balance. - if (data.version == 0) { - ICurve(data.curveAddress).exchange_underlying( - data.fromCoinIdx, - data.toCoinIdx, - // dx - fromTokenBalance, - // min dy - amount, - // expires - block.timestamp + 1 - ); - } else { - ICurve(data.curveAddress).exchange_underlying( - data.fromCoinIdx, - data.toCoinIdx, - // dx - fromTokenBalance, - // min dy - amount - ); + { + (bool didSucceed, bytes memory resultData) = + data.curveAddress.call(abi.encodeWithSelector( + data.exchangeFunctionSelector, + data.fromCoinIdx, + data.toCoinIdx, + // dx + fromTokenBalance, + // min dy + amount + )); + if (!didSucceed) { + assembly { revert(add(resultData, 32), mload(resultData)) } + } } uint256 toTokenBalance = IERC20Token(toTokenAddress).balanceOf(address(this)); @@ -102,7 +93,7 @@ contract CurveBridge is LibERC20Token.transfer(toTokenAddress, to, toTokenBalance); emit ERC20BridgeTransfer( - fromTokenAddress, + data.fromTokenAddress, toTokenAddress, fromTokenBalance, toTokenBalance, diff --git a/contracts/asset-proxy/contracts/src/interfaces/ICurve.sol b/contracts/asset-proxy/contracts/src/interfaces/ICurve.sol index 5ca526364c..2f59939ce7 100644 --- a/contracts/asset-proxy/contracts/src/interfaces/ICurve.sol +++ b/contracts/asset-proxy/contracts/src/interfaces/ICurve.sol @@ -22,22 +22,6 @@ pragma solidity ^0.5.9; // solhint-disable func-name-mixedcase interface ICurve { - /// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token. - /// This function exists on early versions of Curve (USDC/DAI) - /// @param i The token index being sold. - /// @param j The token index being bought. - /// @param sellAmount The amount of token being bought. - /// @param minBuyAmount The minimum buy amount of the token being bought. - /// @param deadline The time in seconds when this operation should expire. - function exchange_underlying( - int128 i, - int128 j, - uint256 sellAmount, - uint256 minBuyAmount, - uint256 deadline - ) - external; - /// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token. /// This function exists on later versions of Curve (USDC/DAI/USDT) /// @param i The token index being sold. diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.json b/contracts/erc20-bridge-sampler/CHANGELOG.json index 8f59f58093..cb32a6c40b 100644 --- a/contracts/erc20-bridge-sampler/CHANGELOG.json +++ b/contracts/erc20-bridge-sampler/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.8.0", + "changes": [ + { + "note": "Refactor and support more varied curves", + "pr": 2633 + } + ] + }, { "version": "1.7.0", "changes": [ diff --git a/contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol b/contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol new file mode 100644 index 0000000000..a6776b3457 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol @@ -0,0 +1,122 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; + + +contract ApproximateBuys { + + /// @dev Information computing buy quotes for sources that do not have native + /// buy quote support. + struct ApproximateBuyQuoteOpts { + // Arbitrary maker token data to pass to `getSellQuoteCallback`. + bytes makerTokenData; + // Arbitrary taker token data to pass to `getSellQuoteCallback`. + bytes takerTokenData; + // Callback to retrieve a sell quote. + function (bytes memory, bytes memory, uint256) + internal + view + returns (uint256) getSellQuoteCallback; + } + + uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4; + /// @dev Maximum approximate (positive) error rate when approximating a buy quote. + uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4; + /// @dev Maximum iterations to perform when approximating a buy quote. + uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5; + + function _sampleApproximateBuys( + ApproximateBuyQuoteOpts memory opts, + uint256[] memory makerTokenAmounts + ) + internal + view + returns (uint256[] memory takerTokenAmounts) + { + takerTokenAmounts = new uint256[](makerTokenAmounts.length); + if (makerTokenAmounts.length == 0) { + return takerTokenAmounts; + } + + uint256 sellAmount = opts.getSellQuoteCallback( + opts.makerTokenData, + opts.takerTokenData, + makerTokenAmounts[0] + ); + if (sellAmount == 0) { + return takerTokenAmounts; + } + + uint256 buyAmount = opts.getSellQuoteCallback( + opts.takerTokenData, + opts.makerTokenData, + sellAmount + ); + if (buyAmount == 0) { + return takerTokenAmounts; + } + + for (uint256 i = 0; i < makerTokenAmounts.length; i++) { + for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) { + // adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER + sellAmount = LibMath.getPartialAmountCeil( + makerTokenAmounts[i], + buyAmount, + sellAmount + ); + sellAmount = LibMath.getPartialAmountCeil( + (ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS), + ONE_HUNDED_PERCENT_BPS, + sellAmount + ); + uint256 _buyAmount = opts.getSellQuoteCallback( + opts.takerTokenData, + opts.makerTokenData, + sellAmount + ); + if (_buyAmount == 0) { + break; + } + // We re-use buyAmount next iteration, only assign if it is + // non zero + buyAmount = _buyAmount; + // If we've reached our goal, exit early + if (buyAmount >= makerTokenAmounts[i]) { + uint256 eps = + (buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS / + makerTokenAmounts[i]; + if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) { + break; + } + } + } + // We do our best to close in on the requested amount, but we can either over buy or under buy and exit + // if we hit a max iteration limit + // We scale the sell amount to get the approximate target + takerTokenAmounts[i] = LibMath.getPartialAmountCeil( + makerTokenAmounts[i], + buyAmount, + sellAmount + ); + } + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol new file mode 100644 index 0000000000..b5cce7c016 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol @@ -0,0 +1,156 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "./ICurve.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract CurveSampler is + SamplerUtils, + ApproximateBuys +{ + /// @dev Information for sampling from curve sources. + struct CurveInfo { + address poolAddress; + bytes4 sellQuoteFunctionSelector; + bytes4 buyQuoteFunctionSelector; + } + + /// @dev Base gas limit for Curve calls. Some Curves have multiple tokens + /// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens. + uint256 constant private CURVE_CALL_GAS = 600e3; // 600k + + /// @dev Sample sell quotes from Curve. + /// @param curveInfo Curve information specific to this token pair. + /// @param fromTokenIdx Index of the taker token (what to sell). + /// @param toTokenIdx Index of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromCurve( + CurveInfo memory curveInfo, + int128 fromTokenIdx, + int128 toTokenIdx, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)( + abi.encodeWithSelector( + curveInfo.sellQuoteFunctionSelector, + fromTokenIdx, + toTokenIdx, + takerTokenAmounts[i] + )); + uint256 buyAmount = 0; + if (didSucceed) { + buyAmount = abi.decode(resultData, (uint256)); + } else { + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample buy quotes from Curve. + /// @param curveInfo Curve information specific to this token pair. + /// @param fromTokenIdx Index of the taker token (what to sell). + /// @param toTokenIdx Index of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromCurve( + CurveInfo memory curveInfo, + int128 fromTokenIdx, + int128 toTokenIdx, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) { + // Buys not supported on this curve, so approximate it. + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(toTokenIdx, curveInfo), + takerTokenData: abi.encode(fromTokenIdx, curveInfo), + getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve + }), + makerTokenAmounts + ); + } + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)( + abi.encodeWithSelector( + curveInfo.buyQuoteFunctionSelector, + fromTokenIdx, + toTokenIdx, + makerTokenAmounts[i] + )); + uint256 sellAmount = 0; + if (didSucceed) { + sellAmount = abi.decode(resultData, (uint256)); + } else { + break; + } + takerTokenAmounts[i] = sellAmount; + } + } + + function _sampleSellForApproximateBuyFromCurve( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (int128 takerTokenIdx, CurveInfo memory curveInfo) = + abi.decode(takerTokenData, (int128, CurveInfo)); + (int128 makerTokenIdx) = + abi.decode(makerTokenData, (int128)); + (bool success, bytes memory resultData) = + address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromCurve.selector, + curveInfo, + takerTokenIdx, + makerTokenIdx, + _toSingleValueArray(sellAmount) + )); + if (!success) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256[]))[0]; + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 5098c07a02..7028238f61 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -19,53 +19,26 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; -import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; -import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "./IDevUtils.sol"; -import "./IERC20BridgeSampler.sol"; -import "./IEth2Dai.sol"; -import "./IKyberNetwork.sol"; -import "./IKyberNetworkProxy.sol"; -import "./IKyberStorage.sol"; -import "./IKyberHintHandler.sol"; -import "./IUniswapExchangeQuotes.sol"; -import "./ICurve.sol"; -import "./ILiquidityProvider.sol"; -import "./ILiquidityProviderRegistry.sol"; -import "./IUniswapV2Router01.sol"; -import "./IMultiBridge.sol"; +import "./CurveSampler.sol"; +import "./Eth2DaiSampler.sol"; +import "./KyberSampler.sol"; +import "./LiquidityProviderSampler.sol"; +import "./MultiBridgeSampler.sol"; +import "./NativeOrderSampler.sol"; +import "./UniswapSampler.sol"; +import "./UniswapV2Sampler.sol"; contract ERC20BridgeSampler is - IERC20BridgeSampler, - DeploymentConstants + Eth2DaiSampler, + UniswapSampler, + KyberSampler, + CurveSampler, + LiquidityProviderSampler, + UniswapV2Sampler, + MultiBridgeSampler, + NativeOrderSampler { - /// @dev Gas limit for DevUtils calls. - uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k - /// @dev Gas limit for Kyber calls. - uint256 constant internal KYBER_CALL_GAS = 1500e3; // 1.5m - /// @dev Gas limit for Uniswap calls. - uint256 constant internal UNISWAP_CALL_GAS = 150e3; // 150k - /// @dev Gas limit for UniswapV2 calls. - uint256 constant internal UNISWAPV2_CALL_GAS = 150e3; // 150k - /// @dev Base gas limit for Eth2Dai calls. - uint256 constant internal ETH2DAI_CALL_GAS = 1000e3; // 1m - /// @dev Base gas limit for Curve calls. Some Curves have multiple tokens - /// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens. - uint256 constant internal CURVE_CALL_GAS = 600e3; // 600k - /// @dev Default gas limit for liquidity provider calls. - uint256 constant internal DEFAULT_CALL_GAS = 400e3; // 400k - /// @dev The Kyber Uniswap Reserve address - address constant internal KYBER_UNISWAP_RESERVE = 0x31E085Afd48a1d6e51Cc193153d625e8f0514C7F; - /// @dev The Kyber Uniswap V2 Reserve address - address constant internal KYBER_UNISWAPV2_RESERVE = 0x10908C875D865C66f271F5d3949848971c9595C9; - /// @dev The Kyber Eth2Dai Reserve address - address constant internal KYBER_ETH2DAI_RESERVE = 0x1E158c0e93c30d24e918Ef83d1e0bE23595C3c0f; - /// @dev Call multiple public functions on this contract in a single transaction. /// @param callDatas ABI-encoded call data for each function call. /// @return callResults ABI-encoded results data for each call. @@ -83,961 +56,4 @@ contract ERC20BridgeSampler is callResults[i] = resultData; } } - - /// @dev Queries the fillable taker asset amounts of native orders. - /// Effectively ignores orders that have empty signatures or - /// maker/taker asset amounts (returning 0). - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param devUtilsAddress Address to the DevUtils contract. - /// @return orderFillableTakerAssetAmounts How much taker asset can be filled - /// by each order in `orders`. - function getOrderFillableTakerAssetAmounts( - LibOrder.Order[] memory orders, - bytes[] memory orderSignatures, - address devUtilsAddress - ) - public - view - returns (uint256[] memory orderFillableTakerAssetAmounts) - { - orderFillableTakerAssetAmounts = new uint256[](orders.length); - for (uint256 i = 0; i != orders.length; i++) { - // Ignore orders with no signature or empty maker/taker amounts. - if (orderSignatures[i].length == 0 || - orders[i].makerAssetAmount == 0 || - orders[i].takerAssetAmount == 0) { - orderFillableTakerAssetAmounts[i] = 0; - continue; - } - // solhint-disable indent - (bool didSucceed, bytes memory resultData) = - devUtilsAddress - .staticcall - .gas(DEV_UTILS_CALL_GAS) - (abi.encodeWithSelector( - IDevUtils(devUtilsAddress).getOrderRelevantState.selector, - orders[i], - orderSignatures[i] - )); - // solhint-enable indent - if (!didSucceed) { - orderFillableTakerAssetAmounts[i] = 0; - continue; - } - ( - LibOrder.OrderInfo memory orderInfo, - uint256 fillableTakerAssetAmount, - bool isValidSignature - ) = abi.decode( - resultData, - (LibOrder.OrderInfo, uint256, bool) - ); - // The fillable amount is zero if the order is not fillable or if the - // signature is invalid. - if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE || - !isValidSignature) { - orderFillableTakerAssetAmounts[i] = 0; - } else { - orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount; - } - } - } - - /// @dev Queries the fillable taker asset amounts of native orders. - /// Effectively ignores orders that have empty signatures or - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param devUtilsAddress Address to the DevUtils contract. - /// @return orderFillableMakerAssetAmounts How much maker asset can be filled - /// by each order in `orders`. - function getOrderFillableMakerAssetAmounts( - LibOrder.Order[] memory orders, - bytes[] memory orderSignatures, - address devUtilsAddress - ) - public - view - returns (uint256[] memory orderFillableMakerAssetAmounts) - { - orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts( - orders, - orderSignatures, - devUtilsAddress - ); - // `orderFillableMakerAssetAmounts` now holds taker asset amounts, so - // convert them to maker asset amounts. - for (uint256 i = 0; i < orders.length; ++i) { - if (orderFillableMakerAssetAmounts[i] != 0) { - orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil( - orderFillableMakerAssetAmounts[i], - orders[i].takerAssetAmount, - orders[i].makerAssetAmount - ); - } - } - } - - /// @dev Sample sell quotes from Kyber. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromKyberNetwork( - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - address wethAddress = _getWethAddress(); - uint256 value; - for (uint256 i = 0; i < numSamples; i++) { - if (takerToken == wethAddress || makerToken == wethAddress) { - // Direct ETH based trade - value = _sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts[i]); - } else { - // Hop to ETH - value = _sampleSellFromKyberNetwork(takerToken, wethAddress, takerTokenAmounts[i]); - if (value != 0) { - value = _sampleSellFromKyberNetwork(wethAddress, makerToken, value); - } - } - makerTokenAmounts[i] = value; - } - } - - /// @dev Sample buy quotes from Kyber. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @param opts `FakeBuyOptions` specifying target slippage and max iterations. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromKyberNetwork( - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts, - FakeBuyOptions memory opts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - return _sampleApproximateBuysFromSource( - takerToken, - makerToken, - makerTokenAmounts, - opts, - this.sampleSellsFromKyberNetwork.selector, - address(0) // PLP registry address - ); - } - - /// @dev Sample sell quotes from Eth2Dai/Oasis. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromEth2Dai( - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( - abi.encodeWithSelector( - IEth2Dai(0).getBuyAmount.selector, - makerToken, - takerToken, - takerTokenAmounts[i] - )); - uint256 buyAmount = 0; - if (didSucceed) { - buyAmount = abi.decode(resultData, (uint256)); - } else{ - break; - } - makerTokenAmounts[i] = buyAmount; - } - } - - /// @dev Sample sell quotes from Eth2Dai/Oasis using a hop to an intermediate token. - /// I.e WBTC/DAI via ETH or WBTC/ETH via DAI - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param intermediateToken Address of the token to hop to. - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromEth2DaiHop( - address takerToken, - address makerToken, - address intermediateToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - if (makerToken == intermediateToken || takerToken == intermediateToken) { - return makerTokenAmounts; - } - uint256[] memory intermediateAmounts = sampleSellsFromEth2Dai(takerToken, intermediateToken, takerTokenAmounts); - makerTokenAmounts = sampleSellsFromEth2Dai(intermediateToken, makerToken, intermediateAmounts); - } - - /// @dev Sample buy quotes from Eth2Dai/Oasis. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Maker token sell amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromEth2Dai( - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - uint256 numSamples = makerTokenAmounts.length; - takerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( - abi.encodeWithSelector( - IEth2Dai(0).getPayAmount.selector, - takerToken, - makerToken, - makerTokenAmounts[i] - )); - uint256 sellAmount = 0; - if (didSucceed) { - sellAmount = abi.decode(resultData, (uint256)); - } else { - break; - } - takerTokenAmounts[i] = sellAmount; - } - } - - /// @dev Sample sell quotes from Uniswap. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromUniswap( - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? - IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); - IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? - IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); - for (uint256 i = 0; i < numSamples; i++) { - bool didSucceed = true; - if (makerToken == _getWethAddress()) { - (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(takerTokenExchange), - takerTokenExchange.getTokenToEthInputPrice.selector, - takerTokenAmounts[i] - ); - } else if (takerToken == _getWethAddress()) { - (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(makerTokenExchange), - makerTokenExchange.getEthToTokenInputPrice.selector, - takerTokenAmounts[i] - ); - } else { - uint256 ethBought; - (ethBought, didSucceed) = _callUniswapExchangePriceFunction( - address(takerTokenExchange), - takerTokenExchange.getTokenToEthInputPrice.selector, - takerTokenAmounts[i] - ); - if (ethBought != 0) { - (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(makerTokenExchange), - makerTokenExchange.getEthToTokenInputPrice.selector, - ethBought - ); - } else { - makerTokenAmounts[i] = 0; - } - } - if (!didSucceed) { - break; - } - } - } - - /// @dev Sample buy quotes from Uniswap. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token sell amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromUniswap( - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - uint256 numSamples = makerTokenAmounts.length; - takerTokenAmounts = new uint256[](numSamples); - IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? - IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); - IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? - IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); - for (uint256 i = 0; i < numSamples; i++) { - bool didSucceed = true; - if (makerToken == _getWethAddress()) { - (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(takerTokenExchange), - takerTokenExchange.getTokenToEthOutputPrice.selector, - makerTokenAmounts[i] - ); - } else if (takerToken == _getWethAddress()) { - (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(makerTokenExchange), - makerTokenExchange.getEthToTokenOutputPrice.selector, - makerTokenAmounts[i] - ); - } else { - uint256 ethSold; - (ethSold, didSucceed) = _callUniswapExchangePriceFunction( - address(makerTokenExchange), - makerTokenExchange.getEthToTokenOutputPrice.selector, - makerTokenAmounts[i] - ); - if (ethSold != 0) { - (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( - address(takerTokenExchange), - takerTokenExchange.getTokenToEthOutputPrice.selector, - ethSold - ); - } else { - takerTokenAmounts[i] = 0; - } - } - if (!didSucceed) { - break; - } - } - } - - /// @dev Sample sell quotes from Curve. - /// @param curveAddress Address of the Curve contract. - /// @param fromTokenIdx Index of the taker token (what to sell). - /// @param toTokenIdx Index of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromCurve( - address curveAddress, - int128 fromTokenIdx, - int128 toTokenIdx, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - curveAddress.staticcall.gas(CURVE_CALL_GAS)( - abi.encodeWithSelector( - ICurve(0).get_dy_underlying.selector, - fromTokenIdx, - toTokenIdx, - takerTokenAmounts[i] - )); - uint256 buyAmount = 0; - if (didSucceed) { - buyAmount = abi.decode(resultData, (uint256)); - } else { - break; - } - makerTokenAmounts[i] = buyAmount; - } - } - - /// @dev Sample buy quotes from Curve. - /// @param curveAddress Address of the Curve contract. - /// @param fromTokenIdx Index of the taker token (what to sell). - /// @param toTokenIdx Index of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromCurve( - address curveAddress, - int128 fromTokenIdx, - int128 toTokenIdx, - uint256[] memory makerTokenAmounts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - uint256 numSamples = makerTokenAmounts.length; - takerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - curveAddress.staticcall.gas(CURVE_CALL_GAS)( - abi.encodeWithSelector( - ICurve(0).get_dx_underlying.selector, - fromTokenIdx, - toTokenIdx, - makerTokenAmounts[i] - )); - uint256 sellAmount = 0; - if (didSucceed) { - sellAmount = abi.decode(resultData, (uint256)); - } else { - break; - } - takerTokenAmounts[i] = sellAmount; - } - } - - /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. - /// @param registryAddress Address of the liquidity provider registry contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromLiquidityProviderRegistry( - address registryAddress, - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - // Initialize array of maker token amounts. - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - - // Query registry for provider address. - address providerAddress = getLiquidityProviderFromRegistry( - registryAddress, - takerToken, - makerToken - ); - // If provider doesn't exist, return all zeros. - if (providerAddress == address(0)) { - return makerTokenAmounts; - } - - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - providerAddress.staticcall.gas(DEFAULT_CALL_GAS)( - abi.encodeWithSelector( - ILiquidityProvider(0).getSellQuote.selector, - takerToken, - makerToken, - takerTokenAmounts[i] - )); - uint256 buyAmount = 0; - if (didSucceed) { - buyAmount = abi.decode(resultData, (uint256)); - } else { - // Exit early if the amount is too high for the liquidity provider to serve - break; - } - makerTokenAmounts[i] = buyAmount; - } - } - - /// @dev Sample sell quotes from MultiBridge. - /// @param multibridge Address of the MultiBridge contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param intermediateToken The address of the intermediate token to - /// use in an indirect route. - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromMultiBridge( - address multibridge, - address takerToken, - address intermediateToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - // Initialize array of maker token amounts. - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - - // If no address provided, return all zeros. - if (multibridge == address(0)) { - return makerTokenAmounts; - } - - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - multibridge.staticcall.gas(DEFAULT_CALL_GAS)( - abi.encodeWithSelector( - IMultiBridge(0).getSellQuote.selector, - takerToken, - intermediateToken, - makerToken, - takerTokenAmounts[i] - )); - uint256 buyAmount = 0; - if (didSucceed) { - buyAmount = abi.decode(resultData, (uint256)); - } else { - // Exit early if the amount is too high for the liquidity provider to serve - break; - } - makerTokenAmounts[i] = buyAmount; - } - } - - /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. - /// @param registryAddress Address of the liquidity provider registry contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @param opts `FakeBuyOptions` specifying target slippage and max iterations. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromLiquidityProviderRegistry( - address registryAddress, - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts, - FakeBuyOptions memory opts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - return _sampleApproximateBuysFromSource( - takerToken, - makerToken, - makerTokenAmounts, - opts, - this.sampleSellsFromLiquidityProviderRegistry.selector, - registryAddress - ); - } - - /// @dev Returns the address of a liquidity provider for the given market - /// (takerToken, makerToken), from a registry of liquidity providers. - /// Returns address(0) if no such provider exists in the registry. - /// @param takerToken Taker asset managed by liquidity provider. - /// @param makerToken Maker asset managed by liquidity provider. - /// @return providerAddress Address of the liquidity provider. - function getLiquidityProviderFromRegistry( - address registryAddress, - address takerToken, - address makerToken - ) - public - view - returns (address providerAddress) - { - bytes memory callData = abi.encodeWithSelector( - ILiquidityProviderRegistry(0).getLiquidityProviderForMarket.selector, - takerToken, - makerToken - ); - (bool didSucceed, bytes memory returnData) = registryAddress.staticcall(callData); - if (didSucceed && returnData.length == 32) { - return LibBytes.readAddress(returnData, 12); - } - } - - /// @dev Sample sell quotes from UniswapV2. - /// @param path Token route. Should be takerToken -> makerToken - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromUniswapV2( - address[] memory path, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - _getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)( - abi.encodeWithSelector( - IUniswapV2Router01(0).getAmountsOut.selector, - takerTokenAmounts[i], - path - )); - uint256 buyAmount = 0; - if (didSucceed) { - // solhint-disable-next-line indent - buyAmount = abi.decode(resultData, (uint256[]))[path.length - 1]; - } else { - break; - } - makerTokenAmounts[i] = buyAmount; - } - } - - /// @dev Sample buy quotes from UniswapV2. - /// @param path Token route. Should be takerToken -> makerToken. - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromUniswapV2( - address[] memory path, - uint256[] memory makerTokenAmounts - ) - public - view - returns (uint256[] memory takerTokenAmounts) - { - uint256 numSamples = makerTokenAmounts.length; - takerTokenAmounts = new uint256[](numSamples); - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - _getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)( - abi.encodeWithSelector( - IUniswapV2Router01(0).getAmountsIn.selector, - makerTokenAmounts[i], - path - )); - uint256 sellAmount = 0; - if (didSucceed) { - // solhint-disable-next-line indent - sellAmount = abi.decode(resultData, (uint256[]))[0]; - } else { - break; - } - takerTokenAmounts[i] = sellAmount; - } - } - - /// @dev Overridable way to get token decimals. - /// @param tokenAddress Address of the token. - /// @return decimals The decimal places for the token. - function _getTokenDecimals(address tokenAddress) - internal - view - returns (uint8 decimals) - { - return LibERC20Token.decimals(tokenAddress); - } - - /// @dev Gracefully calls a Uniswap pricing function. - /// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange. - /// @param functionSelector Selector of the target function. - /// @param inputAmount Quantity parameter particular to the pricing function. - /// @return outputAmount The returned amount from the function call. Will be - /// zero if the call fails or if `uniswapExchangeAddress` is zero. - function _callUniswapExchangePriceFunction( - address uniswapExchangeAddress, - bytes4 functionSelector, - uint256 inputAmount - ) - private - view - returns (uint256 outputAmount, bool didSucceed) - { - if (uniswapExchangeAddress == address(0)) { - return (outputAmount, didSucceed); - } - bytes memory resultData; - (didSucceed, resultData) = - uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)( - abi.encodeWithSelector( - functionSelector, - inputAmount - )); - if (didSucceed) { - outputAmount = abi.decode(resultData, (uint256)); - } - } - - /// @dev Retrive an existing Uniswap exchange contract. - /// Throws if the exchange does not exist. - /// @param tokenAddress Address of the token contract. - /// @return exchange `IUniswapExchangeQuotes` for the token. - function _getUniswapExchange(address tokenAddress) - private - view - returns (IUniswapExchangeQuotes exchange) - { - exchange = IUniswapExchangeQuotes( - address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress()) - .getExchange(tokenAddress)) - ); - } - - /// @dev Assert that the tokens in a trade pair are valid. - /// @param makerToken Address of the maker token. - /// @param takerToken Address of the taker token. - function _assertValidPair(address makerToken, address takerToken) - private - pure - { - require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR"); - } - - function _sampleSellForApproximateBuy( - address takerToken, - address makerToken, - uint256 takerTokenAmount, - bytes4 selector, - address plpRegistryAddress - ) - private - view - returns (uint256 makerTokenAmount) - { - bytes memory callData; - uint256[] memory tmpTakerAmounts = new uint256[](1); - tmpTakerAmounts[0] = takerTokenAmount; - if (selector == this.sampleSellsFromKyberNetwork.selector) { - callData = abi.encodeWithSelector( - this.sampleSellsFromKyberNetwork.selector, - takerToken, - makerToken, - tmpTakerAmounts - ); - } else { - callData = abi.encodeWithSelector( - this.sampleSellsFromLiquidityProviderRegistry.selector, - plpRegistryAddress, - takerToken, - makerToken, - tmpTakerAmounts - ); - } - (bool success, bytes memory resultData) = address(this).staticcall(callData); - if (!success) { - return 0; - } - // solhint-disable indent - makerTokenAmount = abi.decode(resultData, (uint256[]))[0]; - } - - function _sampleApproximateBuysFromSource( - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts, - FakeBuyOptions memory opts, - bytes4 selector, - address plpRegistryAddress - ) - private - view - returns (uint256[] memory takerTokenAmounts) - { - _assertValidPair(makerToken, takerToken); - if (makerTokenAmounts.length == 0) { - return takerTokenAmounts; - } - uint256 sellAmount; - uint256 buyAmount; - uint256 slippageFromTarget; - takerTokenAmounts = new uint256[](makerTokenAmounts.length); - sellAmount = _sampleSellForApproximateBuy( - makerToken, - takerToken, - makerTokenAmounts[0], - selector, - plpRegistryAddress - ); - - if (sellAmount == 0) { - return takerTokenAmounts; - } - - buyAmount = _sampleSellForApproximateBuy( - takerToken, - makerToken, - sellAmount, - selector, - plpRegistryAddress - ); - if (buyAmount == 0) { - return takerTokenAmounts; - } - - for (uint256 i = 0; i < makerTokenAmounts.length; i++) { - for (uint256 iter = 0; iter < opts.maxIterations; iter++) { - // adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER - sellAmount = LibMath.getPartialAmountCeil( - makerTokenAmounts[i], - buyAmount, - sellAmount - ); - sellAmount = LibMath.getPartialAmountCeil( - (10000 + opts.targetSlippageBps), - 10000, - sellAmount - ); - uint256 _buyAmount = _sampleSellForApproximateBuy( - takerToken, - makerToken, - sellAmount, - selector, - plpRegistryAddress - ); - if (_buyAmount == 0) { - break; - } - // We re-use buyAmount next iteration, only assign if it is - // non zero - buyAmount = _buyAmount; - // If we've reached our goal, exit early - if (buyAmount >= makerTokenAmounts[i]) { - uint256 slippageFromTarget = (buyAmount - makerTokenAmounts[i]) * 10000 / - makerTokenAmounts[i]; - if (slippageFromTarget <= opts.targetSlippageBps) { - break; - } - } - } - // We do our best to close in on the requested amount, but we can either over buy or under buy and exit - // if we hit a max iteration limit - // We scale the sell amount to get the approximate target - takerTokenAmounts[i] = LibMath.getPartialAmountCeil( - makerTokenAmounts[i], - buyAmount, - sellAmount - ); - } - } - - function _appendToList(bytes32[] memory list, bytes32 item) private view returns (bytes32[] memory appendedList) - { - appendedList = new bytes32[](list.length + 1); - for (uint256 i = 0; i < list.length; i++) { - appendedList[i] = list[i]; - } - appendedList[appendedList.length - 1] = item; - } - - function _getKyberAddresses() - private - view - returns (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) - { - (, , kyberHint, kyberStorage, ,) = IKyberNetwork( - IKyberNetworkProxy(_getKyberNetworkProxyAddress()).kyberNetwork()).getContracts(); - return (IKyberHintHandler(kyberHint), IKyberStorage(kyberStorage)); - } - - function _sampleSellFromKyberNetwork( - address takerToken, - address makerToken, - uint256 takerTokenAmount - ) - private - view - returns (uint256 makerTokenAmount) - { - (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) = _getKyberAddresses(); - // Ban reserves which can clash with our internal aggregation - bytes32[] memory reserveIds = kyberStorage.getReserveIdsPerTokenSrc( - takerToken == _getWethAddress() ? makerToken : takerToken - ); - bytes32[] memory bannedReserveIds = new bytes32[](0); - // Poor mans resize and append - for (uint256 i = 0; i < reserveIds.length; i++) { - if ( - reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAP_RESERVE) || - reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAPV2_RESERVE) || - reserveIds[i] == kyberStorage.getReserveId(KYBER_ETH2DAI_RESERVE) - ) { - bannedReserveIds = _appendToList(bannedReserveIds, reserveIds[i]); - } - } - // Sampler either detects X->ETH/ETH->X - // or subsamples as X->ETH-Y. So token->token here is not possible - bytes memory hint; - if (takerToken == _getWethAddress()) { - // ETH -> X - hint = kyberHint.buildEthToTokenHint( - makerToken, - IKyberHintHandler.TradeType.MaskOut, - bannedReserveIds, - new uint256[](0)); - } else { - // X->ETH - hint = kyberHint.buildEthToTokenHint( - takerToken, - IKyberHintHandler.TradeType.MaskOut, - bannedReserveIds, - new uint256[](0)); - } - (bool didSucceed, bytes memory resultData) = - _getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)( - abi.encodeWithSelector( - IKyberNetworkProxy(0).getExpectedRateAfterFee.selector, - takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken, - makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken, - takerTokenAmount, - 0, // fee - hint - )); - uint256 rate = 0; - if (didSucceed) { - (rate) = abi.decode(resultData, (uint256)); - } else { - return 0; - } - - uint256 makerTokenDecimals = _getTokenDecimals(makerToken); - uint256 takerTokenDecimals = _getTokenDecimals(takerToken); - makerTokenAmount = - rate * - takerTokenAmount * - 10 ** makerTokenDecimals / - 10 ** takerTokenDecimals / - 10 ** 18; - return makerTokenAmount; - } } diff --git a/contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol new file mode 100644 index 0000000000..13ca9075cf --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol @@ -0,0 +1,140 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "./IEth2Dai.sol"; +import "./SamplerUtils.sol"; + + +contract Eth2DaiSampler is + DeploymentConstants, + SamplerUtils +{ + /// @dev Base gas limit for Eth2Dai calls. + uint256 constant private ETH2DAI_CALL_GAS = 1000e3; // 1m + + /// @dev Sample sell quotes from Eth2Dai/Oasis. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromEth2Dai( + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( + abi.encodeWithSelector( + IEth2Dai(0).getBuyAmount.selector, + makerToken, + takerToken, + takerTokenAmounts[i] + )); + uint256 buyAmount = 0; + if (didSucceed) { + buyAmount = abi.decode(resultData, (uint256)); + } else{ + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample sell quotes from Eth2Dai/Oasis using a hop to an intermediate token. + /// I.e WBTC/DAI via ETH or WBTC/ETH via DAI + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param intermediateToken Address of the token to hop to. + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromEth2DaiHop( + address takerToken, + address makerToken, + address intermediateToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + if (makerToken == intermediateToken || takerToken == intermediateToken) { + return makerTokenAmounts; + } + uint256[] memory intermediateAmounts = sampleSellsFromEth2Dai( + takerToken, + intermediateToken, + takerTokenAmounts + ); + makerTokenAmounts = sampleSellsFromEth2Dai( + intermediateToken, + makerToken, + intermediateAmounts + ); + } + + /// @dev Sample buy quotes from Eth2Dai/Oasis. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Maker token sell amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromEth2Dai( + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( + abi.encodeWithSelector( + IEth2Dai(0).getPayAmount.selector, + takerToken, + makerToken, + makerTokenAmounts[i] + )); + uint256 sellAmount = 0; + if (didSucceed) { + sellAmount = abi.decode(resultData, (uint256)); + } else { + break; + } + takerTokenAmounts[i] = sellAmount; + } + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/ICurve.sol b/contracts/erc20-bridge-sampler/contracts/src/ICurve.sol index ac2645106b..13a0d75ea0 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ICurve.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ICurve.sol @@ -22,22 +22,6 @@ pragma solidity ^0.5.9; // solhint-disable func-name-mixedcase interface ICurve { - /// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token. - /// This function exists on early versions of Curve (USDC/DAI) - /// @param i The token index being sold. - /// @param j The token index being bought. - /// @param sellAmount The amount of token being bought. - /// @param minBuyAmount The minimum buy amount of the token being bought. - /// @param deadline The time in seconds when this operation should expire. - function exchange_underlying( - int128 i, - int128 j, - uint256 sellAmount, - uint256 minBuyAmount, - uint256 deadline - ) - external; - /// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token. /// This function exists on later versions of Curve (USDC/DAI/USDT) /// @param i The token index being sold. diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol deleted file mode 100644 index 69a6895c76..0000000000 --- a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol +++ /dev/null @@ -1,293 +0,0 @@ -/* - - Copyright 2019 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.5.9; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; - - -interface IERC20BridgeSampler { - - struct FakeBuyOptions { - uint256 targetSlippageBps; - uint256 maxIterations; - } - - /// @dev Call multiple public functions on this contract in a single transaction. - /// @param callDatas ABI-encoded call data for each function call. - /// @return callResults ABI-encoded results data for each call. - function batchCall(bytes[] calldata callDatas) - external - view - returns (bytes[] memory callResults); - - /// @dev Queries the fillable taker asset amounts of native orders. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param devUtilsAddress Address to the DevUtils contract. - /// @return orderFillableTakerAssetAmounts How much taker asset can be filled - /// by each order in `orders`. - function getOrderFillableTakerAssetAmounts( - LibOrder.Order[] calldata orders, - bytes[] calldata orderSignatures, - address devUtilsAddress - ) - external - view - returns (uint256[] memory orderFillableTakerAssetAmounts); - - /// @dev Queries the fillable maker asset amounts of native orders. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param devUtilsAddress Address to the DevUtils contract. - /// @return orderFillableMakerAssetAmounts How much maker asset can be filled - /// by each order in `orders`. - function getOrderFillableMakerAssetAmounts( - LibOrder.Order[] calldata orders, - bytes[] calldata orderSignatures, - address devUtilsAddress - ) - external - view - returns (uint256[] memory orderFillableMakerAssetAmounts); - - /// @dev Sample sell quotes from Kyber. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromKyberNetwork( - address takerToken, - address makerToken, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample buy quotes from Kyber. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @param opts `FakeBuyOptions` specifying target slippage and max iterations. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromKyberNetwork( - address takerToken, - address makerToken, - uint256[] calldata makerTokenAmounts, - FakeBuyOptions calldata opts - ) - external - view - returns (uint256[] memory takerTokenAmounts); - - /// @dev Sample sell quotes from Eth2Dai/Oasis. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromEth2Dai( - address takerToken, - address makerToken, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample sell quotes from Uniswap. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromUniswap( - address takerToken, - address makerToken, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample buy quotes from Uniswap. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromUniswap( - address takerToken, - address makerToken, - uint256[] calldata makerTokenAmounts - ) - external - view - returns (uint256[] memory takerTokenAmounts); - - /// @dev Sample buy quotes from Eth2Dai/Oasis. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromEth2Dai( - address takerToken, - address makerToken, - uint256[] calldata makerTokenAmounts - ) - external - view - returns (uint256[] memory takerTokenAmounts); - - /// @dev Sample sell quotes from Curve. - /// @param curveAddress Address of the Curve contract. - /// @param fromTokenIdx Index of the taker token (what to sell). - /// @param toTokenIdx Index of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromCurve( - address curveAddress, - int128 fromTokenIdx, - int128 toTokenIdx, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample buy quotes from Curve. - /// @param curveAddress Address of the Curve contract. - /// @param fromTokenIdx Index of the taker token (what to sell). - /// @param toTokenIdx Index of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromCurve( - address curveAddress, - int128 fromTokenIdx, - int128 toTokenIdx, - uint256[] calldata makerTokenAmounts - ) - external - view - returns (uint256[] memory takerTokenAmounts); - - /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. - /// @param registryAddress Address of the liquidity provider registry contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromLiquidityProviderRegistry( - address registryAddress, - address takerToken, - address makerToken, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample sell quotes from MultiBridge. - /// @param multibridge Address of the MultiBridge contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param intermediateToken The address of the intermediate token to - /// use in an indirect route. - /// @param makerToken Address of the maker token (what to buy). - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromMultiBridge( - address multibridge, - address takerToken, - address intermediateToken, - address makerToken, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. - /// @param registryAddress Address of the liquidity provider registry contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param makerToken Address of the maker token (what to buy). - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @param opts `FakeBuyOptions` specifying target slippage and max iterations. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromLiquidityProviderRegistry( - address registryAddress, - address takerToken, - address makerToken, - uint256[] calldata makerTokenAmounts, - FakeBuyOptions calldata opts - - ) - external - view - returns (uint256[] memory takerTokenAmounts); - - /// @dev Returns the address of a liquidity provider for the given market - /// (takerToken, makerToken), from a registry of liquidity providers. - /// Returns address(0) if no such provider exists in the registry. - /// @param takerToken Taker asset managed by liquidity provider. - /// @param makerToken Maker asset managed by liquidity provider. - /// @return providerAddress Address of the liquidity provider. - function getLiquidityProviderFromRegistry( - address registryAddress, - address takerToken, - address makerToken - ) - external - view - returns (address providerAddress); - - /// @dev Sample sell quotes from UniswapV2. - /// @param path Token route. - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. - function sampleSellsFromUniswapV2( - address[] calldata path, - uint256[] calldata takerTokenAmounts - ) - external - view - returns (uint256[] memory makerTokenAmounts); - - /// @dev Sample buy quotes from UniswapV2. - /// @param path Token route. - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. - function sampleBuysFromUniswapV2( - address[] calldata path, - uint256[] calldata makerTokenAmounts - ) - external - view - returns (uint256[] memory takerTokenAmounts); -} diff --git a/contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol new file mode 100644 index 0000000000..59fca06978 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol @@ -0,0 +1,218 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "./IKyberNetwork.sol"; +import "./IKyberNetworkProxy.sol"; +import "./IKyberStorage.sol"; +import "./IKyberHintHandler.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract KyberSampler is + DeploymentConstants, + SamplerUtils, + ApproximateBuys +{ + /// @dev Gas limit for Kyber calls. + uint256 constant private KYBER_CALL_GAS = 1500e3; // 1.5m + /// @dev The Kyber Uniswap Reserve address + address constant private KYBER_UNISWAP_RESERVE = 0x31E085Afd48a1d6e51Cc193153d625e8f0514C7F; + /// @dev The Kyber Uniswap V2 Reserve address + address constant private KYBER_UNISWAPV2_RESERVE = 0x10908C875D865C66f271F5d3949848971c9595C9; + /// @dev The Kyber Eth2Dai Reserve address + address constant private KYBER_ETH2DAI_RESERVE = 0x1E158c0e93c30d24e918Ef83d1e0bE23595C3c0f; + + /// @dev Sample sell quotes from Kyber. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromKyberNetwork( + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + address wethAddress = _getWethAddress(); + uint256 value; + for (uint256 i = 0; i < numSamples; i++) { + if (takerToken == wethAddress || makerToken == wethAddress) { + // Direct ETH based trade + value = _sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts[i]); + } else { + // Hop to ETH + value = _sampleSellFromKyberNetwork(takerToken, wethAddress, takerTokenAmounts[i]); + if (value != 0) { + value = _sampleSellFromKyberNetwork(wethAddress, makerToken, value); + } + } + makerTokenAmounts[i] = value; + } + } + + /// @dev Sample buy quotes from Kyber. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromKyberNetwork( + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken), + takerTokenData: abi.encode(takerToken), + getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromKyber( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (bool success, bytes memory resultData) = + address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromKyberNetwork.selector, + abi.decode(takerTokenData, (address)), + abi.decode(makerTokenData, (address)), + _toSingleValueArray(sellAmount) + )); + if (!success) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256[]))[0]; + } + + function _appendToList(bytes32[] memory list, bytes32 item) private view returns (bytes32[] memory appendedList) + { + appendedList = new bytes32[](list.length + 1); + for (uint256 i = 0; i < list.length; i++) { + appendedList[i] = list[i]; + } + appendedList[appendedList.length - 1] = item; + } + + function _getKyberAddresses() + private + view + returns (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) + { + (, , kyberHint, kyberStorage, ,) = IKyberNetwork( + IKyberNetworkProxy(_getKyberNetworkProxyAddress()).kyberNetwork()).getContracts(); + return (IKyberHintHandler(kyberHint), IKyberStorage(kyberStorage)); + } + + function _sampleSellFromKyberNetwork( + address takerToken, + address makerToken, + uint256 takerTokenAmount + ) + private + view + returns (uint256 makerTokenAmount) + { + (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) = _getKyberAddresses(); + // Ban reserves which can clash with our internal aggregation + bytes32[] memory reserveIds = kyberStorage.getReserveIdsPerTokenSrc( + takerToken == _getWethAddress() ? makerToken : takerToken + ); + bytes32[] memory bannedReserveIds = new bytes32[](0); + // Poor mans resize and append + for (uint256 i = 0; i < reserveIds.length; i++) { + if ( + reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAP_RESERVE) || + reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAPV2_RESERVE) || + reserveIds[i] == kyberStorage.getReserveId(KYBER_ETH2DAI_RESERVE) + ) { + bannedReserveIds = _appendToList(bannedReserveIds, reserveIds[i]); + } + } + // Sampler either detects X->ETH/ETH->X + // or subsamples as X->ETH-Y. So token->token here is not possible + bytes memory hint; + if (takerToken == _getWethAddress()) { + // ETH -> X + hint = kyberHint.buildEthToTokenHint( + makerToken, + IKyberHintHandler.TradeType.MaskOut, + bannedReserveIds, + new uint256[](0)); + } else { + // X->ETH + hint = kyberHint.buildEthToTokenHint( + takerToken, + IKyberHintHandler.TradeType.MaskOut, + bannedReserveIds, + new uint256[](0)); + } + (bool didSucceed, bytes memory resultData) = + _getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)( + abi.encodeWithSelector( + IKyberNetworkProxy(0).getExpectedRateAfterFee.selector, + takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken, + makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken, + takerTokenAmount, + 0, // fee + hint + )); + uint256 rate = 0; + if (didSucceed) { + (rate) = abi.decode(resultData, (uint256)); + } else { + return 0; + } + + uint256 makerTokenDecimals = _getTokenDecimals(makerToken); + uint256 takerTokenDecimals = _getTokenDecimals(takerToken); + makerTokenAmount = + rate * + takerTokenAmount * + 10 ** makerTokenDecimals / + 10 ** takerTokenDecimals / + 10 ** 18; + return makerTokenAmount; + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/LiquidityProviderSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/LiquidityProviderSampler.sol new file mode 100644 index 0000000000..b10b5912b0 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/LiquidityProviderSampler.sol @@ -0,0 +1,168 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "./ILiquidityProvider.sol"; +import "./ILiquidityProviderRegistry.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract LiquidityProviderSampler is + SamplerUtils, + ApproximateBuys +{ + /// @dev Default gas limit for liquidity provider calls. + uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k + + /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. + /// @param registryAddress Address of the liquidity provider registry contract. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromLiquidityProviderRegistry( + address registryAddress, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + // Initialize array of maker token amounts. + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + // Query registry for provider address. + address providerAddress = getLiquidityProviderFromRegistry( + registryAddress, + takerToken, + makerToken + ); + // If provider doesn't exist, return all zeros. + if (providerAddress == address(0)) { + return makerTokenAmounts; + } + + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + providerAddress.staticcall.gas(DEFAULT_CALL_GAS)( + abi.encodeWithSelector( + ILiquidityProvider(0).getSellQuote.selector, + takerToken, + makerToken, + takerTokenAmounts[i] + )); + uint256 buyAmount = 0; + if (didSucceed) { + buyAmount = abi.decode(resultData, (uint256)); + } else { + // Exit early if the amount is too high for the liquidity provider to serve + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. + /// @param registryAddress Address of the liquidity provider registry contract. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromLiquidityProviderRegistry( + address registryAddress, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken, registryAddress), + takerTokenData: abi.encode(takerToken, registryAddress), + getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProviderRegistry + }), + makerTokenAmounts + ); + } + + /// @dev Returns the address of a liquidity provider for the given market + /// (takerToken, makerToken), from a registry of liquidity providers. + /// Returns address(0) if no such provider exists in the registry. + /// @param takerToken Taker asset managed by liquidity provider. + /// @param makerToken Maker asset managed by liquidity provider. + /// @return providerAddress Address of the liquidity provider. + function getLiquidityProviderFromRegistry( + address registryAddress, + address takerToken, + address makerToken + ) + public + view + returns (address providerAddress) + { + bytes memory callData = abi.encodeWithSelector( + ILiquidityProviderRegistry(0).getLiquidityProviderForMarket.selector, + takerToken, + makerToken + ); + (bool didSucceed, bytes memory returnData) = registryAddress.staticcall(callData); + if (didSucceed && returnData.length == 32) { + return LibBytes.readAddress(returnData, 12); + } + } + + function _sampleSellForApproximateBuyFromLiquidityProviderRegistry( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (address takerToken, address plpRegistryAddress) = + abi.decode(takerTokenData, (address, address)); + (address makerToken) = + abi.decode(makerTokenData, (address)); + (bool success, bytes memory resultData) = + address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromLiquidityProviderRegistry.selector, + plpRegistryAddress, + takerToken, + makerToken, + _toSingleValueArray(sellAmount) + )); + if (!success) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256[]))[0]; + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/MultiBridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/MultiBridgeSampler.sol new file mode 100644 index 0000000000..0e85f34f0c --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/MultiBridgeSampler.sol @@ -0,0 +1,79 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "./IMultiBridge.sol"; + + +contract MultiBridgeSampler { + + /// @dev Default gas limit for multibridge calls. + uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k + + /// @dev Sample sell quotes from MultiBridge. + /// @param multibridge Address of the MultiBridge contract. + /// @param takerToken Address of the taker token (what to sell). + /// @param intermediateToken The address of the intermediate token to + /// use in an indirect route. + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromMultiBridge( + address multibridge, + address takerToken, + address intermediateToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + // Initialize array of maker token amounts. + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + // If no address provided, return all zeros. + if (multibridge == address(0)) { + return makerTokenAmounts; + } + + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + multibridge.staticcall.gas(DEFAULT_CALL_GAS)( + abi.encodeWithSelector( + IMultiBridge(0).getSellQuote.selector, + takerToken, + intermediateToken, + makerToken, + takerTokenAmounts[i] + )); + uint256 buyAmount = 0; + if (didSucceed) { + buyAmount = abi.decode(resultData, (uint256)); + } else { + // Exit early if the amount is too high for the liquidity provider to serve + break; + } + makerTokenAmounts[i] = buyAmount; + } + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/NativeOrderSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/NativeOrderSampler.sol new file mode 100644 index 0000000000..a7798f7d42 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/NativeOrderSampler.sol @@ -0,0 +1,125 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; +import "./IDevUtils.sol"; + + +contract NativeOrderSampler { + + /// @dev Gas limit for DevUtils calls. + uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k + + /// @dev Queries the fillable taker asset amounts of native orders. + /// Effectively ignores orders that have empty signatures or + /// maker/taker asset amounts (returning 0). + /// @param orders Native orders to query. + /// @param orderSignatures Signatures for each respective order in `orders`. + /// @param devUtilsAddress Address to the DevUtils contract. + /// @return orderFillableTakerAssetAmounts How much taker asset can be filled + /// by each order in `orders`. + function getOrderFillableTakerAssetAmounts( + LibOrder.Order[] memory orders, + bytes[] memory orderSignatures, + address devUtilsAddress + ) + public + view + returns (uint256[] memory orderFillableTakerAssetAmounts) + { + orderFillableTakerAssetAmounts = new uint256[](orders.length); + for (uint256 i = 0; i != orders.length; i++) { + // Ignore orders with no signature or empty maker/taker amounts. + if (orderSignatures[i].length == 0 || + orders[i].makerAssetAmount == 0 || + orders[i].takerAssetAmount == 0) { + orderFillableTakerAssetAmounts[i] = 0; + continue; + } + // solhint-disable indent + (bool didSucceed, bytes memory resultData) = + devUtilsAddress + .staticcall + .gas(DEV_UTILS_CALL_GAS) + (abi.encodeWithSelector( + IDevUtils(devUtilsAddress).getOrderRelevantState.selector, + orders[i], + orderSignatures[i] + )); + // solhint-enable indent + if (!didSucceed) { + orderFillableTakerAssetAmounts[i] = 0; + continue; + } + ( + LibOrder.OrderInfo memory orderInfo, + uint256 fillableTakerAssetAmount, + bool isValidSignature + ) = abi.decode( + resultData, + (LibOrder.OrderInfo, uint256, bool) + ); + // The fillable amount is zero if the order is not fillable or if the + // signature is invalid. + if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE || + !isValidSignature) { + orderFillableTakerAssetAmounts[i] = 0; + } else { + orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount; + } + } + } + + /// @dev Queries the fillable taker asset amounts of native orders. + /// Effectively ignores orders that have empty signatures or + /// @param orders Native orders to query. + /// @param orderSignatures Signatures for each respective order in `orders`. + /// @param devUtilsAddress Address to the DevUtils contract. + /// @return orderFillableMakerAssetAmounts How much maker asset can be filled + /// by each order in `orders`. + function getOrderFillableMakerAssetAmounts( + LibOrder.Order[] memory orders, + bytes[] memory orderSignatures, + address devUtilsAddress + ) + public + view + returns (uint256[] memory orderFillableMakerAssetAmounts) + { + orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts( + orders, + orderSignatures, + devUtilsAddress + ); + // `orderFillableMakerAssetAmounts` now holds taker asset amounts, so + // convert them to maker asset amounts. + for (uint256 i = 0; i < orders.length; ++i) { + if (orderFillableMakerAssetAmounts[i] != 0) { + orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil( + orderFillableMakerAssetAmounts[i], + orders[i].takerAssetAmount, + orders[i].makerAssetAmount + ); + } + } + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/SamplerUtils.sol b/contracts/erc20-bridge-sampler/contracts/src/SamplerUtils.sol new file mode 100644 index 0000000000..01c5dbbdb8 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/SamplerUtils.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; + + +contract SamplerUtils { + + /// @dev Overridable way to get token decimals. + /// @param tokenAddress Address of the token. + /// @return decimals The decimal places for the token. + function _getTokenDecimals(address tokenAddress) + internal + view + returns (uint8 decimals) + { + return LibERC20Token.decimals(tokenAddress); + } + + function _toSingleValueArray(uint256 v) + internal + pure + returns (uint256[] memory arr) + { + arr = new uint256[](1); + arr[0] = v; + } + + /// @dev Assert that the tokens in a trade pair are valid. + /// @param makerToken Address of the maker token. + /// @param takerToken Address of the taker token. + function _assertValidPair(address makerToken, address takerToken) + internal + pure + { + require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR"); + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol new file mode 100644 index 0000000000..7137653daa --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol @@ -0,0 +1,201 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "./IUniswapExchangeQuotes.sol"; +import "./SamplerUtils.sol"; + + +contract UniswapSampler is + DeploymentConstants, + SamplerUtils +{ + /// @dev Gas limit for Uniswap calls. + uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k + + /// @dev Sample sell quotes from Uniswap. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromUniswap( + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); + IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); + for (uint256 i = 0; i < numSamples; i++) { + bool didSucceed = true; + if (makerToken == _getWethAddress()) { + (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(takerTokenExchange), + takerTokenExchange.getTokenToEthInputPrice.selector, + takerTokenAmounts[i] + ); + } else if (takerToken == _getWethAddress()) { + (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(makerTokenExchange), + makerTokenExchange.getEthToTokenInputPrice.selector, + takerTokenAmounts[i] + ); + } else { + uint256 ethBought; + (ethBought, didSucceed) = _callUniswapExchangePriceFunction( + address(takerTokenExchange), + takerTokenExchange.getTokenToEthInputPrice.selector, + takerTokenAmounts[i] + ); + if (ethBought != 0) { + (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(makerTokenExchange), + makerTokenExchange.getEthToTokenInputPrice.selector, + ethBought + ); + } else { + makerTokenAmounts[i] = 0; + } + } + if (!didSucceed) { + break; + } + } + } + + /// @dev Sample buy quotes from Uniswap. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token sell amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromUniswap( + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); + IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); + for (uint256 i = 0; i < numSamples; i++) { + bool didSucceed = true; + if (makerToken == _getWethAddress()) { + (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(takerTokenExchange), + takerTokenExchange.getTokenToEthOutputPrice.selector, + makerTokenAmounts[i] + ); + } else if (takerToken == _getWethAddress()) { + (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(makerTokenExchange), + makerTokenExchange.getEthToTokenOutputPrice.selector, + makerTokenAmounts[i] + ); + } else { + uint256 ethSold; + (ethSold, didSucceed) = _callUniswapExchangePriceFunction( + address(makerTokenExchange), + makerTokenExchange.getEthToTokenOutputPrice.selector, + makerTokenAmounts[i] + ); + if (ethSold != 0) { + (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( + address(takerTokenExchange), + takerTokenExchange.getTokenToEthOutputPrice.selector, + ethSold + ); + } else { + takerTokenAmounts[i] = 0; + } + } + if (!didSucceed) { + break; + } + } + } + + /// @dev Gracefully calls a Uniswap pricing function. + /// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange. + /// @param functionSelector Selector of the target function. + /// @param inputAmount Quantity parameter particular to the pricing function. + /// @return outputAmount The returned amount from the function call. Will be + /// zero if the call fails or if `uniswapExchangeAddress` is zero. + function _callUniswapExchangePriceFunction( + address uniswapExchangeAddress, + bytes4 functionSelector, + uint256 inputAmount + ) + private + view + returns (uint256 outputAmount, bool didSucceed) + { + if (uniswapExchangeAddress == address(0)) { + return (outputAmount, didSucceed); + } + bytes memory resultData; + (didSucceed, resultData) = + uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)( + abi.encodeWithSelector( + functionSelector, + inputAmount + )); + if (didSucceed) { + outputAmount = abi.decode(resultData, (uint256)); + } + } + + /// @dev Retrive an existing Uniswap exchange contract. + /// Throws if the exchange does not exist. + /// @param tokenAddress Address of the token contract. + /// @return exchange `IUniswapExchangeQuotes` for the token. + function _getUniswapExchange(address tokenAddress) + private + view + returns (IUniswapExchangeQuotes exchange) + { + exchange = IUniswapExchangeQuotes( + address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress()) + .getExchange(tokenAddress)) + ); + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/UniswapV2Sampler.sol b/contracts/erc20-bridge-sampler/contracts/src/UniswapV2Sampler.sol new file mode 100644 index 0000000000..09c0963cc3 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/UniswapV2Sampler.sol @@ -0,0 +1,99 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "./IUniswapV2Router01.sol"; + + +contract UniswapV2Sampler is + DeploymentConstants +{ + /// @dev Gas limit for UniswapV2 calls. + uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k + + /// @dev Sample sell quotes from UniswapV2. + /// @param path Token route. Should be takerToken -> makerToken + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromUniswapV2( + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + _getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)( + abi.encodeWithSelector( + IUniswapV2Router01(0).getAmountsOut.selector, + takerTokenAmounts[i], + path + )); + uint256 buyAmount = 0; + if (didSucceed) { + // solhint-disable-next-line indent + buyAmount = abi.decode(resultData, (uint256[]))[path.length - 1]; + } else { + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample buy quotes from UniswapV2. + /// @param path Token route. Should be takerToken -> makerToken. + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromUniswapV2( + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + _getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)( + abi.encodeWithSelector( + IUniswapV2Router01(0).getAmountsIn.selector, + makerTokenAmounts[i], + path + )); + uint256 sellAmount = 0; + if (didSucceed) { + // solhint-disable-next-line indent + sellAmount = abi.decode(resultData, (uint256[]))[0]; + } else { + break; + } + takerTokenAmounts[i] = sellAmount; + } + } +} diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json index d537beafd7..06c7d36e04 100644 --- a/contracts/erc20-bridge-sampler/package.json +++ b/contracts/erc20-bridge-sampler/package.json @@ -36,9 +36,9 @@ "compile:truffle": "truffle compile" }, "config": { - "publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider", + "publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|TestERC20BridgeSampler).json" + "abis": "./test/generated-artifacts/@(ApproximateBuys|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|ICurve|IDevUtils|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|UniswapSampler|UniswapV2Sampler).json" }, "repository": { "type": "git", diff --git a/contracts/erc20-bridge-sampler/src/artifacts.ts b/contracts/erc20-bridge-sampler/src/artifacts.ts index 837f883201..249ae04744 100644 --- a/contracts/erc20-bridge-sampler/src/artifacts.ts +++ b/contracts/erc20-bridge-sampler/src/artifacts.ts @@ -8,12 +8,10 @@ import { ContractArtifact } from 'ethereum-types'; import * as DummyLiquidityProvider from '../generated-artifacts/DummyLiquidityProvider.json'; import * as DummyLiquidityProviderRegistry from '../generated-artifacts/DummyLiquidityProviderRegistry.json'; import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json'; -import * as IERC20BridgeSampler from '../generated-artifacts/IERC20BridgeSampler.json'; import * as ILiquidityProvider from '../generated-artifacts/ILiquidityProvider.json'; import * as ILiquidityProviderRegistry from '../generated-artifacts/ILiquidityProviderRegistry.json'; export const artifacts = { ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, - IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, ILiquidityProvider: ILiquidityProvider as ContractArtifact, ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact, DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact, diff --git a/contracts/erc20-bridge-sampler/src/wrappers.ts b/contracts/erc20-bridge-sampler/src/wrappers.ts index bb1fd88c01..9e18a8fe91 100644 --- a/contracts/erc20-bridge-sampler/src/wrappers.ts +++ b/contracts/erc20-bridge-sampler/src/wrappers.ts @@ -6,6 +6,5 @@ export * from '../generated-wrappers/dummy_liquidity_provider'; export * from '../generated-wrappers/dummy_liquidity_provider_registry'; export * from '../generated-wrappers/erc20_bridge_sampler'; -export * from '../generated-wrappers/i_erc20_bridge_sampler'; export * from '../generated-wrappers/i_liquidity_provider'; export * from '../generated-wrappers/i_liquidity_provider_registry'; diff --git a/contracts/erc20-bridge-sampler/test/artifacts.ts b/contracts/erc20-bridge-sampler/test/artifacts.ts index 729ec25186..378361abeb 100644 --- a/contracts/erc20-bridge-sampler/test/artifacts.ts +++ b/contracts/erc20-bridge-sampler/test/artifacts.ts @@ -5,12 +5,14 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; +import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json'; import * as DummyLiquidityProviderRegistry from '../test/generated-artifacts/DummyLiquidityProviderRegistry.json'; import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; +import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json'; import * as ICurve from '../test/generated-artifacts/ICurve.json'; import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json'; -import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; import * as IKyberHintHandler from '../test/generated-artifacts/IKyberHintHandler.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; @@ -21,14 +23,23 @@ import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquid import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json'; import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json'; +import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json'; +import * as LiquidityProviderSampler from '../test/generated-artifacts/LiquidityProviderSampler.json'; +import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json'; +import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json'; +import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json'; import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; +import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json'; +import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json'; export const artifacts = { + ApproximateBuys: ApproximateBuys as ContractArtifact, + CurveSampler: CurveSampler as ContractArtifact, DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact, DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, + Eth2DaiSampler: Eth2DaiSampler as ContractArtifact, ICurve: ICurve as ContractArtifact, IDevUtils: IDevUtils as ContractArtifact, - IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact, IKyberHintHandler: IKyberHintHandler as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, @@ -39,5 +50,12 @@ export const artifacts = { IMultiBridge: IMultiBridge as ContractArtifact, IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact, + KyberSampler: KyberSampler as ContractArtifact, + LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact, + MultiBridgeSampler: MultiBridgeSampler as ContractArtifact, + NativeOrderSampler: NativeOrderSampler as ContractArtifact, + SamplerUtils: SamplerUtils as ContractArtifact, + UniswapSampler: UniswapSampler as ContractArtifact, + UniswapV2Sampler: UniswapV2Sampler as ContractArtifact, TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact, }; diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index a20b66cbd9..e4783468ac 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -34,10 +34,6 @@ blockchainTests('erc20-bridge-sampler', env => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); let devUtilsAddress: string; - const FAKE_BUY_OPTS = { - targetSlippageBps: new BigNumber(5), - maxIterations: new BigNumber(5), - }; before(async () => { testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( @@ -443,14 +439,12 @@ blockchainTests('erc20-bridge-sampler', env => { }); it('throws if tokens are the same', async () => { - const tx = testContract.sampleBuysFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, [], FAKE_BUY_OPTS).callAsync(); + const tx = testContract.sampleBuysFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { - const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, [], FAKE_BUY_OPTS) - .callAsync(); + const quotes = await testContract.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); const expectQuotesWithinRange = ( @@ -485,7 +479,7 @@ blockchainTests('erc20-bridge-sampler', env => { const [ethToMakerQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, MAKER_TOKEN, ['Kyber'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], ethToMakerQuotes); const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -495,7 +489,7 @@ blockchainTests('erc20-bridge-sampler', env => { const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -504,7 +498,7 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -514,7 +508,7 @@ blockchainTests('erc20-bridge-sampler', env => { const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -523,7 +517,7 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); const quotes = await testContract - .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -533,7 +527,7 @@ blockchainTests('erc20-bridge-sampler', env => { const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); const quotes = await testContract - .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -924,13 +918,7 @@ blockchainTests('erc20-bridge-sampler', env => { it('should be able to query buys from the liquidity provider', async () => { const result = await testContract - .sampleBuysFromLiquidityProviderRegistry( - registryContract.address, - yAsset, - xAsset, - sampleAmounts, - FAKE_BUY_OPTS, - ) + .sampleBuysFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts) .callAsync(); result.forEach((value, idx) => { expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1)); @@ -944,7 +932,6 @@ blockchainTests('erc20-bridge-sampler', env => { yAsset, randomAddress(), sampleAmounts, - FAKE_BUY_OPTS, ) .callAsync(); result.forEach(value => { @@ -954,7 +941,7 @@ blockchainTests('erc20-bridge-sampler', env => { it('should just return zeros if the registry does not exist', async () => { const result = await testContract - .sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts, FAKE_BUY_OPTS) + .sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts) .callAsync(); result.forEach(value => { expect(value).is.bignumber.eql(constants.ZERO_AMOUNT); diff --git a/contracts/erc20-bridge-sampler/test/wrappers.ts b/contracts/erc20-bridge-sampler/test/wrappers.ts index ec7b3d1b5b..33964f8fcb 100644 --- a/contracts/erc20-bridge-sampler/test/wrappers.ts +++ b/contracts/erc20-bridge-sampler/test/wrappers.ts @@ -3,12 +3,14 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../test/generated-wrappers/approximate_buys'; +export * from '../test/generated-wrappers/curve_sampler'; export * from '../test/generated-wrappers/dummy_liquidity_provider'; export * from '../test/generated-wrappers/dummy_liquidity_provider_registry'; export * from '../test/generated-wrappers/erc20_bridge_sampler'; +export * from '../test/generated-wrappers/eth2_dai_sampler'; export * from '../test/generated-wrappers/i_curve'; export * from '../test/generated-wrappers/i_dev_utils'; -export * from '../test/generated-wrappers/i_erc20_bridge_sampler'; export * from '../test/generated-wrappers/i_eth2_dai'; export * from '../test/generated-wrappers/i_kyber_hint_handler'; export * from '../test/generated-wrappers/i_kyber_network'; @@ -19,4 +21,11 @@ export * from '../test/generated-wrappers/i_liquidity_provider_registry'; export * from '../test/generated-wrappers/i_multi_bridge'; export * from '../test/generated-wrappers/i_uniswap_exchange_quotes'; export * from '../test/generated-wrappers/i_uniswap_v2_router01'; +export * from '../test/generated-wrappers/kyber_sampler'; +export * from '../test/generated-wrappers/liquidity_provider_sampler'; +export * from '../test/generated-wrappers/multi_bridge_sampler'; +export * from '../test/generated-wrappers/native_order_sampler'; +export * from '../test/generated-wrappers/sampler_utils'; export * from '../test/generated-wrappers/test_erc20_bridge_sampler'; +export * from '../test/generated-wrappers/uniswap_sampler'; +export * from '../test/generated-wrappers/uniswap_v2_sampler'; diff --git a/contracts/erc20-bridge-sampler/tsconfig.json b/contracts/erc20-bridge-sampler/tsconfig.json index 9aec3552fc..9ad2490b42 100644 --- a/contracts/erc20-bridge-sampler/tsconfig.json +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -6,15 +6,16 @@ "generated-artifacts/DummyLiquidityProvider.json", "generated-artifacts/DummyLiquidityProviderRegistry.json", "generated-artifacts/ERC20BridgeSampler.json", - "generated-artifacts/IERC20BridgeSampler.json", "generated-artifacts/ILiquidityProvider.json", "generated-artifacts/ILiquidityProviderRegistry.json", + "test/generated-artifacts/ApproximateBuys.json", + "test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/DummyLiquidityProvider.json", "test/generated-artifacts/DummyLiquidityProviderRegistry.json", "test/generated-artifacts/ERC20BridgeSampler.json", + "test/generated-artifacts/Eth2DaiSampler.json", "test/generated-artifacts/ICurve.json", "test/generated-artifacts/IDevUtils.json", - "test/generated-artifacts/IERC20BridgeSampler.json", "test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IKyberHintHandler.json", "test/generated-artifacts/IKyberNetwork.json", @@ -25,7 +26,14 @@ "test/generated-artifacts/IMultiBridge.json", "test/generated-artifacts/IUniswapExchangeQuotes.json", "test/generated-artifacts/IUniswapV2Router01.json", - "test/generated-artifacts/TestERC20BridgeSampler.json" + "test/generated-artifacts/KyberSampler.json", + "test/generated-artifacts/LiquidityProviderSampler.json", + "test/generated-artifacts/MultiBridgeSampler.json", + "test/generated-artifacts/NativeOrderSampler.json", + "test/generated-artifacts/SamplerUtils.json", + "test/generated-artifacts/TestERC20BridgeSampler.json", + "test/generated-artifacts/UniswapSampler.json", + "test/generated-artifacts/UniswapV2Sampler.json" ], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/contracts/integrations/CHANGELOG.json b/contracts/integrations/CHANGELOG.json index 0c45587723..50c5c3863e 100644 --- a/contracts/integrations/CHANGELOG.json +++ b/contracts/integrations/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "2.7.0", + "changes": [ + { + "note": "Update curveBridge tests", + "pr": 2633 + } + ] + }, { "version": "2.6.0", "changes": [ diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index 1f93584714..5dcadf5a4b 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -102,6 +102,7 @@ "@0x/contracts-multisig": "^4.1.7", "@0x/contracts-staking": "^2.0.14", "@0x/contracts-test-utils": "^5.3.4", + "@0x/subproviders": "^6.1.1", "@0x/types": "^3.2.0", "@0x/typescript-typings": "^5.1.1", "@0x/utils": "^5.5.1", diff --git a/contracts/integrations/test/bridge_sampler/bridge_sampler_mainnet_test.ts b/contracts/integrations/test/bridge_sampler/bridge_sampler_mainnet_test.ts index 05ea74122a..5fe0d3f7d4 100644 --- a/contracts/integrations/test/bridge_sampler/bridge_sampler_mainnet_test.ts +++ b/contracts/integrations/test/bridge_sampler/bridge_sampler_mainnet_test.ts @@ -1,16 +1,11 @@ import { artifacts, ERC20BridgeSamplerContract } from '@0x/contracts-erc20-bridge-sampler'; import { blockchainTests, describe, expect, toBaseUnitAmount, Web3ProviderEngine } from '@0x/contracts-test-utils'; -import { RPCSubprovider } from '@0x/dev-utils/node_modules/@0x/subproviders'; +import { RPCSubprovider } from '@0x/subproviders'; import { BigNumber, providerUtils } from '@0x/utils'; export const VB = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b'; -blockchainTests.configure({ - fork: { - unlockedAccounts: [VB], - }, -}); -blockchainTests.fork.resets('Mainnet Sampler Tests', env => { +blockchainTests.skip('Mainnet Sampler Tests', env => { let testContract: ERC20BridgeSamplerContract; const fakeSamplerAddress = '0x1111111111111111111111111111111111111111'; const overrides = { @@ -21,7 +16,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { before(async () => { const provider = new Web3ProviderEngine(); // tslint:disable-next-line:no-non-null-assertion - provider.addProvider(new RPCSubprovider(process.env.FORK_RPC_URL!)); + provider.addProvider(new RPCSubprovider(process.env.RPC_URL!)); providerUtils.startProviderEngine(provider); testContract = new ERC20BridgeSamplerContract(fakeSamplerAddress, provider, { ...env.txDefaults, @@ -29,14 +24,19 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { }); }); describe('Curve', () => { - const CURVE_ADDRESS = '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56'; + const CURVE_ADDRESS = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51'; const DAI_TOKEN_INDEX = new BigNumber(0); const USDC_TOKEN_INDEX = new BigNumber(1); + const CURVE_INFO = { + poolAddress: CURVE_ADDRESS, + sellQuoteFunctionSelector: '0x07211ef7', + buyQuoteFunctionSelector: '0x0e71d1b9', + }; describe('sampleSellsFromCurve()', () => { it('samples sells from Curve DAI->USDC', async () => { const samples = await testContract - .sampleSellsFromCurve(CURVE_ADDRESS, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)]) + .sampleSellsFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -44,7 +44,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { it('samples sells from Curve USDC->DAI', async () => { const samples = await testContract - .sampleSellsFromCurve(CURVE_ADDRESS, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) + .sampleSellsFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -56,7 +56,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { // From DAI to USDC // I want to buy 1 USDC const samples = await testContract - .sampleBuysFromCurve(CURVE_ADDRESS, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) + .sampleBuysFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -66,7 +66,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { // From USDC to DAI // I want to buy 1 DAI const samples = await testContract - .sampleBuysFromCurve(CURVE_ADDRESS, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)]) + .sampleBuysFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -74,10 +74,6 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { }); }); describe('Kyber', () => { - const FAKE_BUY_OPTS = { - targetSlippageBps: new BigNumber(5), - maxIterations: new BigNumber(5), - }; const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'; const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; @@ -110,7 +106,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { // From ETH to DAI // I want to buy 1 DAI const samples = await testContract - .sampleBuysFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)], FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -120,7 +116,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => { // From USDC to DAI // I want to buy 1 WETH const samples = await testContract - .sampleBuysFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)], FAKE_BUY_OPTS) + .sampleBuysFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); diff --git a/contracts/integrations/test/bridges/curve_bridge_mainnet_test.ts b/contracts/integrations/test/bridges/curve_bridge_mainnet_test.ts index 8954b7b1f1..da65662648 100644 --- a/contracts/integrations/test/bridges/curve_bridge_mainnet_test.ts +++ b/contracts/integrations/test/bridges/curve_bridge_mainnet_test.ts @@ -4,107 +4,128 @@ import { ERC20TokenContract } from '@0x/contracts-erc20'; import { blockchainTests, constants, describe, toBaseUnitAmount } from '@0x/contracts-test-utils'; import { AbiEncoder } from '@0x/utils'; -blockchainTests.fork.resets('Mainnet curve bridge tests', env => { +const USDC_WALLET = '0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3'; +const DAI_WALLET = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b'; +const WBTC_WALLET = '0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9'; +blockchainTests.configure({ + fork: { + unlockedAccounts: [USDC_WALLET, DAI_WALLET, WBTC_WALLET], + }, +}); + +blockchainTests.fork('Mainnet curve bridge tests', env => { let testContract: CurveBridgeContract; - const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308'; - const usdcWallet = '0xF977814e90dA44bFA03b6295A0616a897441aceC'; - const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - const daiWallet = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b'; - const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; - const curveAddressUsdcDai = '0x2e60CF74d81ac34eB21eEff58Db4D385920ef419'; - const curveAddressUsdcDaiUsdt = '0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C'; - const daiTokenIdx = 0; - const usdcTokenIdx = 1; + const RECEIVER = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308'; const bridgeDataEncoder = AbiEncoder.create([ { name: 'curveAddress', type: 'address' }, + { name: 'exchangeFunctionSelector', type: 'bytes4' }, + { name: 'fromTokenAddress', type: 'address' }, { name: 'fromTokenIdx', type: 'int128' }, { name: 'toTokenIdx', type: 'int128' }, - { name: 'version', type: 'int128' }, ]); before(async () => { testContract = await CurveBridgeContract.deployFrom0xArtifactAsync( assetProxyArtifacts.CurveBridge, env.provider, - { ...env.txDefaults, from: daiWallet }, + { ...env.txDefaults }, {}, ); }); describe('bridgeTransferFrom()', () => { - describe('Version 0', () => { - const version = 0; + describe('exchange_underlying()', () => { + const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + const DAI_TOKEN_IDX = 0; + const USDC_TOKEN_IDX = 1; + const EXCHANGE_UNDERLYING_SELECTOR = '0xa6417ed6'; + const CURVE_ADDRESS = '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56'; it('succeeds exchanges DAI for USDC', async () => { - const bridgeData = bridgeDataEncoder.encode([curveAddressUsdcDai, daiTokenIdx, usdcTokenIdx, version]); + const bridgeData = bridgeDataEncoder.encode([ + CURVE_ADDRESS, + EXCHANGE_UNDERLYING_SELECTOR, + DAI_ADDRESS, + DAI_TOKEN_IDX, + USDC_TOKEN_IDX, + ]); // Fund the Bridge - const dai = new ERC20TokenContract(daiAddress, env.provider, { ...env.txDefaults, from: daiWallet }); + const dai = new ERC20TokenContract(DAI_ADDRESS, env.provider, { ...env.txDefaults, from: DAI_WALLET }); await dai .transfer(testContract.address, toBaseUnitAmount(1)) - .awaitTransactionSuccessAsync({ from: daiWallet }, { shouldValidate: false }); + .awaitTransactionSuccessAsync({}, { shouldValidate: false }); // Exchange via Curve await testContract .bridgeTransferFrom( - usdcAddress, + USDC_ADDRESS, constants.NULL_ADDRESS, - receiver, + RECEIVER, constants.ZERO_AMOUNT, bridgeData, ) - .awaitTransactionSuccessAsync({ from: daiWallet, gasPrice: 1 }, { shouldValidate: false }); + .awaitTransactionSuccessAsync({}, { shouldValidate: false }); }); it('succeeds exchanges USDC for DAI', async () => { - const bridgeData = bridgeDataEncoder.encode([curveAddressUsdcDai, usdcTokenIdx, daiTokenIdx, version]); + const bridgeData = bridgeDataEncoder.encode([ + CURVE_ADDRESS, + EXCHANGE_UNDERLYING_SELECTOR, + USDC_ADDRESS, + USDC_TOKEN_IDX, + DAI_TOKEN_IDX, + ]); // Fund the Bridge - const usdc = new ERC20TokenContract(usdcAddress, env.provider, { ...env.txDefaults, from: usdcWallet }); + const usdc = new ERC20TokenContract(USDC_ADDRESS, env.provider, { + ...env.txDefaults, + from: USDC_WALLET, + }); await usdc .transfer(testContract.address, toBaseUnitAmount(1, 6)) - .awaitTransactionSuccessAsync({ from: usdcWallet }, { shouldValidate: false }); + .awaitTransactionSuccessAsync({}, { shouldValidate: false }); // Exchange via Curve await testContract - .bridgeTransferFrom(daiAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) - .awaitTransactionSuccessAsync({ from: usdcWallet, gasPrice: 1 }, { shouldValidate: false }); + .bridgeTransferFrom( + DAI_ADDRESS, + constants.NULL_ADDRESS, + RECEIVER, + constants.ZERO_AMOUNT, + bridgeData, + ) + .awaitTransactionSuccessAsync({}, { shouldValidate: false }); }); }); - describe('Version 1', () => { - const version = 1; - it('succeeds exchanges DAI for USDC', async () => { + + describe('exchange()', () => { + const WBTC_ADDRESS = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'; + const RENBTC_ADDRESS = '0xeb4c2781e4eba804ce9a9803c67d0893436bb27d'; + const RENBTC_TOKEN_IDX = 0; + const WBTC_TOKEN_IDX = 1; + const EXCHANGE_SELECTOR = '0x3df02124'; + const CURVE_ADDRESS = '0x7fc77b5c7614e1533320ea6ddc2eb61fa00a9714'; + it('succeeds exchanges WBTC for renBTC', async () => { const bridgeData = bridgeDataEncoder.encode([ - curveAddressUsdcDaiUsdt, - daiTokenIdx, - usdcTokenIdx, - version, + CURVE_ADDRESS, + EXCHANGE_SELECTOR, + WBTC_ADDRESS, + WBTC_TOKEN_IDX, + RENBTC_TOKEN_IDX, ]); // Fund the Bridge - const dai = new ERC20TokenContract(daiAddress, env.provider, { ...env.txDefaults, from: daiWallet }); - await dai - .transfer(testContract.address, toBaseUnitAmount(1)) - .awaitTransactionSuccessAsync({ from: daiWallet }, { shouldValidate: false }); + const wbtc = new ERC20TokenContract(WBTC_ADDRESS, env.provider, { + ...env.txDefaults, + from: WBTC_WALLET, + }); + await wbtc + .transfer(testContract.address, toBaseUnitAmount(1, 8)) + .awaitTransactionSuccessAsync({}, { shouldValidate: false }); // Exchange via Curve await testContract .bridgeTransferFrom( - usdcAddress, + RENBTC_ADDRESS, constants.NULL_ADDRESS, - receiver, + RECEIVER, constants.ZERO_AMOUNT, bridgeData, ) - .awaitTransactionSuccessAsync({ from: daiWallet, gasPrice: 1 }, { shouldValidate: false }); - }); - it('succeeds exchanges USDC for DAI', async () => { - const bridgeData = bridgeDataEncoder.encode([ - curveAddressUsdcDaiUsdt, - usdcTokenIdx, - daiTokenIdx, - version, - ]); - // Fund the Bridge - const usdc = new ERC20TokenContract(usdcAddress, env.provider, { ...env.txDefaults, from: usdcWallet }); - await usdc - .transfer(testContract.address, toBaseUnitAmount(1, 6)) - .awaitTransactionSuccessAsync({ from: usdcWallet }, { shouldValidate: false }); - // Exchange via Curve - await testContract - .bridgeTransferFrom(daiAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) - .awaitTransactionSuccessAsync({ from: usdcWallet, gasPrice: 1 }, { shouldValidate: false }); + .awaitTransactionSuccessAsync({ gas: 6e6 }, { shouldValidate: false }); }); }); }); diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 9cb0a8dcde..56659a8926 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -18,6 +18,10 @@ { "note": "Potentially heavy CPU functions inside the optimizer now yield to the event loop. As such they are now async.", "pr": 2637 + }, + { + "note": "Support more varied curves", + "pr": 2633 } ] }, @@ -551,4 +555,4 @@ } ] } -] +] \ No newline at end of file diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index 8886c5bcaa..c7dc690e32 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -47,7 +47,6 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = { rfqt: { takerApiKeyWhitelist: [], makerAssetOfferings: {}, - skipBuyRequests: false, }, }; diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index c5634d0d9c..f7ab148f4e 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -74,6 +74,7 @@ export { BalancerFillData, CollapsedFill, CurveFillData, + CurveInfo, ERC20BridgeSource, FeeSchedule, FillData, @@ -82,6 +83,7 @@ export { NativeFillData, OptimizedMarketOrder, UniswapV2FillData, + CurveFunctionSelectors, } from './utils/market_operation_utils/types'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; export { QuoteRequestor } from './utils/quote_requestor'; diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 5bcb609f65..2bccf271d4 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -181,8 +181,8 @@ export class SwapQuoter { const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); const defaultCodeOverrides = samplerBytecode ? { - [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, - } + [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, + } : {}; const samplerOverrides = _.assign( { block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides }, @@ -551,10 +551,23 @@ export class SwapQuoter { } else { gasPrice = await this.getGasPriceEstimationOrThrowAsync(); } + + // If RFQT is enabled and `nativeExclusivelyRFQT` is set, then `ERC20BridgeSource.Native` should + // never be excluded. + if ( + opts.rfqt && + opts.rfqt.nativeExclusivelyRFQT === true && + opts.excludedSources.includes(ERC20BridgeSource.Native) + ) { + throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQT" is set'); + } + // get batches of orders from different sources, awaiting sources in parallel const orderBatchPromises: Array> = []; - if (!opts.excludedSources.includes(ERC20BridgeSource.Native)) { + const skipOpenOrderbook = opts.excludedSources.includes(ERC20BridgeSource.Native) || + (opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true); + if (!skipOpenOrderbook) { orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book } @@ -567,12 +580,11 @@ export class SwapQuoter { ); if ( - opts.rfqt && - opts.rfqt.intentOnFilling && + opts.rfqt && // This is an RFQT-enabled API request + opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote opts.rfqt.apiKey && - this._isApiKeyWhitelisted(opts.rfqt.apiKey) && - !opts.excludedSources.includes(ERC20BridgeSource.Native) && - !(marketOperation === MarketOperation.Buy && this._shouldSkipRfqtBuyRequests()) + 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) { throw new Error('RFQ-T requests must specify a taker address'); @@ -633,19 +645,5 @@ export class SwapQuoter { const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; return whitelistedApiKeys.includes(apiKey); } - private _shouldSkipRfqtBuyRequests(): boolean { - const rfqtOptions = this._rfqtOptions; - - if (rfqtOptions && rfqtOptions.skipBuyRequests !== undefined) { - return rfqtOptions.skipBuyRequests; - } - - const defaultRfqtOptions = constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt; - if (defaultRfqtOptions && defaultRfqtOptions.skipBuyRequests !== undefined) { - return defaultRfqtOptions.skipBuyRequests; - } - - return false; - } } // tslint:disable-next-line: max-file-line-count diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 7041b1ecfb..e645ee87eb 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -201,12 +201,18 @@ export interface SwapQuoteOrdersBreakdown { [source: string]: BigNumber; } +/** + * nativeExclusivelyRFQT: if set to `true`, Swap quote will exclude Open Orderbook liquidity. + * If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources` + * array in `SwapQuoteRequestOpts`, an Error will be raised. + */ export interface RfqtRequestOpts { takerAddress: string; apiKey: string; intentOnFilling: boolean; isIndicative?: boolean; makerEndpointMaxResponseTimeMs?: number; + nativeExclusivelyRFQT?: boolean; } /** @@ -220,7 +226,7 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts { /** * Opts required to generate a SwapQuote with SwapQuoteCalculator */ -export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {} +export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts { } /** * A mapping from RFQ-T quote provider URLs to the trading pairs they support. @@ -235,7 +241,6 @@ export { LogFunction } from './utils/quote_requestor'; export interface SwapQuoterRfqtOpts { takerApiKeyWhitelist: string[]; makerAssetOfferings: RfqtMakerAssetOfferings; - skipBuyRequests?: boolean; warningLogger?: LogFunction; infoLogger?: LogFunction; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index 4fec2a348c..f454560b4c 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -1,6 +1,6 @@ import { BigNumber } from '@0x/utils'; -import { ERC20BridgeSource, FakeBuyOpts, GetMarketOrdersOpts } from './types'; +import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types'; // tslint:disable: custom-no-magic-numbers @@ -42,11 +42,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { shouldBatchBridgeOrders: true, }; -export const DEFAULT_FAKE_BUY_OPTS: FakeBuyOpts = { - targetSlippageBps: new BigNumber(5), - maxIterations: new BigNumber(5), -}; - /** * Sources to poll for ETH fee price estimates. */ @@ -60,34 +55,80 @@ export const FEE_QUOTE_SOURCES = [ /** * Mainnet Curve configuration */ -export const MAINNET_CURVE_CONTRACTS: { [curveAddress: string]: string[] } = { - '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - ], - '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - ], - '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD - ], - '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x4fabb145d64652a948d72533023f6e7a623c7c53', // BUSD - ], - '0xa5407eae9ba41422680e2e00537571bcc53efbfd': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // SUSD - ], +export const MAINNET_CURVE_INFOS: { [name: string]: CurveInfo } = { + DaiUsdc: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56', + tokens: ['0x6b175474e89094c44da98b954eedeac495271d0f', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'], + }, + // DaiUsdcUsdt: { + // exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + // sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + // buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + // poolAddress: '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c', + // tokens: [ + // '0x6b175474e89094c44da98b954eedeac495271d0f', + // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + // '0xdac17f958d2ee523a2206206994597c13d831ec7', + // ], + // }, + DaiUsdcUsdtTusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x0000000000085d4780b73119b644ae5ecd22b376', + ], + }, + // Looks like it's dying. + DaiUsdcUsdtBusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x4fabb145d64652a948d72533023f6e7a623c7c53', + ], + }, + DaiUsdcUsdtSusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0xa5407eae9ba41422680e2e00537571bcc53efbfd', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x57ab1ec28d129707052df4df418d58a2d46d5f51', + ], + }, + RenbtcWbtc: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy, + buyQuoteFunctionSelector: CurveFunctionSelectors.None, + poolAddress: '0x93054188d876f558f4a66b2ef1d97d16edf0895b', + tokens: ['0xeb4c2781e4eba804ce9a9803c67d0893436bb27d', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'], + }, + RenbtcWbtcSbtc: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy, + buyQuoteFunctionSelector: CurveFunctionSelectors.None, + poolAddress: '0x7fc77b5c7614e1533320ea6ddc2eb61fa00a9714', + tokens: [ + '0xeb4c2781e4eba804ce9a9803c67d0893436bb27d', + '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + '0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6', + ], + }, }; export const ERC20_PROXY_ID = '0xf47261b0'; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts index 2af692acfe..182029e9fb 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts @@ -1,8 +1,7 @@ -import { MAINNET_CURVE_CONTRACTS } from './constants'; +import { MAINNET_CURVE_INFOS } from './constants'; +import { CurveInfo } from './types'; // tslint:disable completed-docs -export function getCurveAddressesForPair(takerToken: string, makerToken: string): string[] { - return Object.keys(MAINNET_CURVE_CONTRACTS).filter(a => - [makerToken, takerToken].every(t => MAINNET_CURVE_CONTRACTS[a].includes(t)), - ); +export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] { + return Object.values(MAINNET_CURVE_INFOS).filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t))); } 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 836f0f0862..13ce139e20 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -30,14 +30,23 @@ import { OrderDomain, } from './types'; -async function getRfqtIndicativeQuotesAsync( +/** + * Returns a indicative quotes or an empty array if RFQT is not enabled or requested + * @param makerAssetData the maker asset data + * @param takerAssetData the taker asset data + * @param marketOperation Buy or Sell + * @param assetFillAmount the amount to fill, in base units + * @param opts market request options + */ +export async function getRfqtIndicativeQuotesAsync( makerAssetData: string, takerAssetData: string, marketOperation: MarketOperation, assetFillAmount: BigNumber, opts: Partial, ): Promise { - if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) { + const hasExcludedNativeLiquidity = opts.excludedSources && opts.excludedSources.includes(ERC20BridgeSource.Native); + if (!hasExcludedNativeLiquidity && opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) { return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync( makerAssetData, takerAssetData, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 66ecf6387d..abeba32e73 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -219,10 +219,11 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): makerToken, bridgeAddress, createCurveBridgeData( - curveFillData.poolAddress, + curveFillData.curve.poolAddress, + curveFillData.curve.exchangeFunctionSelector, + takerToken, curveFillData.fromTokenIdx, curveFillData.toTokenIdx, - 1, // "version" ), ); break; @@ -341,17 +342,25 @@ function createBalancerBridgeData(takerToken: string, poolAddress: string): stri function createCurveBridgeData( curveAddress: string, + exchangeFunctionSelector: string, + takerToken: string, fromTokenIdx: number, toTokenIdx: number, - version: number, ): string { const curveBridgeDataEncoder = AbiEncoder.create([ { name: 'curveAddress', type: 'address' }, + { name: 'exchangeFunctionSelector', type: 'bytes4' }, + { name: 'fromTokenAddress', type: 'address' }, { name: 'fromTokenIdx', type: 'int128' }, { name: 'toTokenIdx', type: 'int128' }, - { name: 'version', type: 'int128' }, ]); - return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx, version]); + return curveBridgeDataEncoder.encode([ + curveAddress, + exchangeFunctionSelector, + takerToken, + fromTokenIdx, + toTokenIdx, + ]); } function createUniswapV2BridgeData(tokenAddressPath: string[]): string { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index ec04b0c2d9..4090914a83 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -3,15 +3,14 @@ import * as _ from 'lodash'; import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..'; import { BalancerPool, BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils'; -import { DEFAULT_FAKE_BUY_OPTS, MAINNET_CURVE_CONTRACTS } from './constants'; -import { getCurveAddressesForPair } from './curve_utils'; +import { getCurveInfosForPair } from './curve_utils'; import { getMultiBridgeIntermediateToken } from './multibridge_utils'; import { BalancerFillData, BatchedOperation, CurveFillData, + CurveInfo, DexSample, - FakeBuyOpts, SourceQuoteOperation, UniswapV2FillData, } from './types'; @@ -58,17 +57,12 @@ export const samplerOperations = { }, }; }, - getKyberBuyQuotes( - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, - ): SourceQuoteOperation { + getKyberBuyQuotes(makerToken: string, takerToken: string, makerFillAmounts: BigNumber[]): SourceQuoteOperation { return { source: ERC20BridgeSource.Kyber, encodeCall: contract => { return contract - .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts, fakeBuyOpts) + .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts) .getABIEncodedTransactionData(); }, handleCallResultsAsync: async (contract, callResults) => { @@ -162,19 +156,12 @@ export const samplerOperations = { makerToken: string, takerToken: string, makerFillAmounts: BigNumber[], - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, ): SourceQuoteOperation { return { source: ERC20BridgeSource.LiquidityProvider, encodeCall: contract => { return contract - .sampleBuysFromLiquidityProviderRegistry( - registryAddress, - takerToken, - makerToken, - makerFillAmounts, - fakeBuyOpts, - ) + .sampleBuysFromLiquidityProviderRegistry(registryAddress, takerToken, makerToken, makerFillAmounts) .getABIEncodedTransactionData(); }, handleCallResultsAsync: async (contract, callResults) => { @@ -237,7 +224,7 @@ export const samplerOperations = { }; }, getCurveSellQuotes( - curveAddress: string, + curve: CurveInfo, fromTokenIdx: number, toTokenIdx: number, takerFillAmounts: BigNumber[], @@ -245,14 +232,18 @@ export const samplerOperations = { return { source: ERC20BridgeSource.Curve, fillData: { - poolAddress: curveAddress, + curve, fromTokenIdx, toTokenIdx, }, encodeCall: contract => { return contract .sampleSellsFromCurve( - curveAddress, + { + poolAddress: curve.poolAddress, + sellQuoteFunctionSelector: curve.sellQuoteFunctionSelector, + buyQuoteFunctionSelector: curve.buyQuoteFunctionSelector, + }, new BigNumber(fromTokenIdx), new BigNumber(toTokenIdx), takerFillAmounts, @@ -265,7 +256,7 @@ export const samplerOperations = { }; }, getCurveBuyQuotes( - curveAddress: string, + curve: CurveInfo, fromTokenIdx: number, toTokenIdx: number, makerFillAmounts: BigNumber[], @@ -273,14 +264,18 @@ export const samplerOperations = { return { source: ERC20BridgeSource.Curve, fillData: { - poolAddress: curveAddress, + curve, fromTokenIdx, toTokenIdx, }, encodeCall: contract => { return contract .sampleBuysFromCurve( - curveAddress, + { + poolAddress: curve.poolAddress, + sellQuoteFunctionSelector: curve.sellQuoteFunctionSelector, + buyQuoteFunctionSelector: curve.buyQuoteFunctionSelector, + }, new BigNumber(fromTokenIdx), new BigNumber(toTokenIdx), makerFillAmounts, @@ -416,11 +411,11 @@ export const samplerOperations = { case ERC20BridgeSource.Kyber: return samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); case ERC20BridgeSource.Curve: - return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => + return getCurveInfosForPair(takerToken, makerToken).map(curve => samplerOperations.getCurveSellQuotes( - curveAddress, - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), + curve, + curve.tokens.indexOf(takerToken), + curve.tokens.indexOf(makerToken), takerFillAmounts, ), ); @@ -502,7 +497,6 @@ export const samplerOperations = { wethAddress: string, balancerPoolsCache?: BalancerPoolsCache, liquidityProviderRegistryAddress?: string, - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, ): Promise> => { const subOps = _.flatten( await Promise.all( @@ -527,18 +521,13 @@ export const samplerOperations = { } return ops; case ERC20BridgeSource.Kyber: - return samplerOperations.getKyberBuyQuotes( - makerToken, - takerToken, - makerFillAmounts, - fakeBuyOpts, - ); + return samplerOperations.getKyberBuyQuotes(makerToken, takerToken, makerFillAmounts); case ERC20BridgeSource.Curve: - return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => + return getCurveInfosForPair(takerToken, makerToken).map(curve => samplerOperations.getCurveBuyQuotes( - curveAddress, - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), + curve, + curve.tokens.indexOf(takerToken), + curve.tokens.indexOf(makerToken), makerFillAmounts, ), ); @@ -553,7 +542,6 @@ export const samplerOperations = { makerToken, takerToken, makerFillAmounts, - fakeBuyOpts, ); case ERC20BridgeSource.Balancer: if (balancerPoolsCache === undefined) { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index aceafc1234..ab2f5a292c 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -38,8 +38,34 @@ export enum ERC20BridgeSource { Balancer = 'Balancer', } +// tslint:disable: enum-naming +/** + * Curve contract function selectors. + */ +export enum CurveFunctionSelectors { + None = '0x00000000', + exchange = '0x3df02124', + exchange_underlying = '0xa6417ed6', + get_dy_underlying = '0x07211ef7', + get_dx_underlying = '0x0e71d1b9', + get_dy = '0x5e0d443f', + get_dx = '0x67df02ca', +} +// tslint:enable: enum-naming + +/** + * Configuration info on a Curve pool. + */ +export interface CurveInfo { + exchangeFunctionSelector: CurveFunctionSelectors; + sellQuoteFunctionSelector: CurveFunctionSelectors; + buyQuoteFunctionSelector: CurveFunctionSelectors; + poolAddress: string; + tokens: string[]; +} + // Internal `fillData` field for `Fill` objects. -export interface FillData {} +export interface FillData { } // `FillData` for native fills. export interface NativeFillData extends FillData { @@ -47,9 +73,9 @@ export interface NativeFillData extends FillData { } export interface CurveFillData extends FillData { - poolAddress: string; fromTokenIdx: number; toTokenIdx: number; + curve: CurveInfo; } export interface BalancerFillData extends FillData { @@ -137,7 +163,7 @@ export interface CollapsedFill { /** * A `CollapsedFill` wrapping a native order. */ -export interface NativeCollapsedFill extends CollapsedFill {} +export interface NativeCollapsedFill extends CollapsedFill { } /** * Optimized orders to fill. diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 4e315c866e..4392762120 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -13,14 +13,20 @@ import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; import { AssetProxyId, ERC20BridgeAssetData, SignedOrder } from '@0x/types'; import { BigNumber, fromTokenUnitAmount, hexUtils, NULL_ADDRESS } from '@0x/utils'; import * as _ from 'lodash'; +import * as TypeMoq from 'typemoq'; -import { MarketOperation, SignedOrderWithFillableAmounts } from '../src'; -import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; +import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src'; +import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/'; import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants'; import { createFillPaths } from '../src/utils/market_operation_utils/fills'; import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types'; +const MAKER_TOKEN = randomAddress(); +const TAKER_TOKEN = randomAddress(); +const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN); +const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN); + // tslint:disable: custom-no-magic-numbers promise-function-async describe('MarketOperationUtils tests', () => { const CHAIN_ID = 1; @@ -30,11 +36,6 @@ describe('MarketOperationUtils tests', () => { const UNISWAP_BRIDGE_ADDRESS = contractAddresses.uniswapBridge; const UNISWAP_V2_BRIDGE_ADDRESS = contractAddresses.uniswapV2Bridge; const CURVE_BRIDGE_ADDRESS = contractAddresses.curveBridge; - - const MAKER_TOKEN = randomAddress(); - const TAKER_TOKEN = randomAddress(); - const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN); - const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN); let originalSamplerOperations: any; before(() => { @@ -302,7 +303,17 @@ describe('MarketOperationUtils tests', () => { const DEFAULT_FILL_DATA: FillDataBySource = { [ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] }, [ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() }, - [ERC20BridgeSource.Curve]: { poolAddress: randomAddress(), fromTokenIdx: 0, toTokenIdx: 1 }, + [ERC20BridgeSource.Curve]: { + curve: { + poolAddress: randomAddress(), + tokens: [TAKER_TOKEN, MAKER_TOKEN], + exchangeFunctionSelector: hexUtils.random(4), + sellQuoteFunctionSelector: hexUtils.random(4), + buyQuoteFunctionSelector: hexUtils.random(4), + }, + fromTokenIdx: 0, + toTokenIdx: 1, + }, }; const DEFAULT_OPS = { @@ -334,6 +345,68 @@ describe('MarketOperationUtils tests', () => { }, } as any) as DexOrderSampler; + describe('getRfqtIndicativeQuotesAsync', () => { + const partialRfqt: RfqtRequestOpts = { + apiKey: 'foo', + takerAddress: NULL_ADDRESS, + isIndicative: true, + intentOnFilling: false, + }; + + it('returns an empty array if native liquidity is excluded from the salad', async () => { + const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Strict); + const result = await getRfqtIndicativeQuotesAsync( + MAKER_ASSET_DATA, + TAKER_ASSET_DATA, + MarketOperation.Sell, + new BigNumber('100e18'), + { + rfqt: { quoteRequestor: requestor.object, ...partialRfqt }, + excludedSources: [ERC20BridgeSource.Native], + }, + ); + expect(result.length).to.eql(0); + requestor.verify( + r => + r.requestRfqtIndicativeQuotesAsync( + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + ), + TypeMoq.Times.never(), + ); + }); + + it('calls RFQT if Native source is not excluded', async () => { + const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose); + requestor + .setup(r => + r.requestRfqtIndicativeQuotesAsync( + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + ), + ) + .returns(() => Promise.resolve([])) + .verifiable(TypeMoq.Times.once()); + await getRfqtIndicativeQuotesAsync( + MAKER_ASSET_DATA, + TAKER_ASSET_DATA, + MarketOperation.Sell, + new BigNumber('100e18'), + { + rfqt: { quoteRequestor: requestor.object, ...partialRfqt }, + excludedSources: [], + }, + ); + requestor.verifyAll(); + }); + }); + describe('MarketOperationUtils', () => { let marketOperationUtils: MarketOperationUtils; diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 3864732fcf..21bfc6ae26 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.12.0", + "changes": [ + { + "note": "Update `CurveBridge` address on all networks", + "pr": 2633 + } + ] + }, { "version": "4.11.0", "changes": [ diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 451361b286..ef3c29d503 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -29,7 +29,7 @@ "godsUnchainedValidator": "0x09a379ef7218bcfd8913faa8b281ebc5a2e0bc04", "broker": "0xd4690a51044db77d91d7aa8f7a3a5ad5da331af0", "chainlinkStopLimit": "0xeb27220f95f364e1d9531992c48613f231839f53", - "curveBridge": "0x6dc7950423ada9f56fb2c93a23edb787f1e29088", + "curveBridge": "0x1796cd592d19e3bcd744fbb025bb61a6d8cb2c09", "maximumGasPrice": "0xe2bfd35306495d11e3c9db0d8de390cda24563cf", "dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a", "multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1", @@ -76,7 +76,7 @@ "godsUnchainedValidator": "0xd4690a51044db77d91d7aa8f7a3a5ad5da331af0", "broker": "0x4aa817c6f383c8e8ae77301d18ce48efb16fd2be", "chainlinkStopLimit": "0x67a094cf028221ffdd93fc658f963151d05e2a74", - "curveBridge": "0x0000000000000000000000000000000000000000", + "curveBridge": "0x1796cd592d19e3bcd744fbb025bb61a6d8cb2c09", "maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", "dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", "multiBridge": "0x0000000000000000000000000000000000000000", @@ -123,7 +123,7 @@ "godsUnchainedValidator": "0x0000000000000000000000000000000000000000", "broker": "0x0000000000000000000000000000000000000000", "chainlinkStopLimit": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", - "curveBridge": "0x0000000000000000000000000000000000000000", + "curveBridge": "0x1796cd592d19e3bcd744fbb025bb61a6d8cb2c09", "maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", "dexForwarderBridge": "0x0000000000000000000000000000000000000000", "multiBridge": "0x0000000000000000000000000000000000000000", @@ -170,7 +170,7 @@ "godsUnchainedValidator": "0x0000000000000000000000000000000000000000", "broker": "0x0000000000000000000000000000000000000000", "chainlinkStopLimit": "0x0000000000000000000000000000000000000000", - "curveBridge": "0x90c62c91a9f655f4f739e6cee85c84f9ccf47323", + "curveBridge": "0x81c0ab53a7352d2e97f682a37cba44e54647eefb", "maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74", "dexForwarderBridge": "0xf220eb0b29e18bbc8ebc964e915b7547c7b4de4f", "multiBridge": "0x0000000000000000000000000000000000000000", diff --git a/packages/contract-artifacts/CHANGELOG.json b/packages/contract-artifacts/CHANGELOG.json index 19269627cf..2dc7f74a35 100644 --- a/packages/contract-artifacts/CHANGELOG.json +++ b/packages/contract-artifacts/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add `IZeroEx` artifact", "pr": 2626 + }, + { + "note": "Update `ERC20BridgeSampler` artifact", + "pr": 2633 } ] }, diff --git a/packages/contract-artifacts/artifacts/ERC20BridgeSampler.json b/packages/contract-artifacts/artifacts/ERC20BridgeSampler.json index 74746d3371..e8eac88ee2 100644 --- a/packages/contract-artifacts/artifacts/ERC20BridgeSampler.json +++ b/packages/contract-artifacts/artifacts/ERC20BridgeSampler.json @@ -98,7 +98,16 @@ { "constant": true, "inputs": [ - { "internalType": "address", "name": "curveAddress", "type": "address" }, + { + "components": [ + { "internalType": "address", "name": "poolAddress", "type": "address" }, + { "internalType": "bytes4", "name": "sellQuoteFunctionSelector", "type": "bytes4" }, + { "internalType": "bytes4", "name": "buyQuoteFunctionSelector", "type": "bytes4" } + ], + "internalType": "struct CurveSampler.CurveInfo", + "name": "curveInfo", + "type": "tuple" + }, { "internalType": "int128", "name": "fromTokenIdx", "type": "int128" }, { "internalType": "int128", "name": "toTokenIdx", "type": "int128" }, { "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" } @@ -127,16 +136,7 @@ "inputs": [ { "internalType": "address", "name": "takerToken", "type": "address" }, { "internalType": "address", "name": "makerToken", "type": "address" }, - { "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }, - { - "components": [ - { "internalType": "uint256", "name": "targetSlippageBps", "type": "uint256" }, - { "internalType": "uint256", "name": "maxIterations", "type": "uint256" } - ], - "internalType": "struct IERC20BridgeSampler.FakeBuyOptions", - "name": "opts", - "type": "tuple" - } + { "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" } ], "name": "sampleBuysFromKyberNetwork", "outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }], @@ -150,16 +150,7 @@ { "internalType": "address", "name": "registryAddress", "type": "address" }, { "internalType": "address", "name": "takerToken", "type": "address" }, { "internalType": "address", "name": "makerToken", "type": "address" }, - { "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }, - { - "components": [ - { "internalType": "uint256", "name": "targetSlippageBps", "type": "uint256" }, - { "internalType": "uint256", "name": "maxIterations", "type": "uint256" } - ], - "internalType": "struct IERC20BridgeSampler.FakeBuyOptions", - "name": "opts", - "type": "tuple" - } + { "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" } ], "name": "sampleBuysFromLiquidityProviderRegistry", "outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }], @@ -195,7 +186,16 @@ { "constant": true, "inputs": [ - { "internalType": "address", "name": "curveAddress", "type": "address" }, + { + "components": [ + { "internalType": "address", "name": "poolAddress", "type": "address" }, + { "internalType": "bytes4", "name": "sellQuoteFunctionSelector", "type": "bytes4" }, + { "internalType": "bytes4", "name": "buyQuoteFunctionSelector", "type": "bytes4" } + ], + "internalType": "struct CurveSampler.CurveInfo", + "name": "curveInfo", + "type": "tuple" + }, { "internalType": "int128", "name": "fromTokenIdx", "type": "int128" }, { "internalType": "int128", "name": "toTokenIdx", "type": "int128" }, { "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" } @@ -334,10 +334,10 @@ }, "return": "orderFillableTakerAssetAmounts How much taker asset can be filled by each order in `orders`." }, - "sampleBuysFromCurve(address,int128,int128,uint256[])": { + "sampleBuysFromCurve((address,bytes4,bytes4),int128,int128,uint256[])": { "details": "Sample buy quotes from Curve.", "params": { - "curveAddress": "Address of the Curve contract.", + "curveInfo": "Curve information specific to this token pair.", "fromTokenIdx": "Index of the taker token (what to sell).", "makerTokenAmounts": "Maker token buy amount for each sample.", "toTokenIdx": "Index of the maker token (what to buy)." @@ -353,22 +353,20 @@ }, "return": "takerTokenAmounts Taker amounts sold at each maker token amount." }, - "sampleBuysFromKyberNetwork(address,address,uint256[],(uint256,uint256))": { + "sampleBuysFromKyberNetwork(address,address,uint256[])": { "details": "Sample buy quotes from Kyber.", "params": { "makerToken": "Address of the maker token (what to buy).", "makerTokenAmounts": "Maker token buy amount for each sample.", - "opts": "`FakeBuyOptions` specifying target slippage and max iterations.", "takerToken": "Address of the taker token (what to sell)." }, "return": "takerTokenAmounts Taker amounts sold at each maker token amount." }, - "sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[],(uint256,uint256))": { + "sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[])": { "details": "Sample buy quotes from an arbitrary on-chain liquidity provider.", "params": { "makerToken": "Address of the maker token (what to buy).", "makerTokenAmounts": "Maker token buy amount for each sample.", - "opts": "`FakeBuyOptions` specifying target slippage and max iterations.", "registryAddress": "Address of the liquidity provider registry contract.", "takerToken": "Address of the taker token (what to sell)." }, @@ -391,10 +389,10 @@ }, "return": "takerTokenAmounts Taker amounts sold at each maker token amount." }, - "sampleSellsFromCurve(address,int128,int128,uint256[])": { + "sampleSellsFromCurve((address,bytes4,bytes4),int128,int128,uint256[])": { "details": "Sample sell quotes from Curve.", "params": { - "curveAddress": "Address of the Curve contract.", + "curveInfo": "Curve information specific to this token pair.", "fromTokenIdx": "Index of the taker token (what to sell).", "takerTokenAmounts": "Taker token sell amount for each sample.", "toTokenIdx": "Index of the maker token (what to buy)." @@ -471,10 +469,10 @@ }, "evm": { "bytecode": { - "object": "0x608060405234801561001057600080fd5b5061447d806100206000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c806368be3cf2116100b2578063abffc76111610081578063d0eea06d11610066578063d0eea06d14610288578063e68248f71461029b578063e9a8e442146102ae57610136565b8063abffc76114610255578063c7f7142e1461026857610136565b806368be3cf2146101fc5780636dd6b78d1461021c57806398cdafba1461022f5780639f76ad351461024257610136565b80634703a7e61161010957806358306ba0116100ee57806358306ba0146101c357806360ee052a146101d657806364ee6ade146101e957610136565b80634703a7e61461019d5780634cb8e253146101b057610136565b80631796fb871461013b5780632d753aa414610164578063354152a31461017757806339b085ad1461018a575b600080fd5b61014e6101493660046139e0565b6102c1565b60405161015b91906140df565b60405180910390f35b61014e610172366004613691565b610492565b61014e6101853660046139e0565b610688565b61014e610198366004613be0565b610842565b61014e6101ab36600461380f565b610aff565b61014e6101be36600461380f565b610cdb565b61014e6101d1366004613717565b610e0e565b61014e6101e436600461380f565b610ea5565b61014e6101f736600461380f565b61115a565b61020f61020a366004613b74565b611320565b60405161015b9190614061565b61014e61022a36600461380f565b611466565b61014e61023d36600461378a565b61170e565b61014e610250366004613717565b611748565b61014e610263366004613a1e565b61194c565b61027b610276366004613647565b611b37565b60405161015b9190613e5f565b61014e61029636600461386f565b611c72565b61014e6102a9366004613a1e565b611ca3565b61014e6102bc366004613be0565b611e76565b6060600082519050806040519080825280602002602001820160405280156102f3578160200160208202803883390190505b50915060005b8181101561048857600060608873ffffffffffffffffffffffffffffffffffffffff16620927c0600073ffffffffffffffffffffffffffffffffffffffff16630e71d1b9905060e01b8a8a8a888151811061035057fe5b602002602001015160405160240161036a93929190614121565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516103f39190613e43565b6000604051808303818686fa925050503d806000811461042f576040519150601f19603f3d011682016040523d82523d6000602084013e610434565b606091505b5090925090506000821561045d57818060200190516104569190810190613ca2565b9050610465565b505050610488565b8086858151811061047257fe5b60209081029190910101525050506001016102f9565b5050949350505050565b6060600082519050806040519080825280602002602001820160405280156104c4578160200160208202803883390190505b50915073ffffffffffffffffffffffffffffffffffffffff87166104e8575061067f565b60005b8181101561067c57600060608973ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff16636e79e133905060e01b8b8b8b8b898151811061054357fe5b602002602001015160405160240161055e9493929190613ee8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516105e79190613e43565b6000604051808303818686fa925050503d8060008114610623576040519150601f19603f3d011682016040523d82523d6000602084013e610628565b606091505b50909250905060008215610651578180602001905161064a9190810190613ca2565b9050610659565b50505061067c565b8086858151811061066657fe5b60209081029190910101525050506001016104eb565b50505b95945050505050565b6060600082519050806040519080825280602002602001820160405280156106ba578160200160208202803883390190505b50915060005b8181101561048857600060608873ffffffffffffffffffffffffffffffffffffffff16620927c0600073ffffffffffffffffffffffffffffffffffffffff166307211ef7905060e01b8a8a8a888151811061071757fe5b602002602001015160405160240161073193929190614121565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516107ba9190613e43565b6000604051808303818686fa925050503d80600081146107f6576040519150601f19603f3d011682016040523d82523d6000602084013e6107fb565b606091505b5090925090506000821561045d578180602001905161081d9190810190613ca2565b90508086858151811061082c57fe5b60209081029190910101525050506001016106c0565b6060835160405190808252806020026020018201604052801561086f578160200160208202803883390190505b50905060005b84518114610af75783818151811061088957fe5b602002602001015151600014806108b757508481815181106108a757fe5b6020026020010151608001516000145b806108d957508481815181106108c957fe5b602002602001015160a001516000145b156108fd5760008282815181106108ec57fe5b602002602001018181525050610aef565b600060608473ffffffffffffffffffffffffffffffffffffffff166207a1208673ffffffffffffffffffffffffffffffffffffffff1663e77286eb905060e01b89868151811061094957fe5b602002602001015189878151811061095d57fe5b602002602001015160405160240161097692919061419c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516109ff9190613e43565b6000604051808303818686fa925050503d8060008114610a3b576040519150601f19603f3d011682016040523d82523d6000602084013e610a40565b606091505b509150915081610a6b576000848481518110610a5857fe5b6020026020010181815250505050610aef565b610a73613340565b60008083806020019051610a8a9190810190613d27565b91945092509050600383516006811115610aa057fe5b141580610aab575080155b15610acf576000878781518110610abe57fe5b602002602001018181525050610ae9565b81878781518110610adc57fe5b6020026020010181815250505b50505050505b600101610875565b509392505050565b6060610b0b8385611f19565b8151604080518281526020808402820101909152818015610b36578160200160208202803883390190505b50915060005b81811015610cd25760006060610b50611f8c565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663ff1fd974905060e01b8a8a8a8881518110610b9a57fe5b6020026020010151604051602401610bb493929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610c3d9190613e43565b6000604051808303818686fa925050503d8060008114610c79576040519150601f19603f3d011682016040523d82523d6000602084013e610c7e565b606091505b50909250905060008215610ca75781806020019051610ca09190810190613ca2565b9050610caf565b505050610cd2565b80868581518110610cbc57fe5b6020908102919091010152505050600101610b3c565b50509392505050565b6060610ce78385611f19565b8151604080518281526020808402820101909152818015610d12578160200160208202803883390190505b5091506000610d1f611fa4565b90506000805b83811015610e03578273ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161480610d9257508273ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16145b15610dbc57610db58888888481518110610da857fe5b6020026020010151611fbc565b9150610de3565b610dcd8884888481518110610da857fe5b91508115610de357610de0838884611fbc565b91505b81858281518110610df057fe5b6020908102919091010152600101610d25565b505050509392505050565b60608273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610e7557508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16145b15610e7f57610e9d565b6060610e8c86858561115a565b9050610e9984868361115a565b9150505b949350505050565b6060610eb18385611f19565b8151604080518281526020808402820101909152818015610edc578160200160208202803883390190505b5091506000610ee9611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f2957610f24866127dc565b610f2c565b60005b90506000610f38611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f7857610f73866127dc565b610f7b565b60005b905060005b83811015610e03576001610f92611fa4565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156110295786516110089085907f2640f62c00000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b6020026020010151612874565b87848151811061101457fe5b60200260200101819350828152505050611146565b611031611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16141561109a5786516110089084907f59e9486200000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b86516000906110d39085907f59e9486200000000000000000000000000000000000000000000000000000000908b9087908110610ffb57fe5b92509050801561112957611108857f2640f62c0000000000000000000000000000000000000000000000000000000083612874565b88858151811061111457fe5b60200260200101819450828152505050611144565b600087848151811061113757fe5b6020026020010181815250505b505b806111515750610e03565b50600101610f80565b60606111668385611f19565b8151604080518281526020808402820101909152818015611191578160200160208202803883390190505b50915060005b81811015610cd257600060606111ab611f8c565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663144a2752905060e01b898b8a88815181106111f557fe5b602002602001015160405160240161120f93929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516112989190613e43565b6000604051808303818686fa925050503d80600081146112d4576040519150601f19603f3d011682016040523d82523d6000602084013e6112d9565b606091505b50909250905060008215610ca757818060200190516112fb9190810190613ca2565b90508086858151811061130a57fe5b6020908102919091010152505050600101611197565b60408051828152602080840282010190915260609082801561135657816020015b60608152602001906001900390816113415790505b50905060005b80831461145f57600060603086868581811061137457fe5b6020028201905080357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1368490030181126113ae57600080fd5b9091016020810191503567ffffffffffffffff8111156113cd57600080fd5b368190038213156113dd57600080fd5b6040516113eb929190613e33565b600060405180830381855afa9150503d8060008114611426576040519150601f19603f3d011682016040523d82523d6000602084013e61142b565b606091505b50915091508161143d57805160208201fd5b8084848151811061144a57fe5b6020908102919091010152505060010161135c565b5092915050565b60606114728385611f19565b815160408051828152602080840282010190915281801561149d578160200160208202803883390190505b50915060006114aa611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146114ea576114e5866127dc565b6114ed565b60005b905060006114f9611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161461153957611534866127dc565b61153c565b60005b905060005b83811015610e03576001611553611fa4565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156115dd5786516115bc9085907f95b68fe700000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b8784815181106115c857fe5b602002602001018193508281525050506116fa565b6115e5611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16141561164e5786516115bc9084907fcd7724c300000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b86516000906116879086907f95b68fe700000000000000000000000000000000000000000000000000000000908b9087908110610ffb57fe5b9250905080156116dd576116bc847fcd7724c30000000000000000000000000000000000000000000000000000000083612874565b8885815181106116c857fe5b602002602001018194508281525050506116f8565b60008784815181106116eb57fe5b6020026020010181815250505b505b806117055750610e03565b50600101611541565b606061173e858585857f9f76ad35000000000000000000000000000000000000000000000000000000008b6129bd565b9695505050505050565b60606000825190508060405190808252806020026020018201604052801561177a578160200160208202803883390190505b509150600061178a878787611b37565b905073ffffffffffffffffffffffffffffffffffffffff81166117af5750610e9d9050565b60005b8281101561194157600060608373ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff1663343fbcdd905060e01b8b8b8b888151811061180957fe5b602002602001015160405160240161182393929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516118ac9190613e43565b6000604051808303818686fa925050503d80600081146118e8576040519150601f19603f3d011682016040523d82523d6000602084013e6118ed565b606091505b50909250905060008215611916578180602001905161190f9190810190613ca2565b905061191e565b505050611941565b8087858151811061192b57fe5b60209081029190910101525050506001016117b2565b505050949350505050565b60606000825190508060405190808252806020026020018201604052801561197e578160200160208202803883390190505b50915060005b81811015611b2f5760006060611998612b83565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff1663d06ca61f905060e01b8886815181106119e057fe5b60200260200101518a6040516024016119fa9291906142ee565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611a839190613e43565b6000604051808303818686fa925050503d8060008114611abf576040519150601f19603f3d011682016040523d82523d6000602084013e611ac4565b606091505b50909250905060008215611b045781806020019051611ae69190810190613ae1565b600189510381518110611af557fe5b60200260200101519050611b0c565b505050611b2f565b80868581518110611b1957fe5b6020908102919091010152505050600101611984565b505092915050565b6040516000906060907f153f59970000000000000000000000000000000000000000000000000000000090611b729086908690602401613e80565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050600060608673ffffffffffffffffffffffffffffffffffffffff1683604051611bfa9190613e43565b600060405180830381855afa9150503d8060008114611c35576040519150601f19603f3d011682016040523d82523d6000602084013e611c3a565b606091505b5091509150818015611c4d575080516020145b15611c6757611c5d81600c612b9b565b9350505050611c6b565b5050505b9392505050565b606061067f858585857f4cb8e2530000000000000000000000000000000000000000000000000000000060006129bd565b606060008251905080604051908082528060200260200182016040528015611cd5578160200160208202803883390190505b50915060005b81811015611b2f5760006060611cef612b83565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff16631f00ca74905060e01b888681518110611d3757fe5b60200260200101518a604051602401611d519291906142ee565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611dda9190613e43565b6000604051808303818686fa925050503d8060008114611e16576040519150601f19603f3d011682016040523d82523d6000602084013e611e1b565b606091505b50909250905060008215611b045781806020019051611e3d9190810190613ae1565b600081518110611e4957fe5b6020026020010151905080868581518110611e6057fe5b6020908102919091010152505050600101611cdb565b6060611e83848484610842565b905060005b8451811015610af757818181518110611e9d57fe5b6020026020010151600014611f1157611ef8828281518110611ebb57fe5b6020026020010151868381518110611ecf57fe5b602002602001015160a00151878481518110611ee757fe5b602002602001015160800151612be0565b828281518110611f0457fe5b6020026020010181815250505b600101611e88565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611f88576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f7f9061413f565b60405180910390fd5b5050565b73794e6e91555438afc3ccf1c5076a74f42133d08d90565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290565b6000806000611fc9612c22565b9150915060608173ffffffffffffffffffffffffffffffffffffffff16633d3dc52c611ff3611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161461202b578861202d565b875b6040518263ffffffff1660e01b81526004016120499190613e5f565b60006040518083038186803b15801561206157600080fd5b505afa158015612075573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526120bb9190810190613ae1565b604080516000808252602082019092529192505b8251811015612374576040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061213e907331e085afd48a1d6e51cc193153d625e8f0514c7f90600401613e5f565b60206040518083038186803b15801561215657600080fd5b505afa15801561216a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061218e9190810190613ca2565b83828151811061219a57fe5b6020026020010151148061227557506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061220f907310908c875d865c66f271f5d3949848971c9595c990600401613e5f565b60206040518083038186803b15801561222757600080fd5b505afa15801561223b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061225f9190810190613ca2565b83828151811061226b57fe5b6020026020010151145b8061234757506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b906122e190731e158c0e93c30d24e918ef83d1e0be23595c3c0f90600401613e5f565b60206040518083038186803b1580156122f957600080fd5b505afa15801561230d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506123319190810190613ca2565b83828151811061233d57fe5b6020026020010151145b1561236c576123698284838151811061235c57fe5b6020026020010151612d56565b91505b6001016120cf565b50606061237f611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161415612496578473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8960028560006040519080825280602002602001820160405280156123fd578160200160208202803883390190505b506040518563ffffffff1660e01b815260040161241d9493929190613fdc565b60006040518083038186803b15801561243557600080fd5b505afa158015612449573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261248f9190810190613cba565b9050612576565b8473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8a60028560006040519080825280602002602001820160405280156124e1578160200160208202803883390190505b506040518563ffffffff1660e01b81526004016125019493929190613fdc565b60006040518083038186803b15801561251957600080fd5b505afa15801561252d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526125739190810190613cba565b90505b60006060612582612dec565b73ffffffffffffffffffffffffffffffffffffffff166216e3607f418436bc000000000000000000000000000000000000000000000000000000006125c5611fa4565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff16146125fd578d612613565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b61261b611fa4565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612653578d612669565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b8d600089604051602401612681959493929190613f89565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161270a9190613e43565b6000604051808303818686fa925050503d8060008114612746576040519150601f19603f3d011682016040523d82523d6000602084013e61274b565b606091505b50909250905060008215612774578180602001905161276d9190810190613ca2565b9050612785565b600098505050505050505050611c6b565b60006127908c612e04565b60ff16905060006127a08e612e04565b60ff169050670de0b6b3a764000081600a0a83600a0a8e860202816127c157fe5b04816127c957fe5b049e9d5050505050505050505050505050565b60006127e6612e0f565b73ffffffffffffffffffffffffffffffffffffffff166306f2bf62836040518263ffffffff1660e01b815260040161281e9190613e5f565b60206040518083038186803b15801561283657600080fd5b505afa15801561284a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061286e919081019061362b565b92915050565b60008073ffffffffffffffffffffffffffffffffffffffff8516612897576129b5565b60608573ffffffffffffffffffffffffffffffffffffffff16620249f086866040516024016128c691906142e5565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161294f9190613e43565b6000604051808303818686fa925050503d806000811461298b576040519150601f19603f3d011682016040523d82523d6000602084013e612990565b606091505b50909250905081156129b357808060200190516129b09190810190613ca2565b92505b505b935093915050565b60606129c98688611f19565b84516129d45761173e565b60008060008751604051908082528060200260200182016040528015612a04578160200160208202803883390190505b509350612a28898b8a600081518110612a1957fe5b60200260200101518989612e27565b925082612a38575061173e915050565b612a458a8a858989612e27565b915081612a55575061173e915050565b60005b8851811015612b755760005b8860200151811015612b3757612a8e8a8381518110612a7f57fe5b60200260200101518587612be0565b9450612aa589600001516127100161271087612be0565b94506000612ab68d8d888c8c612e27565b905080612ac35750612b37565b8094508a8381518110612ad257fe5b60200260200101518510612b2e5760008b8481518110612aee57fe5b60200260200101518c8581518110612b0257fe5b602002602001015187036127100281612b1757fe5b0490508a600001518111612b2c575050612b37565b505b50600101612a64565b50612b56898281518110612b4757fe5b60200260200101518486612be0565b858281518110612b6257fe5b6020908102919091010152600101612a58565b505050509695505050505050565b73f164fc0ec4e93095b804a4795bbe1e041497b92a90565b60008160140183511015612bc157612bc1612bbc60048551856014016130dc565b613181565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b6000610e9d83612c16612bfa82600163ffffffff61318916565b612c0a888763ffffffff6131a816565b9063ffffffff6131d916565b9063ffffffff6131f516565b600080612c2d612dec565b73ffffffffffffffffffffffffffffffffffffffff1663b78b842d6040518163ffffffff1660e01b815260040160206040518083038186803b158015612c7257600080fd5b505afa158015612c86573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612caa919081019061362b565b73ffffffffffffffffffffffffffffffffffffffff1663c3a2a93a6040518163ffffffff1660e01b815260040160006040518083038186803b158015612cef57600080fd5b505afa158015612d03573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052612d4991908101906138e1565b5091955093505050509091565b60608251600101604051908082528060200260200182016040528015612d86578160200160208202803883390190505b50905060005b8351811015612dc857838181518110612da157fe5b6020026020010151828281518110612db557fe5b6020908102919091010152600101612d8c565b508181600183510381518110612dda57fe5b60200260200101818152505092915050565b739aab3f75489902f3a48495025729a0af77d4b11e90565b600061286e8261321f565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9590565b6040805160018082528183019092526000916060918291602080830190803883390190505090508581600081518110612e5c57fe5b60209081029190910101527fffffffff0000000000000000000000000000000000000000000000000000000085167f4cb8e253000000000000000000000000000000000000000000000000000000001415612f6c576040517f4cb8e2530000000000000000000000000000000000000000000000000000000090612ee8908a908a908590602401613f1f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529150613025565b6040517f9f76ad350000000000000000000000000000000000000000000000000000000090612fa59086908b908b908690602401613ea7565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915291505b600060603073ffffffffffffffffffffffffffffffffffffffff168460405161304e9190613e43565b600060405180830381855afa9150503d8060008114613089576040519150601f19603f3d011682016040523d82523d6000602084013e61308e565b606091505b5091509150816130a557600094505050505061067f565b808060200190516130b99190810190613ae1565b6000815181106130c557fe5b602002602001015194505050505095945050505050565b6060632800659560e01b8484846040516024016130fb93929190614113565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915290509392505050565b805160208201fd5b6000828211156131a2576131a2612bbc600285856132f0565b50900390565b6000826131b75750600061286e565b828202828482816131c457fe5b0414611c6b57611c6b612bbc600186866132f0565b600082820183811015611c6b57611c6b612bbc600086866132f0565b60008161320b5761320b612bbc600385856132f0565b600082848161321657fe5b04949350505050565b600060129050600060608373ffffffffffffffffffffffffffffffffffffffff166040518060400160405280600481526020017f313ce567000000000000000000000000000000000000000000000000000000008152506040516132839190613e43565b600060405180830381855afa9150503d80600081146132be576040519150601f19603f3d011682016040523d82523d6000602084013e6132c3565b606091505b50915091508180156132d6575080516020145b156132e9576132e681600061330f565b92505b5050919050565b606063e946c1bb60e01b8484846040516024016130fb939291906140f2565b6000611c6b83836000816020018351101561333757613337612bbc60058551856020016130dc565b50016020015190565b6040805160608101909152806000815260006020820181905260409091015290565b803561286e81614418565b805161286e81614418565b600082601f830112613388578081fd5b813561339b61339682614379565b614352565b8181529150602080830190840160005b838110156133d8576133c3876020843589010161344b565b835260209283019291909101906001016133ab565b5050505092915050565b600082601f8301126133f2578081fd5b813561340061339682614379565b81815291506020808301908481018184028601820187101561342157600080fd5b60005b8481101561344057813584529282019290820190600101613424565b505050505092915050565b600082601f83011261345b578081fd5b813561346961339682614399565b915080825283602082850101111561348057600080fd5b8060208401602084013760009082016020015292915050565b8035600f81900b811461286e57600080fd5b6000604082840312156134bc578081fd5b6134c66040614352565b9050813581526020820135602082015292915050565b60006101c08083850312156134ef578182fd5b6134f881614352565b9150506135058383613362565b81526135148360208401613362565b60208201526135268360408401613362565b60408201526135388360608401613362565b60608201526080820135608082015260a082013560a082015260c082013560c082015260e082013560e08201526101008083013581830152506101208083013581830152506101408083013567ffffffffffffffff8082111561359a57600080fd5b6135a68683870161344b565b838501526101609250828501359150808211156135c257600080fd5b6135ce8683870161344b565b838501526101809250828501359150808211156135ea57600080fd5b6135f68683870161344b565b838501526101a092508285013591508082111561361257600080fd5b5061361f8582860161344b565b82840152505092915050565b60006020828403121561363c578081fd5b8151611c6b81614418565b60008060006060848603121561365b578182fd5b833561366681614418565b9250602084013561367681614418565b9150604084013561368681614418565b809150509250925092565b600080600080600060a086880312156136a8578283fd5b85356136b381614418565b945060208601356136c381614418565b935060408601356136d381614418565b925060608601356136e381614418565b9150608086013567ffffffffffffffff8111156136fe578182fd5b61370a888289016133e2565b9150509295509295909350565b6000806000806080858703121561372c578182fd5b843561373781614418565b9350602085013561374781614418565b9250604085013561375781614418565b9150606085013567ffffffffffffffff811115613772578182fd5b61377e878288016133e2565b91505092959194509250565b600080600080600060c086880312156137a1578283fd5b85356137ac81614418565b945060208601356137bc81614418565b935060408601356137cc81614418565b9250606086013567ffffffffffffffff8111156137e7578182fd5b6137f3888289016133e2565b92505061380387608088016134ab565b90509295509295909350565b600080600060608486031215613823578081fd5b833561382e81614418565b9250602084013561383e81614418565b9150604084013567ffffffffffffffff811115613859578182fd5b613865868287016133e2565b9150509250925092565b60008060008060a08587031215613884578182fd5b843561388f81614418565b9350602085013561389f81614418565b9250604085013567ffffffffffffffff8111156138ba578283fd5b6138c6878288016133e2565b9250506138d686606087016134ab565b905092959194509250565b60008060008060008060c087890312156138f9578384fd5b865161390481614418565b8096505060208088015161391781614418565b604089015190965061392881614418565b606089015190955061393981614418565b608089015190945061394a81614418565b60a089015190935067ffffffffffffffff811115613966578283fd5b8089018a601f820112613977578384fd5b8051915061398761339683614379565b82815283810190828501858502840186018e10156139a3578687fd5b8693505b848410156139cd576139b98e8261336d565b8352600193909301929185019185016139a7565b5080955050505050509295509295509295565b600080600080608085870312156139f5578182fd5b8435613a0081614418565b9350613a0f8660208701613499565b92506137578660408701613499565b60008060408385031215613a30578182fd5b823567ffffffffffffffff80821115613a47578384fd5b81850186601f820112613a58578485fd5b80359250613a6861339684614379565b80848252602080830192508084018a828389028701011115613a88578889fd5b8894505b86851015613ab3578035613a9f81614418565b845260019490940193928101928101613a8c565b509096508701359350505080821115613aca578283fd5b50613ad7858286016133e2565b9150509250929050565b60006020808385031215613af3578182fd5b825167ffffffffffffffff811115613b09578283fd5b80840185601f820112613b1a578384fd5b80519150613b2a61339683614379565b8281528381019082850185850284018601891015613b46578687fd5b8693505b84841015613b68578051835260019390930192918501918501613b4a565b50979650505050505050565b60008060208385031215613b86578182fd5b823567ffffffffffffffff80821115613b9d578384fd5b81850186601f820112613bae578485fd5b8035925081831115613bbe578485fd5b8660208085028301011115613bd1578485fd5b60200196919550909350505050565b600080600060608486031215613bf4578081fd5b833567ffffffffffffffff80821115613c0b578283fd5b81860187601f820112613c1c578384fd5b80359250613c2c61339684614379565b83815260208082019190838101875b87811015613c6457613c528d8484358901016134dc565b85529382019390820190600101613c3b565b50919850890135945050505080821115613c7c578283fd5b50613c8986828701613378565b925050613c998560408601613362565b90509250925092565b600060208284031215613cb3578081fd5b5051919050565b600060208284031215613ccb578081fd5b815167ffffffffffffffff811115613ce1578182fd5b80830184601f820112613cf2578283fd5b80519150613d0261339683614399565b828152856020848401011115613d16578384fd5b61067f8360208301602085016143db565b600080600083850360a0811215613d3c578182fd5b6060811215613d49578182fd5b50613d546060614352565b845160078110613d62578283fd5b815260208581015190820152604080860151908201526060850151608086015191945092508015158114613686578182fd5b73ffffffffffffffffffffffffffffffffffffffff169052565b6000815180845260208401935060208301825b82811015613ddf578151865260209586019590910190600101613dc1565b5093949350505050565b60008151808452613e018160208601602086016143db565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6000828483379101908152919050565b60008251613e558184602087016143db565b9190910192915050565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b73ffffffffffffffffffffffffffffffffffffffff92831681529116602082015260400190565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401528085166040840152506080606083015261173e6080830184613dae565b73ffffffffffffffffffffffffffffffffffffffff9485168152928416602084015292166040820152606081019190915260800190565b600073ffffffffffffffffffffffffffffffffffffffff80861683528085166020840152506060604083015261067f6060830184613dae565b73ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152604081019190915260600190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260ff8416606083015260a06080830152613fd160a0830184613de9565b979650505050505050565b60006080820173ffffffffffffffffffffffffffffffffffffffff871683526140048661440b565b60208381018790526080604085015285519182905285019060a0840190835b81811015614041578351835260209384019390920191600101614023565b505083810360608501526140558186613dae565b98975050505050505050565b6000602080830181845280855180835260408601915060408482028701019250838701855b828110156140d2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526140c0858351613de9565b94509285019290850190600101614086565b5092979650505050505050565b600060208252611c6b6020830184613dae565b606081016140ff8561440b565b938152602081019290925260409091015290565b60608101600885106140ff57fe5b600f93840b81529190920b6020820152604081019190915260600190565b60208082526025908201527f455243323042726964676553616d706c65722f494e56414c49445f544f4b454e60408201527f5f50414952000000000000000000000000000000000000000000000000000000606082015260800190565b6000604082526141b0604083018551613d94565b60208401516141c26060840182613d94565b5060408401516141d56080840182613d94565b5060608401516141e860a0840182613d94565b50608084015160c083015260a084015160e083015260c0840151610100818185015260e086015191506101208281860152818701519250610140915082828601528087015192505061016082818601528187015192506101c091506101808281870152614259610200870185613de9565b8289015194507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc092506101a08388830301818901526142988287613de9565b838b015196508489820301868a01526142b18188613de9565b955050808a0151955050505080858303016101e0860152506142d38183613de9565b8481036020860152613fd18187613de9565b90815260200190565b60006040820184835260406020840152808451808352606085019150602086019250835b8181101561434657835173ffffffffffffffffffffffffffffffffffffffff16835260209384019390920191600101614312565b50909695505050505050565b60405181810167ffffffffffffffff8111828210171561437157600080fd5b604052919050565b600067ffffffffffffffff82111561438f578081fd5b5060209081020190565b600067ffffffffffffffff8211156143af578081fd5b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60005b838110156143f65781810151838201526020016143de565b83811115614405576000848401525b50505050565b6004811061441557fe5b50565b73ffffffffffffffffffffffffffffffffffffffff8116811461441557600080fdfea365627a7a7231582067c5cc763deb33dec3babe92591793d68b6f9441aa149a37c465890b70b5bd1e6c6578706572696d656e74616cf564736f6c63430005110040" + "object": "0x608060405234801561001057600080fd5b50614784806100206000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c806368be3cf2116100b2578063a2c28d4b11610081578063c7f7142e11610066578063c7f7142e1461027b578063e68248f71461029b578063e9a8e442146102ae57610136565b8063a2c28d4b14610255578063abffc7611461026857610136565b806368be3cf2146101fc5780636dd6b78d1461021c5780639209483b1461022f5780639f76ad351461024257610136565b80634703a7e61161010957806358306ba0116100ee57806358306ba0146101c357806360ee052a146101d657806364ee6ade146101e957610136565b80634703a7e61461019d5780634cb8e253146101b057610136565b80630cc6600b1461013b5780632d753aa41461016457806339b085ad1461017757806340bc03ae1461018a575b600080fd5b61014e610149366004613839565b6102c1565b60405161015b919061435f565b60405180910390f35b61014e610172366004613740565b61033b565b61014e610185366004613b5b565b610531565b61014e610198366004613eaa565b6107ee565b61014e6101ab366004613839565b6109a6565b61014e6101be366004613839565b610b82565b61014e6101d13660046137c6565b610cb5565b61014e6101e4366004613839565b610d4c565b61014e6101f7366004613839565b611001565b61020f61020a366004613aef565b6111c7565b60405161015b91906142e1565b61014e61022a366004613839565b61130d565b61014e61023d366004613eaa565b6115b5565b61014e6102503660046137c6565b6117e9565b61014e6102633660046137c6565b6119ed565b61014e610276366004613999565b611a53565b61028e6102893660046136f6565b611c3e565b60405161015b91906140d5565b61014e6102a9366004613999565b611d78565b61014e6102bc366004613b5b565b611f4b565b60606102cd8385611fee565b6103316040518060600160405280856040516020016102ec91906140d5565b60405160208183030381529060405281526020018660405160200161031191906140d5565b6040516020818303038152906040528152602001612061815250836121d9565b90505b9392505050565b60606000825190508060405190808252806020026020018201604052801561036d578160200160208202803883390190505b50915073ffffffffffffffffffffffffffffffffffffffff87166103915750610528565b60005b8181101561052557600060608973ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff16636e79e133905060e01b8b8b8b8b89815181106103ec57fe5b602002602001015160405160240161040794939291906141a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161049091906140b9565b6000604051808303818686fa925050503d80600081146104cc576040519150601f19603f3d011682016040523d82523d6000602084013e6104d1565b606091505b509092509050600082156104fa57818060200190516104f39190810190613d6e565b9050610502565b505050610525565b8086858151811061050f57fe5b6020908102919091010152505050600101610394565b50505b95945050505050565b6060835160405190808252806020026020018201604052801561055e578160200160208202803883390190505b50905060005b845181146107e65783818151811061057857fe5b602002602001015151600014806105a6575084818151811061059657fe5b6020026020010151608001516000145b806105c857508481815181106105b857fe5b602002602001015160a001516000145b156105ec5760008282815181106105db57fe5b6020026020010181815250506107de565b600060608473ffffffffffffffffffffffffffffffffffffffff166207a1208673ffffffffffffffffffffffffffffffffffffffff1663e77286eb905060e01b89868151811061063857fe5b602002602001015189878151811061064c57fe5b6020026020010151604051602401610665929190614466565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516106ee91906140b9565b6000604051808303818686fa925050503d806000811461072a576040519150601f19603f3d011682016040523d82523d6000602084013e61072f565b606091505b50915091508161075a57600084848151811061074757fe5b60200260200101818152505050506107de565b610762613533565b600080838060200190516107799190810190613f46565b9194509250905060038351600681111561078f57fe5b14158061079a575080155b156107be5760008787815181106107ad57fe5b6020026020010181815250506107d8565b818787815181106107cb57fe5b6020026020010181815250505b50505050505b600101610564565b509392505050565b606060008251905080604051908082528060200260200182016040528015610820578160200160208202803883390190505b50915060005b8181101561099c5760006060886000015173ffffffffffffffffffffffffffffffffffffffff16620927c08a602001518a8a8a888151811061086457fe5b602002602001015160405160240161087e939291906143a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161090791906140b9565b6000604051808303818686fa925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b50909250905060008215610971578180602001905161096a9190810190613d6e565b9050610979565b50505061099c565b8086858151811061098657fe5b6020908102919091010152505050600101610826565b5050949350505050565b60606109b28385611fee565b81516040805182815260208084028201019091528180156109dd578160200160208202803883390190505b50915060005b81811015610b7957600060606109f76123ae565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663ff1fd974905060e01b8a8a8a8881518110610a4157fe5b6020026020010151604051602401610a5b939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610ae491906140b9565b6000604051808303818686fa925050503d8060008114610b20576040519150601f19603f3d011682016040523d82523d6000602084013e610b25565b606091505b50909250905060008215610b4e5781806020019051610b479190810190613d6e565b9050610b56565b505050610b79565b80868581518110610b6357fe5b60209081029190910101525050506001016109e3565b50509392505050565b6060610b8e8385611fee565b8151604080518281526020808402820101909152818015610bb9578160200160208202803883390190505b5091506000610bc66123c6565b90506000805b83811015610caa578273ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161480610c3957508273ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16145b15610c6357610c5c8888888481518110610c4f57fe5b60200260200101516123de565b9150610c8a565b610c748884888481518110610c4f57fe5b91508115610c8a57610c878388846123de565b91505b81858281518110610c9757fe5b6020908102919091010152600101610bcc565b505050509392505050565b60608273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610d1c57508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16145b15610d2657610d44565b6060610d33868585611001565b9050610d40848683611001565b9150505b949350505050565b6060610d588385611fee565b8151604080518281526020808402820101909152818015610d83578160200160208202803883390190505b5091506000610d906123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610dd057610dcb86612bfe565b610dd3565b60005b90506000610ddf6123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610e1f57610e1a86612bfe565b610e22565b60005b905060005b83811015610caa576001610e396123c6565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161415610ed0578651610eaf9085907f2640f62c00000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b6020026020010151612c90565b878481518110610ebb57fe5b60200260200101819350828152505050610fed565b610ed86123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161415610f41578651610eaf9084907f59e9486200000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b8651600090610f7a9085907f59e9486200000000000000000000000000000000000000000000000000000000908b9087908110610ea257fe5b925090508015610fd057610faf857f2640f62c0000000000000000000000000000000000000000000000000000000083612c90565b888581518110610fbb57fe5b60200260200101819450828152505050610feb565b6000878481518110610fde57fe5b6020026020010181815250505b505b80610ff85750610caa565b50600101610e27565b606061100d8385611fee565b8151604080518281526020808402820101909152818015611038578160200160208202803883390190505b50915060005b81811015610b7957600060606110526123ae565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663144a2752905060e01b898b8a888151811061109c57fe5b60200260200101516040516024016110b6939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161113f91906140b9565b6000604051808303818686fa925050503d806000811461117b576040519150601f19603f3d011682016040523d82523d6000602084013e611180565b606091505b50909250905060008215610b4e57818060200190516111a29190810190613d6e565b9050808685815181106111b157fe5b602090810291909101015250505060010161103e565b6040805182815260208084028201019091526060908280156111fd57816020015b60608152602001906001900390816111e85790505b50905060005b80831461130657600060603086868581811061121b57fe5b6020028201905080357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe13684900301811261125557600080fd5b9091016020810191503567ffffffffffffffff81111561127457600080fd5b3681900382131561128457600080fd5b6040516112929291906140a9565b600060405180830381855afa9150503d80600081146112cd576040519150601f19603f3d011682016040523d82523d6000602084013e6112d2565b606091505b5091509150816112e457805160208201fd5b808484815181106112f157fe5b60209081029190910101525050600101611203565b5092915050565b60606113198385611fee565b8151604080518281526020808402820101909152818015611344578160200160208202803883390190505b50915060006113516123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146113915761138c86612bfe565b611394565b60005b905060006113a06123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146113e0576113db86612bfe565b6113e3565b60005b905060005b83811015610caa5760016113fa6123c6565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156114845786516114639085907f95b68fe700000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b87848151811061146f57fe5b602002602001018193508281525050506115a1565b61148c6123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614156114f55786516114639084907fcd7724c300000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b865160009061152e9086907f95b68fe700000000000000000000000000000000000000000000000000000000908b9087908110610ea257fe5b92509050801561158457611563847fcd7724c30000000000000000000000000000000000000000000000000000000083612c90565b88858151811061156f57fe5b6020026020010181945082815250505061159f565b600087848151811061159257fe5b6020026020010181815250505b505b806115ac5750610caa565b506001016113e8565b60408401516060907fffffffff000000000000000000000000000000000000000000000000000000001661164f57604080516060810190915261164890806116018689608084016143bf565b604051602081830303815290604052815260200186886040516020016116289291906143bf565b6040516020818303038152906040528152602001612dd9815250836121d9565b9050610d44565b815160408051828152602080840282010190915281801561167a578160200160208202803883390190505b50915060005b8181101561099c5760006060886000015173ffffffffffffffffffffffffffffffffffffffff16620927c08a604001518a8a8a88815181106116be57fe5b60200260200101516040516024016116d8939291906143a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161176191906140b9565b6000604051808303818686fa925050503d806000811461179d576040519150601f19603f3d011682016040523d82523d6000602084013e6117a2565b606091505b5090925090506000821561097157818060200190516117c49190810190613d6e565b9050808685815181106117d357fe5b6020908102919091010152505050600101611680565b60606000825190508060405190808252806020026020018201604052801561181b578160200160208202803883390190505b509150600061182b878787611c3e565b905073ffffffffffffffffffffffffffffffffffffffff81166118505750610d449050565b60005b828110156119e257600060608373ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff1663343fbcdd905060e01b8b8b8b88815181106118aa57fe5b60200260200101516040516024016118c4939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161194d91906140b9565b6000604051808303818686fa925050503d8060008114611989576040519150601f19603f3d011682016040523d82523d6000602084013e61198e565b606091505b509092509050600082156119b757818060200190516119b09190810190613d6e565b90506119bf565b5050506119e2565b808785815181106119cc57fe5b6020908102919091010152505050600101611853565b505050949350505050565b6040805160608181019092526105289080611a0c86896080840161412f565b60405160208183030381529060405281526020018688604051602001611a3392919061412f565b6040516020818303038152906040528152602001612f70815250836121d9565b606060008251905080604051908082528060200260200182016040528015611a85578160200160208202803883390190505b50915060005b81811015611c365760006060611a9f612fea565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff1663d06ca61f905060e01b888681518110611ae757fe5b60200260200101518a604051602401611b019291906145b8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611b8a91906140b9565b6000604051808303818686fa925050503d8060008114611bc6576040519150601f19603f3d011682016040523d82523d6000602084013e611bcb565b606091505b50909250905060008215611c0b5781806020019051611bed9190810190613a5c565b600189510381518110611bfc57fe5b60200260200101519050611c13565b505050611c36565b80868581518110611c2057fe5b6020908102919091010152505050600101611a8b565b505092915050565b6040516000906060907f153f59970000000000000000000000000000000000000000000000000000000090611c79908690869060240161412f565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050600060608673ffffffffffffffffffffffffffffffffffffffff1683604051611d0191906140b9565b600060405180830381855afa9150503d8060008114611d3c576040519150601f19603f3d011682016040523d82523d6000602084013e611d41565b606091505b5091509150818015611d54575080516020145b15611d6e57611d6481600c613002565b9350505050610334565b5050509392505050565b606060008251905080604051908082528060200260200182016040528015611daa578160200160208202803883390190505b50915060005b81811015611c365760006060611dc4612fea565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff16631f00ca74905060e01b888681518110611e0c57fe5b60200260200101518a604051602401611e269291906145b8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611eaf91906140b9565b6000604051808303818686fa925050503d8060008114611eeb576040519150601f19603f3d011682016040523d82523d6000602084013e611ef0565b606091505b50909250905060008215611c0b5781806020019051611f129190810190613a5c565b600081518110611f1e57fe5b6020026020010151905080868581518110611f3557fe5b6020908102919091010152505050600101611db0565b6060611f58848484610531565b905060005b84518110156107e657818181518110611f7257fe5b6020026020010151600014611fe657611fcd828281518110611f9057fe5b6020026020010151868381518110611fa457fe5b602002602001015160a00151878481518110611fbc57fe5b602002602001015160800151613047565b828281518110611fd957fe5b6020026020010181815250505b600101611f5d565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561205d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612054906143d7565b60405180910390fd5b5050565b60008060603073ffffffffffffffffffffffffffffffffffffffff16634cb8e25360e01b8780602001905161209991908101906136a1565b878060200190516120ad91908101906136a1565b6120b688613089565b6040516024016120c8939291906140f6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161215191906140b9565b600060405180830381855afa9150503d806000811461218c576040519150601f19603f3d011682016040523d82523d6000602084013e612191565b606091505b5091509150816121a657600092505050610334565b808060200190516121ba9190810190613a5c565b6000815181106121c657fe5b6020026020010151925050509392505050565b60608151604051908082528060200260200182016040528015612206578160200160208202803883390190505b509050815160001415612218576123a8565b6000612249846000015185602001518560008151811061223457fe5b6020026020010151876040015163ffffffff16565b90508061225657506123a8565b60006122738560200151866000015184886040015163ffffffff16565b90508061228257506123a89050565b60005b84518110156123a45760005b6005811015612366576122b88683815181106122a957fe5b60200260200101518486613047565b93506122c961271561271086613047565b935060006122e888602001518960000151878b6040015163ffffffff16565b9050806122f55750612366565b80935086838151811061230457fe5b6020026020010151841061235d57600087848151811061232057fe5b602002602001015161271089868151811061233757fe5b60200260200101518703028161234957fe5b0490506005811161235b575050612366565b505b50600101612291565b5061238585828151811061237657fe5b60200260200101518385613047565b84828151811061239157fe5b6020908102919091010152600101612285565b5050505b92915050565b73794e6e91555438afc3ccf1c5076a74f42133d08d90565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290565b60008060006123eb6130ca565b9150915060608173ffffffffffffffffffffffffffffffffffffffff16633d3dc52c6124156123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161461244d578861244f565b875b6040518263ffffffff1660e01b815260040161246b91906140d5565b60006040518083038186803b15801561248357600080fd5b505afa158015612497573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526124dd9190810190613a5c565b604080516000808252602082019092529192505b8251811015612796576040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b90612560907331e085afd48a1d6e51cc193153d625e8f0514c7f906004016140d5565b60206040518083038186803b15801561257857600080fd5b505afa15801561258c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506125b09190810190613d6e565b8382815181106125bc57fe5b6020026020010151148061269757506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b90612631907310908c875d865c66f271f5d3949848971c9595c9906004016140d5565b60206040518083038186803b15801561264957600080fd5b505afa15801561265d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506126819190810190613d6e565b83828151811061268d57fe5b6020026020010151145b8061276957506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061270390731e158c0e93c30d24e918ef83d1e0be23595c3c0f906004016140d5565b60206040518083038186803b15801561271b57600080fd5b505afa15801561272f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506127539190810190613d6e565b83828151811061275f57fe5b6020026020010151145b1561278e5761278b8284838151811061277e57fe5b60200260200101516131fe565b91505b6001016124f1565b5060606127a16123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614156128b8578473ffffffffffffffffffffffffffffffffffffffff166381efcbdd89600285600060405190808252806020026020018201604052801561281f578160200160208202803883390190505b506040518563ffffffff1660e01b815260040161283f949392919061425c565b60006040518083038186803b15801561285757600080fd5b505afa15801561286b573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526128b19190810190613d86565b9050612998565b8473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8a6002856000604051908082528060200260200182016040528015612903578160200160208202803883390190505b506040518563ffffffff1660e01b8152600401612923949392919061425c565b60006040518083038186803b15801561293b57600080fd5b505afa15801561294f573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526129959190810190613d86565b90505b600060606129a4613294565b73ffffffffffffffffffffffffffffffffffffffff166216e3607f418436bc000000000000000000000000000000000000000000000000000000006129e76123c6565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612a1f578d612a35565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b612a3d6123c6565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612a75578d612a8b565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b8d600089604051602401612aa3959493929190614209565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612b2c91906140b9565b6000604051808303818686fa925050503d8060008114612b68576040519150601f19603f3d011682016040523d82523d6000602084013e612b6d565b606091505b50909250905060008215612b965781806020019051612b8f9190810190613d6e565b9050612ba7565b600098505050505050505050610334565b6000612bb28c6132ac565b60ff1690506000612bc28e6132ac565b60ff169050670de0b6b3a764000081600a0a83600a0a8e86020281612be357fe5b0481612beb57fe5b049e9d5050505050505050505050505050565b6000612c086132b7565b73ffffffffffffffffffffffffffffffffffffffff166306f2bf62836040518263ffffffff1660e01b8152600401612c4091906140d5565b60206040518083038186803b158015612c5857600080fd5b505afa158015612c6c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506123a891908101906136a1565b60008073ffffffffffffffffffffffffffffffffffffffff8516612cb357612dd1565b60608573ffffffffffffffffffffffffffffffffffffffff16620249f08686604051602401612ce291906145af565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612d6b91906140b9565b6000604051808303818686fa925050503d8060008114612da7576040519150601f19603f3d011682016040523d82523d6000602084013e612dac565b606091505b5090925090508115612dcf5780806020019051612dcc9190810190613d6e565b92505b505b935093915050565b600080612de4613555565b85806020019051612df89190810190613e0f565b91509150600085806020019051612e129190810190613df3565b905060006060307f40bc03ae00000000000000000000000000000000000000000000000000000000858786612e468c613089565b604051602401612e599493929190614434565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612ee291906140b9565b600060405180830381855afa9150503d8060008114612f1d576040519150601f19603f3d011682016040523d82523d6000602084013e612f22565b606091505b509150915081612f3a57600095505050505050610334565b80806020019051612f4e9190810190613a5c565b600081518110612f5a57fe5b6020026020010151955050505050509392505050565b600080600085806020019051612f8991908101906136bd565b91509150600085806020019051612fa391908101906136a1565b905060006060307f9f76ad3500000000000000000000000000000000000000000000000000000000858786612fd78c613089565b604051602401612e599493929190614156565b73f164fc0ec4e93095b804a4795bbe1e041497b92a90565b600081601401835110156130285761302861302360048551856014016132cf565b613374565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006103318361307d61306182600163ffffffff61337c16565b613071888763ffffffff61339b16565b9063ffffffff6133cc16565b9063ffffffff6133e816565b6040805160018082528183019092526060916020808301908038833901905050905081816000815181106130b957fe5b602002602001018181525050919050565b6000806130d5613294565b73ffffffffffffffffffffffffffffffffffffffff1663b78b842d6040518163ffffffff1660e01b815260040160206040518083038186803b15801561311a57600080fd5b505afa15801561312e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061315291908101906136a1565b73ffffffffffffffffffffffffffffffffffffffff1663c3a2a93a6040518163ffffffff1660e01b815260040160006040518083038186803b15801561319757600080fd5b505afa1580156131ab573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526131f19190810190613899565b5091955093505050509091565b6060825160010160405190808252806020026020018201604052801561322e578160200160208202803883390190505b50905060005b83518110156132705783818151811061324957fe5b602002602001015182828151811061325d57fe5b6020908102919091010152600101613234565b50818160018351038151811061328257fe5b60200260200101818152505092915050565b739aab3f75489902f3a48495025729a0af77d4b11e90565b60006123a882613412565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9590565b6060632800659560e01b8484846040516024016132ee93929190614393565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915290509392505050565b805160208201fd5b60008282111561339557613395613023600285856134e3565b50900390565b6000826133aa575060006123a8565b828202828482816133b757fe5b041461033457610334613023600186866134e3565b60008282018381101561033457610334613023600086866134e3565b6000816133fe576133fe613023600385856134e3565b600082848161340957fe5b04949350505050565b600060129050600060608373ffffffffffffffffffffffffffffffffffffffff166040518060400160405280600481526020017f313ce5670000000000000000000000000000000000000000000000000000000081525060405161347691906140b9565b600060405180830381855afa9150503d80600081146134b1576040519150601f19603f3d011682016040523d82523d6000602084013e6134b6565b606091505b50915091508180156134c9575080516020145b156134dc576134d9816000613502565b92505b5050919050565b606063e946c1bb60e01b8484846040516024016132ee93929190614372565b600061033483836000816020018351101561352a5761352a61302360058551856020016132cf565b50016020015190565b6040805160608101909152806000815260006020820181905260409091015290565b604080516060810182526000808252602082018190529181019190915290565b80356123a8816146e2565b600082601f830112613590578081fd5b81356135a361359e82614643565b61461c565b8181529150602080830190840160005b838110156135e0576135cb8760208435890101613653565b835260209283019291909101906001016135b3565b5050505092915050565b600082601f8301126135fa578081fd5b813561360861359e82614643565b81815291506020808301908481018184028601820187101561362957600080fd5b60005b848110156136485781358452928201929082019060010161362c565b505050505092915050565b600082601f830112613663578081fd5b813561367161359e82614663565b915080825283602082850101111561368857600080fd5b8060208401602084013760009082016020015292915050565b6000602082840312156136b2578081fd5b8151610334816146e2565b600080604083850312156136cf578081fd5b82516136da816146e2565b60208401519092506136eb816146e2565b809150509250929050565b60008060006060848603121561370a578081fd5b8335613715816146e2565b92506020840135613725816146e2565b91506040840135613735816146e2565b809150509250925092565b600080600080600060a08688031215613757578283fd5b8535613762816146e2565b94506020860135613772816146e2565b93506040860135613782816146e2565b92506060860135613792816146e2565b9150608086013567ffffffffffffffff8111156137ad578182fd5b6137b9888289016135ea565b9150509295509295909350565b600080600080608085870312156137db578182fd5b84356137e6816146e2565b935060208501356137f6816146e2565b92506040850135613806816146e2565b9150606085013567ffffffffffffffff811115613821578182fd5b61382d878288016135ea565b91505092959194509250565b60008060006060848603121561384d578081fd5b8335613858816146e2565b92506020840135613868816146e2565b9150604084013567ffffffffffffffff811115613883578182fd5b61388f868287016135ea565b9150509250925092565b60008060008060008060c087890312156138b1578384fd5b86516138bc816146e2565b809650506020808801516138cf816146e2565b60408901519096506138e0816146e2565b60608901519095506138f1816146e2565b6080890151909450613902816146e2565b60a089015190935067ffffffffffffffff81111561391e578283fd5b8089018a601f82011261392f578384fd5b8051915061393f61359e83614643565b82815283810190828501858502840186018e101561395b578687fd5b8693505b84841015613986578051613972816146e2565b83526001939093019291850191850161395f565b5080955050505050509295509295509295565b600080604083850312156139ab578182fd5b823567ffffffffffffffff808211156139c2578384fd5b81850186601f8201126139d3578485fd5b803592506139e361359e84614643565b80848252602080830192508084018a828389028701011115613a03578889fd5b8894505b86851015613a2e578035613a1a816146e2565b845260019490940193928101928101613a07565b509096508701359350505080821115613a45578283fd5b50613a52858286016135ea565b9150509250929050565b60006020808385031215613a6e578182fd5b825167ffffffffffffffff811115613a84578283fd5b80840185601f820112613a95578384fd5b80519150613aa561359e83614643565b8281528381019082850185850284018601891015613ac1578687fd5b8693505b84841015613ae3578051835260019390930192918501918501613ac5565b50979650505050505050565b60008060208385031215613b01578182fd5b823567ffffffffffffffff80821115613b18578384fd5b81850186601f820112613b29578485fd5b8035925081831115613b39578485fd5b8660208085028301011115613b4c578485fd5b60200196919550909350505050565b600080600060608486031215613b6f578081fd5b67ffffffffffffffff8085351115613b85578182fd5b8435850186601f820112613b97578283fd5b613ba461359e8235614643565b8135815260208082019190808401865b8535811015613d3157813586016101c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828f03011215613bf3578889fd5b613bfe6101c061461c565b613c0a8e868401613575565b8152613c198e60408401613575565b85820152613c2a8e60608401613575565b6040820152613c3c8e60808401613575565b606082015260a0820135608082015260c082013560a082015260e082013560c082015261010082013560e082015261012082013561010082015261014082013561012082015261016082013589811115613c94578a8bfd5b613ca28f8783860101613653565b6101408301525061018082013589811115613cbb578a8bfd5b613cc98f8783860101613653565b610160830152506101a082013589811115613ce2578a8bfd5b613cf08f8783860101613653565b610180830152506101c082013589811115613d09578a8bfd5b613d178f8783860101613653565b6101a0830152508652509382019390820190600101613bb4565b509197508801359250505081811115613d48578283fd5b613d5487828801613580565b93505050613d658560408601613575565b90509250925092565b600060208284031215613d7f578081fd5b5051919050565b600060208284031215613d97578081fd5b815167ffffffffffffffff811115613dad578182fd5b80830184601f820112613dbe578283fd5b80519150613dce61359e83614663565b828152856020848401011115613de2578384fd5b6105288360208301602085016146a5565b600060208284031215613e04578081fd5b815161033481614732565b60008060808385031215613e21578182fd5b8251613e2c81614732565b915060608385037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0011215613e5f578081fd5b613e69606061461c565b6020840151613e77816146e2565b81526040840151613e8781614704565b60208201526060840151613e9a81614704565b6040820152919491935090915050565b60008060008084860360c0811215613ec0578283fd5b6060811215613ecd578283fd5b50613ed8606061461c565b8535613ee3816146e2565b81526020860135613ef381614704565b60208201526040860135613f0681614704565b604082015293506060850135613f1b81614732565b92506080850135613f2b81614732565b915060a085013567ffffffffffffffff811115613821578182fd5b600080600083850360a0811215613f5b578182fd5b6060811215613f68578182fd5b50613f73606061461c565b845160078110613f81578283fd5b815260208581015190820152604080860151908201526060850151608086015191945092508015158114613735578182fd5b73ffffffffffffffffffffffffffffffffffffffff169052565b6000815180845260208401935060208301825b82811015613ffe578151865260209586019590910190600101613fe0565b5093949350505050565b600081518084526140208160208601602086016146a5565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b805173ffffffffffffffffffffffffffffffffffffffff1682526020808201517fffffffff000000000000000000000000000000000000000000000000000000009081169184019190915260409182015116910152565b6000828483379101908152919050565b600082516140cb8184602087016146a5565b9190910192915050565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b600073ffffffffffffffffffffffffffffffffffffffff8086168352808516602084015250606060408301526105286060830184613fcd565b73ffffffffffffffffffffffffffffffffffffffff92831681529116602082015260400190565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152808516604084015250608060608301526141976080830184613fcd565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff9485168152928416602084015292166040820152606081019190915260800190565b73ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152604081019190915260600190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260ff8416606083015260a0608083015261425160a0830184614008565b979650505050505050565b60006080820173ffffffffffffffffffffffffffffffffffffffff87168352614284866146d5565b60208381018790526080604085015285519182905285019060a0840190835b818110156142c15783518352602093840193909201916001016142a3565b505083810360608501526142d58186613fcd565b98975050505050505050565b6000602080830181845280855180835260408601915060408482028701019250838701855b82811015614352577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452614340858351614008565b94509285019290850190600101614306565b5092979650505050505050565b6000602082526103346020830184613fcd565b6060810161437f856146d5565b938152602081019290925260409091015290565b606081016008851061437f57fe5b600f93840b81529190920b6020820152604081019190915260600190565b600f83900b8152608081016103346020830184614052565b60208082526025908201527f455243323042726964676553616d706c65722f494e56414c49445f544f4b454e60408201527f5f50414952000000000000000000000000000000000000000000000000000000606082015260800190565b60006144408287614052565b84600f0b606083015283600f0b608083015260c060a083015261419760c0830184613fcd565b60006040825261447a604083018551613fb3565b602084015161448c6060840182613fb3565b50604084015161449f6080840182613fb3565b5060608401516144b260a0840182613fb3565b50608084015160c083015260a084015160e083015260c0840151610100818185015260e086015191506101208281860152818701519250610140915082828601528087015192505061016082818601528187015192506101c091506101808281870152614523610200870185614008565b8289015194507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc092506101a08388830301818901526145628287614008565b838b015196508489820301868a015261457b8188614008565b955050808a0151955050505080858303016101e08601525061459d8183614008565b84810360208601526142518187614008565b90815260200190565b60006040820184835260406020840152808451808352606085019150602086019250835b8181101561461057835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016145dc565b50909695505050505050565b60405181810167ffffffffffffffff8111828210171561463b57600080fd5b604052919050565b600067ffffffffffffffff821115614659578081fd5b5060209081020190565b600067ffffffffffffffff821115614679578081fd5b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60005b838110156146c05781810151838201526020016146a8565b838111156146cf576000848401525b50505050565b600481106146df57fe5b50565b73ffffffffffffffffffffffffffffffffffffffff811681146146df57600080fd5b7fffffffff00000000000000000000000000000000000000000000000000000000811681146146df57600080fd5b80600f0b81146146df57600080fdfea365627a7a72315820b6fbcf4008958c944ebfec299da165bd697cf68b9711b57b06ab81852a3933ff6c6578706572696d656e74616cf564736f6c63430005110040" }, "deployedBytecode": { - "object": "0x608060405234801561001057600080fd5b50600436106101365760003560e01c806368be3cf2116100b2578063abffc76111610081578063d0eea06d11610066578063d0eea06d14610288578063e68248f71461029b578063e9a8e442146102ae57610136565b8063abffc76114610255578063c7f7142e1461026857610136565b806368be3cf2146101fc5780636dd6b78d1461021c57806398cdafba1461022f5780639f76ad351461024257610136565b80634703a7e61161010957806358306ba0116100ee57806358306ba0146101c357806360ee052a146101d657806364ee6ade146101e957610136565b80634703a7e61461019d5780634cb8e253146101b057610136565b80631796fb871461013b5780632d753aa414610164578063354152a31461017757806339b085ad1461018a575b600080fd5b61014e6101493660046139e0565b6102c1565b60405161015b91906140df565b60405180910390f35b61014e610172366004613691565b610492565b61014e6101853660046139e0565b610688565b61014e610198366004613be0565b610842565b61014e6101ab36600461380f565b610aff565b61014e6101be36600461380f565b610cdb565b61014e6101d1366004613717565b610e0e565b61014e6101e436600461380f565b610ea5565b61014e6101f736600461380f565b61115a565b61020f61020a366004613b74565b611320565b60405161015b9190614061565b61014e61022a36600461380f565b611466565b61014e61023d36600461378a565b61170e565b61014e610250366004613717565b611748565b61014e610263366004613a1e565b61194c565b61027b610276366004613647565b611b37565b60405161015b9190613e5f565b61014e61029636600461386f565b611c72565b61014e6102a9366004613a1e565b611ca3565b61014e6102bc366004613be0565b611e76565b6060600082519050806040519080825280602002602001820160405280156102f3578160200160208202803883390190505b50915060005b8181101561048857600060608873ffffffffffffffffffffffffffffffffffffffff16620927c0600073ffffffffffffffffffffffffffffffffffffffff16630e71d1b9905060e01b8a8a8a888151811061035057fe5b602002602001015160405160240161036a93929190614121565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516103f39190613e43565b6000604051808303818686fa925050503d806000811461042f576040519150601f19603f3d011682016040523d82523d6000602084013e610434565b606091505b5090925090506000821561045d57818060200190516104569190810190613ca2565b9050610465565b505050610488565b8086858151811061047257fe5b60209081029190910101525050506001016102f9565b5050949350505050565b6060600082519050806040519080825280602002602001820160405280156104c4578160200160208202803883390190505b50915073ffffffffffffffffffffffffffffffffffffffff87166104e8575061067f565b60005b8181101561067c57600060608973ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff16636e79e133905060e01b8b8b8b8b898151811061054357fe5b602002602001015160405160240161055e9493929190613ee8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516105e79190613e43565b6000604051808303818686fa925050503d8060008114610623576040519150601f19603f3d011682016040523d82523d6000602084013e610628565b606091505b50909250905060008215610651578180602001905161064a9190810190613ca2565b9050610659565b50505061067c565b8086858151811061066657fe5b60209081029190910101525050506001016104eb565b50505b95945050505050565b6060600082519050806040519080825280602002602001820160405280156106ba578160200160208202803883390190505b50915060005b8181101561048857600060608873ffffffffffffffffffffffffffffffffffffffff16620927c0600073ffffffffffffffffffffffffffffffffffffffff166307211ef7905060e01b8a8a8a888151811061071757fe5b602002602001015160405160240161073193929190614121565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516107ba9190613e43565b6000604051808303818686fa925050503d80600081146107f6576040519150601f19603f3d011682016040523d82523d6000602084013e6107fb565b606091505b5090925090506000821561045d578180602001905161081d9190810190613ca2565b90508086858151811061082c57fe5b60209081029190910101525050506001016106c0565b6060835160405190808252806020026020018201604052801561086f578160200160208202803883390190505b50905060005b84518114610af75783818151811061088957fe5b602002602001015151600014806108b757508481815181106108a757fe5b6020026020010151608001516000145b806108d957508481815181106108c957fe5b602002602001015160a001516000145b156108fd5760008282815181106108ec57fe5b602002602001018181525050610aef565b600060608473ffffffffffffffffffffffffffffffffffffffff166207a1208673ffffffffffffffffffffffffffffffffffffffff1663e77286eb905060e01b89868151811061094957fe5b602002602001015189878151811061095d57fe5b602002602001015160405160240161097692919061419c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516109ff9190613e43565b6000604051808303818686fa925050503d8060008114610a3b576040519150601f19603f3d011682016040523d82523d6000602084013e610a40565b606091505b509150915081610a6b576000848481518110610a5857fe5b6020026020010181815250505050610aef565b610a73613340565b60008083806020019051610a8a9190810190613d27565b91945092509050600383516006811115610aa057fe5b141580610aab575080155b15610acf576000878781518110610abe57fe5b602002602001018181525050610ae9565b81878781518110610adc57fe5b6020026020010181815250505b50505050505b600101610875565b509392505050565b6060610b0b8385611f19565b8151604080518281526020808402820101909152818015610b36578160200160208202803883390190505b50915060005b81811015610cd25760006060610b50611f8c565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663ff1fd974905060e01b8a8a8a8881518110610b9a57fe5b6020026020010151604051602401610bb493929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610c3d9190613e43565b6000604051808303818686fa925050503d8060008114610c79576040519150601f19603f3d011682016040523d82523d6000602084013e610c7e565b606091505b50909250905060008215610ca75781806020019051610ca09190810190613ca2565b9050610caf565b505050610cd2565b80868581518110610cbc57fe5b6020908102919091010152505050600101610b3c565b50509392505050565b6060610ce78385611f19565b8151604080518281526020808402820101909152818015610d12578160200160208202803883390190505b5091506000610d1f611fa4565b90506000805b83811015610e03578273ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161480610d9257508273ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16145b15610dbc57610db58888888481518110610da857fe5b6020026020010151611fbc565b9150610de3565b610dcd8884888481518110610da857fe5b91508115610de357610de0838884611fbc565b91505b81858281518110610df057fe5b6020908102919091010152600101610d25565b505050509392505050565b60608273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610e7557508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16145b15610e7f57610e9d565b6060610e8c86858561115a565b9050610e9984868361115a565b9150505b949350505050565b6060610eb18385611f19565b8151604080518281526020808402820101909152818015610edc578160200160208202803883390190505b5091506000610ee9611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f2957610f24866127dc565b610f2c565b60005b90506000610f38611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f7857610f73866127dc565b610f7b565b60005b905060005b83811015610e03576001610f92611fa4565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156110295786516110089085907f2640f62c00000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b6020026020010151612874565b87848151811061101457fe5b60200260200101819350828152505050611146565b611031611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16141561109a5786516110089084907f59e9486200000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b86516000906110d39085907f59e9486200000000000000000000000000000000000000000000000000000000908b9087908110610ffb57fe5b92509050801561112957611108857f2640f62c0000000000000000000000000000000000000000000000000000000083612874565b88858151811061111457fe5b60200260200101819450828152505050611144565b600087848151811061113757fe5b6020026020010181815250505b505b806111515750610e03565b50600101610f80565b60606111668385611f19565b8151604080518281526020808402820101909152818015611191578160200160208202803883390190505b50915060005b81811015610cd257600060606111ab611f8c565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663144a2752905060e01b898b8a88815181106111f557fe5b602002602001015160405160240161120f93929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516112989190613e43565b6000604051808303818686fa925050503d80600081146112d4576040519150601f19603f3d011682016040523d82523d6000602084013e6112d9565b606091505b50909250905060008215610ca757818060200190516112fb9190810190613ca2565b90508086858151811061130a57fe5b6020908102919091010152505050600101611197565b60408051828152602080840282010190915260609082801561135657816020015b60608152602001906001900390816113415790505b50905060005b80831461145f57600060603086868581811061137457fe5b6020028201905080357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1368490030181126113ae57600080fd5b9091016020810191503567ffffffffffffffff8111156113cd57600080fd5b368190038213156113dd57600080fd5b6040516113eb929190613e33565b600060405180830381855afa9150503d8060008114611426576040519150601f19603f3d011682016040523d82523d6000602084013e61142b565b606091505b50915091508161143d57805160208201fd5b8084848151811061144a57fe5b6020908102919091010152505060010161135c565b5092915050565b60606114728385611f19565b815160408051828152602080840282010190915281801561149d578160200160208202803883390190505b50915060006114aa611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146114ea576114e5866127dc565b6114ed565b60005b905060006114f9611fa4565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161461153957611534866127dc565b61153c565b60005b905060005b83811015610e03576001611553611fa4565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156115dd5786516115bc9085907f95b68fe700000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b8784815181106115c857fe5b602002602001018193508281525050506116fa565b6115e5611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16141561164e5786516115bc9084907fcd7724c300000000000000000000000000000000000000000000000000000000908a9086908110610ffb57fe5b86516000906116879086907f95b68fe700000000000000000000000000000000000000000000000000000000908b9087908110610ffb57fe5b9250905080156116dd576116bc847fcd7724c30000000000000000000000000000000000000000000000000000000083612874565b8885815181106116c857fe5b602002602001018194508281525050506116f8565b60008784815181106116eb57fe5b6020026020010181815250505b505b806117055750610e03565b50600101611541565b606061173e858585857f9f76ad35000000000000000000000000000000000000000000000000000000008b6129bd565b9695505050505050565b60606000825190508060405190808252806020026020018201604052801561177a578160200160208202803883390190505b509150600061178a878787611b37565b905073ffffffffffffffffffffffffffffffffffffffff81166117af5750610e9d9050565b60005b8281101561194157600060608373ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff1663343fbcdd905060e01b8b8b8b888151811061180957fe5b602002602001015160405160240161182393929190613f58565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516118ac9190613e43565b6000604051808303818686fa925050503d80600081146118e8576040519150601f19603f3d011682016040523d82523d6000602084013e6118ed565b606091505b50909250905060008215611916578180602001905161190f9190810190613ca2565b905061191e565b505050611941565b8087858151811061192b57fe5b60209081029190910101525050506001016117b2565b505050949350505050565b60606000825190508060405190808252806020026020018201604052801561197e578160200160208202803883390190505b50915060005b81811015611b2f5760006060611998612b83565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff1663d06ca61f905060e01b8886815181106119e057fe5b60200260200101518a6040516024016119fa9291906142ee565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611a839190613e43565b6000604051808303818686fa925050503d8060008114611abf576040519150601f19603f3d011682016040523d82523d6000602084013e611ac4565b606091505b50909250905060008215611b045781806020019051611ae69190810190613ae1565b600189510381518110611af557fe5b60200260200101519050611b0c565b505050611b2f565b80868581518110611b1957fe5b6020908102919091010152505050600101611984565b505092915050565b6040516000906060907f153f59970000000000000000000000000000000000000000000000000000000090611b729086908690602401613e80565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050600060608673ffffffffffffffffffffffffffffffffffffffff1683604051611bfa9190613e43565b600060405180830381855afa9150503d8060008114611c35576040519150601f19603f3d011682016040523d82523d6000602084013e611c3a565b606091505b5091509150818015611c4d575080516020145b15611c6757611c5d81600c612b9b565b9350505050611c6b565b5050505b9392505050565b606061067f858585857f4cb8e2530000000000000000000000000000000000000000000000000000000060006129bd565b606060008251905080604051908082528060200260200182016040528015611cd5578160200160208202803883390190505b50915060005b81811015611b2f5760006060611cef612b83565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff16631f00ca74905060e01b888681518110611d3757fe5b60200260200101518a604051602401611d519291906142ee565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611dda9190613e43565b6000604051808303818686fa925050503d8060008114611e16576040519150601f19603f3d011682016040523d82523d6000602084013e611e1b565b606091505b50909250905060008215611b045781806020019051611e3d9190810190613ae1565b600081518110611e4957fe5b6020026020010151905080868581518110611e6057fe5b6020908102919091010152505050600101611cdb565b6060611e83848484610842565b905060005b8451811015610af757818181518110611e9d57fe5b6020026020010151600014611f1157611ef8828281518110611ebb57fe5b6020026020010151868381518110611ecf57fe5b602002602001015160a00151878481518110611ee757fe5b602002602001015160800151612be0565b828281518110611f0457fe5b6020026020010181815250505b600101611e88565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611f88576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f7f9061413f565b60405180910390fd5b5050565b73794e6e91555438afc3ccf1c5076a74f42133d08d90565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290565b6000806000611fc9612c22565b9150915060608173ffffffffffffffffffffffffffffffffffffffff16633d3dc52c611ff3611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161461202b578861202d565b875b6040518263ffffffff1660e01b81526004016120499190613e5f565b60006040518083038186803b15801561206157600080fd5b505afa158015612075573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526120bb9190810190613ae1565b604080516000808252602082019092529192505b8251811015612374576040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061213e907331e085afd48a1d6e51cc193153d625e8f0514c7f90600401613e5f565b60206040518083038186803b15801561215657600080fd5b505afa15801561216a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061218e9190810190613ca2565b83828151811061219a57fe5b6020026020010151148061227557506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061220f907310908c875d865c66f271f5d3949848971c9595c990600401613e5f565b60206040518083038186803b15801561222757600080fd5b505afa15801561223b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061225f9190810190613ca2565b83828151811061226b57fe5b6020026020010151145b8061234757506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b906122e190731e158c0e93c30d24e918ef83d1e0be23595c3c0f90600401613e5f565b60206040518083038186803b1580156122f957600080fd5b505afa15801561230d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506123319190810190613ca2565b83828151811061233d57fe5b6020026020010151145b1561236c576123698284838151811061235c57fe5b6020026020010151612d56565b91505b6001016120cf565b50606061237f611fa4565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161415612496578473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8960028560006040519080825280602002602001820160405280156123fd578160200160208202803883390190505b506040518563ffffffff1660e01b815260040161241d9493929190613fdc565b60006040518083038186803b15801561243557600080fd5b505afa158015612449573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261248f9190810190613cba565b9050612576565b8473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8a60028560006040519080825280602002602001820160405280156124e1578160200160208202803883390190505b506040518563ffffffff1660e01b81526004016125019493929190613fdc565b60006040518083038186803b15801561251957600080fd5b505afa15801561252d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526125739190810190613cba565b90505b60006060612582612dec565b73ffffffffffffffffffffffffffffffffffffffff166216e3607f418436bc000000000000000000000000000000000000000000000000000000006125c5611fa4565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff16146125fd578d612613565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b61261b611fa4565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612653578d612669565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b8d600089604051602401612681959493929190613f89565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161270a9190613e43565b6000604051808303818686fa925050503d8060008114612746576040519150601f19603f3d011682016040523d82523d6000602084013e61274b565b606091505b50909250905060008215612774578180602001905161276d9190810190613ca2565b9050612785565b600098505050505050505050611c6b565b60006127908c612e04565b60ff16905060006127a08e612e04565b60ff169050670de0b6b3a764000081600a0a83600a0a8e860202816127c157fe5b04816127c957fe5b049e9d5050505050505050505050505050565b60006127e6612e0f565b73ffffffffffffffffffffffffffffffffffffffff166306f2bf62836040518263ffffffff1660e01b815260040161281e9190613e5f565b60206040518083038186803b15801561283657600080fd5b505afa15801561284a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061286e919081019061362b565b92915050565b60008073ffffffffffffffffffffffffffffffffffffffff8516612897576129b5565b60608573ffffffffffffffffffffffffffffffffffffffff16620249f086866040516024016128c691906142e5565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161294f9190613e43565b6000604051808303818686fa925050503d806000811461298b576040519150601f19603f3d011682016040523d82523d6000602084013e612990565b606091505b50909250905081156129b357808060200190516129b09190810190613ca2565b92505b505b935093915050565b60606129c98688611f19565b84516129d45761173e565b60008060008751604051908082528060200260200182016040528015612a04578160200160208202803883390190505b509350612a28898b8a600081518110612a1957fe5b60200260200101518989612e27565b925082612a38575061173e915050565b612a458a8a858989612e27565b915081612a55575061173e915050565b60005b8851811015612b755760005b8860200151811015612b3757612a8e8a8381518110612a7f57fe5b60200260200101518587612be0565b9450612aa589600001516127100161271087612be0565b94506000612ab68d8d888c8c612e27565b905080612ac35750612b37565b8094508a8381518110612ad257fe5b60200260200101518510612b2e5760008b8481518110612aee57fe5b60200260200101518c8581518110612b0257fe5b602002602001015187036127100281612b1757fe5b0490508a600001518111612b2c575050612b37565b505b50600101612a64565b50612b56898281518110612b4757fe5b60200260200101518486612be0565b858281518110612b6257fe5b6020908102919091010152600101612a58565b505050509695505050505050565b73f164fc0ec4e93095b804a4795bbe1e041497b92a90565b60008160140183511015612bc157612bc1612bbc60048551856014016130dc565b613181565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b6000610e9d83612c16612bfa82600163ffffffff61318916565b612c0a888763ffffffff6131a816565b9063ffffffff6131d916565b9063ffffffff6131f516565b600080612c2d612dec565b73ffffffffffffffffffffffffffffffffffffffff1663b78b842d6040518163ffffffff1660e01b815260040160206040518083038186803b158015612c7257600080fd5b505afa158015612c86573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612caa919081019061362b565b73ffffffffffffffffffffffffffffffffffffffff1663c3a2a93a6040518163ffffffff1660e01b815260040160006040518083038186803b158015612cef57600080fd5b505afa158015612d03573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052612d4991908101906138e1565b5091955093505050509091565b60608251600101604051908082528060200260200182016040528015612d86578160200160208202803883390190505b50905060005b8351811015612dc857838181518110612da157fe5b6020026020010151828281518110612db557fe5b6020908102919091010152600101612d8c565b508181600183510381518110612dda57fe5b60200260200101818152505092915050565b739aab3f75489902f3a48495025729a0af77d4b11e90565b600061286e8261321f565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9590565b6040805160018082528183019092526000916060918291602080830190803883390190505090508581600081518110612e5c57fe5b60209081029190910101527fffffffff0000000000000000000000000000000000000000000000000000000085167f4cb8e253000000000000000000000000000000000000000000000000000000001415612f6c576040517f4cb8e2530000000000000000000000000000000000000000000000000000000090612ee8908a908a908590602401613f1f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529150613025565b6040517f9f76ad350000000000000000000000000000000000000000000000000000000090612fa59086908b908b908690602401613ea7565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915291505b600060603073ffffffffffffffffffffffffffffffffffffffff168460405161304e9190613e43565b600060405180830381855afa9150503d8060008114613089576040519150601f19603f3d011682016040523d82523d6000602084013e61308e565b606091505b5091509150816130a557600094505050505061067f565b808060200190516130b99190810190613ae1565b6000815181106130c557fe5b602002602001015194505050505095945050505050565b6060632800659560e01b8484846040516024016130fb93929190614113565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915290509392505050565b805160208201fd5b6000828211156131a2576131a2612bbc600285856132f0565b50900390565b6000826131b75750600061286e565b828202828482816131c457fe5b0414611c6b57611c6b612bbc600186866132f0565b600082820183811015611c6b57611c6b612bbc600086866132f0565b60008161320b5761320b612bbc600385856132f0565b600082848161321657fe5b04949350505050565b600060129050600060608373ffffffffffffffffffffffffffffffffffffffff166040518060400160405280600481526020017f313ce567000000000000000000000000000000000000000000000000000000008152506040516132839190613e43565b600060405180830381855afa9150503d80600081146132be576040519150601f19603f3d011682016040523d82523d6000602084013e6132c3565b606091505b50915091508180156132d6575080516020145b156132e9576132e681600061330f565b92505b5050919050565b606063e946c1bb60e01b8484846040516024016130fb939291906140f2565b6000611c6b83836000816020018351101561333757613337612bbc60058551856020016130dc565b50016020015190565b6040805160608101909152806000815260006020820181905260409091015290565b803561286e81614418565b805161286e81614418565b600082601f830112613388578081fd5b813561339b61339682614379565b614352565b8181529150602080830190840160005b838110156133d8576133c3876020843589010161344b565b835260209283019291909101906001016133ab565b5050505092915050565b600082601f8301126133f2578081fd5b813561340061339682614379565b81815291506020808301908481018184028601820187101561342157600080fd5b60005b8481101561344057813584529282019290820190600101613424565b505050505092915050565b600082601f83011261345b578081fd5b813561346961339682614399565b915080825283602082850101111561348057600080fd5b8060208401602084013760009082016020015292915050565b8035600f81900b811461286e57600080fd5b6000604082840312156134bc578081fd5b6134c66040614352565b9050813581526020820135602082015292915050565b60006101c08083850312156134ef578182fd5b6134f881614352565b9150506135058383613362565b81526135148360208401613362565b60208201526135268360408401613362565b60408201526135388360608401613362565b60608201526080820135608082015260a082013560a082015260c082013560c082015260e082013560e08201526101008083013581830152506101208083013581830152506101408083013567ffffffffffffffff8082111561359a57600080fd5b6135a68683870161344b565b838501526101609250828501359150808211156135c257600080fd5b6135ce8683870161344b565b838501526101809250828501359150808211156135ea57600080fd5b6135f68683870161344b565b838501526101a092508285013591508082111561361257600080fd5b5061361f8582860161344b565b82840152505092915050565b60006020828403121561363c578081fd5b8151611c6b81614418565b60008060006060848603121561365b578182fd5b833561366681614418565b9250602084013561367681614418565b9150604084013561368681614418565b809150509250925092565b600080600080600060a086880312156136a8578283fd5b85356136b381614418565b945060208601356136c381614418565b935060408601356136d381614418565b925060608601356136e381614418565b9150608086013567ffffffffffffffff8111156136fe578182fd5b61370a888289016133e2565b9150509295509295909350565b6000806000806080858703121561372c578182fd5b843561373781614418565b9350602085013561374781614418565b9250604085013561375781614418565b9150606085013567ffffffffffffffff811115613772578182fd5b61377e878288016133e2565b91505092959194509250565b600080600080600060c086880312156137a1578283fd5b85356137ac81614418565b945060208601356137bc81614418565b935060408601356137cc81614418565b9250606086013567ffffffffffffffff8111156137e7578182fd5b6137f3888289016133e2565b92505061380387608088016134ab565b90509295509295909350565b600080600060608486031215613823578081fd5b833561382e81614418565b9250602084013561383e81614418565b9150604084013567ffffffffffffffff811115613859578182fd5b613865868287016133e2565b9150509250925092565b60008060008060a08587031215613884578182fd5b843561388f81614418565b9350602085013561389f81614418565b9250604085013567ffffffffffffffff8111156138ba578283fd5b6138c6878288016133e2565b9250506138d686606087016134ab565b905092959194509250565b60008060008060008060c087890312156138f9578384fd5b865161390481614418565b8096505060208088015161391781614418565b604089015190965061392881614418565b606089015190955061393981614418565b608089015190945061394a81614418565b60a089015190935067ffffffffffffffff811115613966578283fd5b8089018a601f820112613977578384fd5b8051915061398761339683614379565b82815283810190828501858502840186018e10156139a3578687fd5b8693505b848410156139cd576139b98e8261336d565b8352600193909301929185019185016139a7565b5080955050505050509295509295509295565b600080600080608085870312156139f5578182fd5b8435613a0081614418565b9350613a0f8660208701613499565b92506137578660408701613499565b60008060408385031215613a30578182fd5b823567ffffffffffffffff80821115613a47578384fd5b81850186601f820112613a58578485fd5b80359250613a6861339684614379565b80848252602080830192508084018a828389028701011115613a88578889fd5b8894505b86851015613ab3578035613a9f81614418565b845260019490940193928101928101613a8c565b509096508701359350505080821115613aca578283fd5b50613ad7858286016133e2565b9150509250929050565b60006020808385031215613af3578182fd5b825167ffffffffffffffff811115613b09578283fd5b80840185601f820112613b1a578384fd5b80519150613b2a61339683614379565b8281528381019082850185850284018601891015613b46578687fd5b8693505b84841015613b68578051835260019390930192918501918501613b4a565b50979650505050505050565b60008060208385031215613b86578182fd5b823567ffffffffffffffff80821115613b9d578384fd5b81850186601f820112613bae578485fd5b8035925081831115613bbe578485fd5b8660208085028301011115613bd1578485fd5b60200196919550909350505050565b600080600060608486031215613bf4578081fd5b833567ffffffffffffffff80821115613c0b578283fd5b81860187601f820112613c1c578384fd5b80359250613c2c61339684614379565b83815260208082019190838101875b87811015613c6457613c528d8484358901016134dc565b85529382019390820190600101613c3b565b50919850890135945050505080821115613c7c578283fd5b50613c8986828701613378565b925050613c998560408601613362565b90509250925092565b600060208284031215613cb3578081fd5b5051919050565b600060208284031215613ccb578081fd5b815167ffffffffffffffff811115613ce1578182fd5b80830184601f820112613cf2578283fd5b80519150613d0261339683614399565b828152856020848401011115613d16578384fd5b61067f8360208301602085016143db565b600080600083850360a0811215613d3c578182fd5b6060811215613d49578182fd5b50613d546060614352565b845160078110613d62578283fd5b815260208581015190820152604080860151908201526060850151608086015191945092508015158114613686578182fd5b73ffffffffffffffffffffffffffffffffffffffff169052565b6000815180845260208401935060208301825b82811015613ddf578151865260209586019590910190600101613dc1565b5093949350505050565b60008151808452613e018160208601602086016143db565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6000828483379101908152919050565b60008251613e558184602087016143db565b9190910192915050565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b73ffffffffffffffffffffffffffffffffffffffff92831681529116602082015260400190565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401528085166040840152506080606083015261173e6080830184613dae565b73ffffffffffffffffffffffffffffffffffffffff9485168152928416602084015292166040820152606081019190915260800190565b600073ffffffffffffffffffffffffffffffffffffffff80861683528085166020840152506060604083015261067f6060830184613dae565b73ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152604081019190915260600190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260ff8416606083015260a06080830152613fd160a0830184613de9565b979650505050505050565b60006080820173ffffffffffffffffffffffffffffffffffffffff871683526140048661440b565b60208381018790526080604085015285519182905285019060a0840190835b81811015614041578351835260209384019390920191600101614023565b505083810360608501526140558186613dae565b98975050505050505050565b6000602080830181845280855180835260408601915060408482028701019250838701855b828110156140d2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526140c0858351613de9565b94509285019290850190600101614086565b5092979650505050505050565b600060208252611c6b6020830184613dae565b606081016140ff8561440b565b938152602081019290925260409091015290565b60608101600885106140ff57fe5b600f93840b81529190920b6020820152604081019190915260600190565b60208082526025908201527f455243323042726964676553616d706c65722f494e56414c49445f544f4b454e60408201527f5f50414952000000000000000000000000000000000000000000000000000000606082015260800190565b6000604082526141b0604083018551613d94565b60208401516141c26060840182613d94565b5060408401516141d56080840182613d94565b5060608401516141e860a0840182613d94565b50608084015160c083015260a084015160e083015260c0840151610100818185015260e086015191506101208281860152818701519250610140915082828601528087015192505061016082818601528187015192506101c091506101808281870152614259610200870185613de9565b8289015194507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc092506101a08388830301818901526142988287613de9565b838b015196508489820301868a01526142b18188613de9565b955050808a0151955050505080858303016101e0860152506142d38183613de9565b8481036020860152613fd18187613de9565b90815260200190565b60006040820184835260406020840152808451808352606085019150602086019250835b8181101561434657835173ffffffffffffffffffffffffffffffffffffffff16835260209384019390920191600101614312565b50909695505050505050565b60405181810167ffffffffffffffff8111828210171561437157600080fd5b604052919050565b600067ffffffffffffffff82111561438f578081fd5b5060209081020190565b600067ffffffffffffffff8211156143af578081fd5b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60005b838110156143f65781810151838201526020016143de565b83811115614405576000848401525b50505050565b6004811061441557fe5b50565b73ffffffffffffffffffffffffffffffffffffffff8116811461441557600080fdfea365627a7a7231582067c5cc763deb33dec3babe92591793d68b6f9441aa149a37c465890b70b5bd1e6c6578706572696d656e74616cf564736f6c63430005110040" + "object": "0x608060405234801561001057600080fd5b50600436106101365760003560e01c806368be3cf2116100b2578063a2c28d4b11610081578063c7f7142e11610066578063c7f7142e1461027b578063e68248f71461029b578063e9a8e442146102ae57610136565b8063a2c28d4b14610255578063abffc7611461026857610136565b806368be3cf2146101fc5780636dd6b78d1461021c5780639209483b1461022f5780639f76ad351461024257610136565b80634703a7e61161010957806358306ba0116100ee57806358306ba0146101c357806360ee052a146101d657806364ee6ade146101e957610136565b80634703a7e61461019d5780634cb8e253146101b057610136565b80630cc6600b1461013b5780632d753aa41461016457806339b085ad1461017757806340bc03ae1461018a575b600080fd5b61014e610149366004613839565b6102c1565b60405161015b919061435f565b60405180910390f35b61014e610172366004613740565b61033b565b61014e610185366004613b5b565b610531565b61014e610198366004613eaa565b6107ee565b61014e6101ab366004613839565b6109a6565b61014e6101be366004613839565b610b82565b61014e6101d13660046137c6565b610cb5565b61014e6101e4366004613839565b610d4c565b61014e6101f7366004613839565b611001565b61020f61020a366004613aef565b6111c7565b60405161015b91906142e1565b61014e61022a366004613839565b61130d565b61014e61023d366004613eaa565b6115b5565b61014e6102503660046137c6565b6117e9565b61014e6102633660046137c6565b6119ed565b61014e610276366004613999565b611a53565b61028e6102893660046136f6565b611c3e565b60405161015b91906140d5565b61014e6102a9366004613999565b611d78565b61014e6102bc366004613b5b565b611f4b565b60606102cd8385611fee565b6103316040518060600160405280856040516020016102ec91906140d5565b60405160208183030381529060405281526020018660405160200161031191906140d5565b6040516020818303038152906040528152602001612061815250836121d9565b90505b9392505050565b60606000825190508060405190808252806020026020018201604052801561036d578160200160208202803883390190505b50915073ffffffffffffffffffffffffffffffffffffffff87166103915750610528565b60005b8181101561052557600060608973ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff16636e79e133905060e01b8b8b8b8b89815181106103ec57fe5b602002602001015160405160240161040794939291906141a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161049091906140b9565b6000604051808303818686fa925050503d80600081146104cc576040519150601f19603f3d011682016040523d82523d6000602084013e6104d1565b606091505b509092509050600082156104fa57818060200190516104f39190810190613d6e565b9050610502565b505050610525565b8086858151811061050f57fe5b6020908102919091010152505050600101610394565b50505b95945050505050565b6060835160405190808252806020026020018201604052801561055e578160200160208202803883390190505b50905060005b845181146107e65783818151811061057857fe5b602002602001015151600014806105a6575084818151811061059657fe5b6020026020010151608001516000145b806105c857508481815181106105b857fe5b602002602001015160a001516000145b156105ec5760008282815181106105db57fe5b6020026020010181815250506107de565b600060608473ffffffffffffffffffffffffffffffffffffffff166207a1208673ffffffffffffffffffffffffffffffffffffffff1663e77286eb905060e01b89868151811061063857fe5b602002602001015189878151811061064c57fe5b6020026020010151604051602401610665929190614466565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516106ee91906140b9565b6000604051808303818686fa925050503d806000811461072a576040519150601f19603f3d011682016040523d82523d6000602084013e61072f565b606091505b50915091508161075a57600084848151811061074757fe5b60200260200101818152505050506107de565b610762613533565b600080838060200190516107799190810190613f46565b9194509250905060038351600681111561078f57fe5b14158061079a575080155b156107be5760008787815181106107ad57fe5b6020026020010181815250506107d8565b818787815181106107cb57fe5b6020026020010181815250505b50505050505b600101610564565b509392505050565b606060008251905080604051908082528060200260200182016040528015610820578160200160208202803883390190505b50915060005b8181101561099c5760006060886000015173ffffffffffffffffffffffffffffffffffffffff16620927c08a602001518a8a8a888151811061086457fe5b602002602001015160405160240161087e939291906143a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161090791906140b9565b6000604051808303818686fa925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b50909250905060008215610971578180602001905161096a9190810190613d6e565b9050610979565b50505061099c565b8086858151811061098657fe5b6020908102919091010152505050600101610826565b5050949350505050565b60606109b28385611fee565b81516040805182815260208084028201019091528180156109dd578160200160208202803883390190505b50915060005b81811015610b7957600060606109f76123ae565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663ff1fd974905060e01b8a8a8a8881518110610a4157fe5b6020026020010151604051602401610a5b939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610ae491906140b9565b6000604051808303818686fa925050503d8060008114610b20576040519150601f19603f3d011682016040523d82523d6000602084013e610b25565b606091505b50909250905060008215610b4e5781806020019051610b479190810190613d6e565b9050610b56565b505050610b79565b80868581518110610b6357fe5b60209081029190910101525050506001016109e3565b50509392505050565b6060610b8e8385611fee565b8151604080518281526020808402820101909152818015610bb9578160200160208202803883390190505b5091506000610bc66123c6565b90506000805b83811015610caa578273ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161480610c3957508273ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16145b15610c6357610c5c8888888481518110610c4f57fe5b60200260200101516123de565b9150610c8a565b610c748884888481518110610c4f57fe5b91508115610c8a57610c878388846123de565b91505b81858281518110610c9757fe5b6020908102919091010152600101610bcc565b505050509392505050565b60608273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610d1c57508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16145b15610d2657610d44565b6060610d33868585611001565b9050610d40848683611001565b9150505b949350505050565b6060610d588385611fee565b8151604080518281526020808402820101909152818015610d83578160200160208202803883390190505b5091506000610d906123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610dd057610dcb86612bfe565b610dd3565b60005b90506000610ddf6123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610e1f57610e1a86612bfe565b610e22565b60005b905060005b83811015610caa576001610e396123c6565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161415610ed0578651610eaf9085907f2640f62c00000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b6020026020010151612c90565b878481518110610ebb57fe5b60200260200101819350828152505050610fed565b610ed86123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161415610f41578651610eaf9084907f59e9486200000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b8651600090610f7a9085907f59e9486200000000000000000000000000000000000000000000000000000000908b9087908110610ea257fe5b925090508015610fd057610faf857f2640f62c0000000000000000000000000000000000000000000000000000000083612c90565b888581518110610fbb57fe5b60200260200101819450828152505050610feb565b6000878481518110610fde57fe5b6020026020010181815250505b505b80610ff85750610caa565b50600101610e27565b606061100d8385611fee565b8151604080518281526020808402820101909152818015611038578160200160208202803883390190505b50915060005b81811015610b7957600060606110526123ae565b73ffffffffffffffffffffffffffffffffffffffff16620f4240600073ffffffffffffffffffffffffffffffffffffffff1663144a2752905060e01b898b8a888151811061109c57fe5b60200260200101516040516024016110b6939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161113f91906140b9565b6000604051808303818686fa925050503d806000811461117b576040519150601f19603f3d011682016040523d82523d6000602084013e611180565b606091505b50909250905060008215610b4e57818060200190516111a29190810190613d6e565b9050808685815181106111b157fe5b602090810291909101015250505060010161103e565b6040805182815260208084028201019091526060908280156111fd57816020015b60608152602001906001900390816111e85790505b50905060005b80831461130657600060603086868581811061121b57fe5b6020028201905080357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe13684900301811261125557600080fd5b9091016020810191503567ffffffffffffffff81111561127457600080fd5b3681900382131561128457600080fd5b6040516112929291906140a9565b600060405180830381855afa9150503d80600081146112cd576040519150601f19603f3d011682016040523d82523d6000602084013e6112d2565b606091505b5091509150816112e457805160208201fd5b808484815181106112f157fe5b60209081029190910101525050600101611203565b5092915050565b60606113198385611fee565b8151604080518281526020808402820101909152818015611344578160200160208202803883390190505b50915060006113516123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146113915761138c86612bfe565b611394565b60005b905060006113a06123c6565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146113e0576113db86612bfe565b6113e3565b60005b905060005b83811015610caa5760016113fa6123c6565b73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614156114845786516114639085907f95b68fe700000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b87848151811061146f57fe5b602002602001018193508281525050506115a1565b61148c6123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614156114f55786516114639084907fcd7724c300000000000000000000000000000000000000000000000000000000908a9086908110610ea257fe5b865160009061152e9086907f95b68fe700000000000000000000000000000000000000000000000000000000908b9087908110610ea257fe5b92509050801561158457611563847fcd7724c30000000000000000000000000000000000000000000000000000000083612c90565b88858151811061156f57fe5b6020026020010181945082815250505061159f565b600087848151811061159257fe5b6020026020010181815250505b505b806115ac5750610caa565b506001016113e8565b60408401516060907fffffffff000000000000000000000000000000000000000000000000000000001661164f57604080516060810190915261164890806116018689608084016143bf565b604051602081830303815290604052815260200186886040516020016116289291906143bf565b6040516020818303038152906040528152602001612dd9815250836121d9565b9050610d44565b815160408051828152602080840282010190915281801561167a578160200160208202803883390190505b50915060005b8181101561099c5760006060886000015173ffffffffffffffffffffffffffffffffffffffff16620927c08a604001518a8a8a88815181106116be57fe5b60200260200101516040516024016116d8939291906143a1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161176191906140b9565b6000604051808303818686fa925050503d806000811461179d576040519150601f19603f3d011682016040523d82523d6000602084013e6117a2565b606091505b5090925090506000821561097157818060200190516117c49190810190613d6e565b9050808685815181106117d357fe5b6020908102919091010152505050600101611680565b60606000825190508060405190808252806020026020018201604052801561181b578160200160208202803883390190505b509150600061182b878787611c3e565b905073ffffffffffffffffffffffffffffffffffffffff81166118505750610d449050565b60005b828110156119e257600060608373ffffffffffffffffffffffffffffffffffffffff1662061a80600073ffffffffffffffffffffffffffffffffffffffff1663343fbcdd905060e01b8b8b8b88815181106118aa57fe5b60200260200101516040516024016118c4939291906141d8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161194d91906140b9565b6000604051808303818686fa925050503d8060008114611989576040519150601f19603f3d011682016040523d82523d6000602084013e61198e565b606091505b509092509050600082156119b757818060200190516119b09190810190613d6e565b90506119bf565b5050506119e2565b808785815181106119cc57fe5b6020908102919091010152505050600101611853565b505050949350505050565b6040805160608181019092526105289080611a0c86896080840161412f565b60405160208183030381529060405281526020018688604051602001611a3392919061412f565b6040516020818303038152906040528152602001612f70815250836121d9565b606060008251905080604051908082528060200260200182016040528015611a85578160200160208202803883390190505b50915060005b81811015611c365760006060611a9f612fea565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff1663d06ca61f905060e01b888681518110611ae757fe5b60200260200101518a604051602401611b019291906145b8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611b8a91906140b9565b6000604051808303818686fa925050503d8060008114611bc6576040519150601f19603f3d011682016040523d82523d6000602084013e611bcb565b606091505b50909250905060008215611c0b5781806020019051611bed9190810190613a5c565b600189510381518110611bfc57fe5b60200260200101519050611c13565b505050611c36565b80868581518110611c2057fe5b6020908102919091010152505050600101611a8b565b505092915050565b6040516000906060907f153f59970000000000000000000000000000000000000000000000000000000090611c79908690869060240161412f565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050600060608673ffffffffffffffffffffffffffffffffffffffff1683604051611d0191906140b9565b600060405180830381855afa9150503d8060008114611d3c576040519150601f19603f3d011682016040523d82523d6000602084013e611d41565b606091505b5091509150818015611d54575080516020145b15611d6e57611d6481600c613002565b9350505050610334565b5050509392505050565b606060008251905080604051908082528060200260200182016040528015611daa578160200160208202803883390190505b50915060005b81811015611c365760006060611dc4612fea565b73ffffffffffffffffffffffffffffffffffffffff16620249f0600073ffffffffffffffffffffffffffffffffffffffff16631f00ca74905060e01b888681518110611e0c57fe5b60200260200101518a604051602401611e269291906145b8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611eaf91906140b9565b6000604051808303818686fa925050503d8060008114611eeb576040519150601f19603f3d011682016040523d82523d6000602084013e611ef0565b606091505b50909250905060008215611c0b5781806020019051611f129190810190613a5c565b600081518110611f1e57fe5b6020026020010151905080868581518110611f3557fe5b6020908102919091010152505050600101611db0565b6060611f58848484610531565b905060005b84518110156107e657818181518110611f7257fe5b6020026020010151600014611fe657611fcd828281518110611f9057fe5b6020026020010151868381518110611fa457fe5b602002602001015160a00151878481518110611fbc57fe5b602002602001015160800151613047565b828281518110611fd957fe5b6020026020010181815250505b600101611f5d565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561205d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612054906143d7565b60405180910390fd5b5050565b60008060603073ffffffffffffffffffffffffffffffffffffffff16634cb8e25360e01b8780602001905161209991908101906136a1565b878060200190516120ad91908101906136a1565b6120b688613089565b6040516024016120c8939291906140f6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161215191906140b9565b600060405180830381855afa9150503d806000811461218c576040519150601f19603f3d011682016040523d82523d6000602084013e612191565b606091505b5091509150816121a657600092505050610334565b808060200190516121ba9190810190613a5c565b6000815181106121c657fe5b6020026020010151925050509392505050565b60608151604051908082528060200260200182016040528015612206578160200160208202803883390190505b509050815160001415612218576123a8565b6000612249846000015185602001518560008151811061223457fe5b6020026020010151876040015163ffffffff16565b90508061225657506123a8565b60006122738560200151866000015184886040015163ffffffff16565b90508061228257506123a89050565b60005b84518110156123a45760005b6005811015612366576122b88683815181106122a957fe5b60200260200101518486613047565b93506122c961271561271086613047565b935060006122e888602001518960000151878b6040015163ffffffff16565b9050806122f55750612366565b80935086838151811061230457fe5b6020026020010151841061235d57600087848151811061232057fe5b602002602001015161271089868151811061233757fe5b60200260200101518703028161234957fe5b0490506005811161235b575050612366565b505b50600101612291565b5061238585828151811061237657fe5b60200260200101518385613047565b84828151811061239157fe5b6020908102919091010152600101612285565b5050505b92915050565b73794e6e91555438afc3ccf1c5076a74f42133d08d90565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290565b60008060006123eb6130ca565b9150915060608173ffffffffffffffffffffffffffffffffffffffff16633d3dc52c6124156123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161461244d578861244f565b875b6040518263ffffffff1660e01b815260040161246b91906140d5565b60006040518083038186803b15801561248357600080fd5b505afa158015612497573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526124dd9190810190613a5c565b604080516000808252602082019092529192505b8251811015612796576040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b90612560907331e085afd48a1d6e51cc193153d625e8f0514c7f906004016140d5565b60206040518083038186803b15801561257857600080fd5b505afa15801561258c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506125b09190810190613d6e565b8382815181106125bc57fe5b6020026020010151148061269757506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b90612631907310908c875d865c66f271f5d3949848971c9595c9906004016140d5565b60206040518083038186803b15801561264957600080fd5b505afa15801561265d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506126819190810190613d6e565b83828151811061268d57fe5b6020026020010151145b8061276957506040517f106e9a4b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063106e9a4b9061270390731e158c0e93c30d24e918ef83d1e0be23595c3c0f906004016140d5565b60206040518083038186803b15801561271b57600080fd5b505afa15801561272f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506127539190810190613d6e565b83828151811061275f57fe5b6020026020010151145b1561278e5761278b8284838151811061277e57fe5b60200260200101516131fe565b91505b6001016124f1565b5060606127a16123c6565b73ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614156128b8578473ffffffffffffffffffffffffffffffffffffffff166381efcbdd89600285600060405190808252806020026020018201604052801561281f578160200160208202803883390190505b506040518563ffffffff1660e01b815260040161283f949392919061425c565b60006040518083038186803b15801561285757600080fd5b505afa15801561286b573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526128b19190810190613d86565b9050612998565b8473ffffffffffffffffffffffffffffffffffffffff166381efcbdd8a6002856000604051908082528060200260200182016040528015612903578160200160208202803883390190505b506040518563ffffffff1660e01b8152600401612923949392919061425c565b60006040518083038186803b15801561293b57600080fd5b505afa15801561294f573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526129959190810190613d86565b90505b600060606129a4613294565b73ffffffffffffffffffffffffffffffffffffffff166216e3607f418436bc000000000000000000000000000000000000000000000000000000006129e76123c6565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612a1f578d612a35565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b612a3d6123c6565b73ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612a75578d612a8b565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b8d600089604051602401612aa3959493929190614209565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612b2c91906140b9565b6000604051808303818686fa925050503d8060008114612b68576040519150601f19603f3d011682016040523d82523d6000602084013e612b6d565b606091505b50909250905060008215612b965781806020019051612b8f9190810190613d6e565b9050612ba7565b600098505050505050505050610334565b6000612bb28c6132ac565b60ff1690506000612bc28e6132ac565b60ff169050670de0b6b3a764000081600a0a83600a0a8e86020281612be357fe5b0481612beb57fe5b049e9d5050505050505050505050505050565b6000612c086132b7565b73ffffffffffffffffffffffffffffffffffffffff166306f2bf62836040518263ffffffff1660e01b8152600401612c4091906140d5565b60206040518083038186803b158015612c5857600080fd5b505afa158015612c6c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506123a891908101906136a1565b60008073ffffffffffffffffffffffffffffffffffffffff8516612cb357612dd1565b60608573ffffffffffffffffffffffffffffffffffffffff16620249f08686604051602401612ce291906145af565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612d6b91906140b9565b6000604051808303818686fa925050503d8060008114612da7576040519150601f19603f3d011682016040523d82523d6000602084013e612dac565b606091505b5090925090508115612dcf5780806020019051612dcc9190810190613d6e565b92505b505b935093915050565b600080612de4613555565b85806020019051612df89190810190613e0f565b91509150600085806020019051612e129190810190613df3565b905060006060307f40bc03ae00000000000000000000000000000000000000000000000000000000858786612e468c613089565b604051602401612e599493929190614434565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051612ee291906140b9565b600060405180830381855afa9150503d8060008114612f1d576040519150601f19603f3d011682016040523d82523d6000602084013e612f22565b606091505b509150915081612f3a57600095505050505050610334565b80806020019051612f4e9190810190613a5c565b600081518110612f5a57fe5b6020026020010151955050505050509392505050565b600080600085806020019051612f8991908101906136bd565b91509150600085806020019051612fa391908101906136a1565b905060006060307f9f76ad3500000000000000000000000000000000000000000000000000000000858786612fd78c613089565b604051602401612e599493929190614156565b73f164fc0ec4e93095b804a4795bbe1e041497b92a90565b600081601401835110156130285761302861302360048551856014016132cf565b613374565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006103318361307d61306182600163ffffffff61337c16565b613071888763ffffffff61339b16565b9063ffffffff6133cc16565b9063ffffffff6133e816565b6040805160018082528183019092526060916020808301908038833901905050905081816000815181106130b957fe5b602002602001018181525050919050565b6000806130d5613294565b73ffffffffffffffffffffffffffffffffffffffff1663b78b842d6040518163ffffffff1660e01b815260040160206040518083038186803b15801561311a57600080fd5b505afa15801561312e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061315291908101906136a1565b73ffffffffffffffffffffffffffffffffffffffff1663c3a2a93a6040518163ffffffff1660e01b815260040160006040518083038186803b15801561319757600080fd5b505afa1580156131ab573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526131f19190810190613899565b5091955093505050509091565b6060825160010160405190808252806020026020018201604052801561322e578160200160208202803883390190505b50905060005b83518110156132705783818151811061324957fe5b602002602001015182828151811061325d57fe5b6020908102919091010152600101613234565b50818160018351038151811061328257fe5b60200260200101818152505092915050565b739aab3f75489902f3a48495025729a0af77d4b11e90565b60006123a882613412565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9590565b6060632800659560e01b8484846040516024016132ee93929190614393565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915290509392505050565b805160208201fd5b60008282111561339557613395613023600285856134e3565b50900390565b6000826133aa575060006123a8565b828202828482816133b757fe5b041461033457610334613023600186866134e3565b60008282018381101561033457610334613023600086866134e3565b6000816133fe576133fe613023600385856134e3565b600082848161340957fe5b04949350505050565b600060129050600060608373ffffffffffffffffffffffffffffffffffffffff166040518060400160405280600481526020017f313ce5670000000000000000000000000000000000000000000000000000000081525060405161347691906140b9565b600060405180830381855afa9150503d80600081146134b1576040519150601f19603f3d011682016040523d82523d6000602084013e6134b6565b606091505b50915091508180156134c9575080516020145b156134dc576134d9816000613502565b92505b5050919050565b606063e946c1bb60e01b8484846040516024016132ee93929190614372565b600061033483836000816020018351101561352a5761352a61302360058551856020016132cf565b50016020015190565b6040805160608101909152806000815260006020820181905260409091015290565b604080516060810182526000808252602082018190529181019190915290565b80356123a8816146e2565b600082601f830112613590578081fd5b81356135a361359e82614643565b61461c565b8181529150602080830190840160005b838110156135e0576135cb8760208435890101613653565b835260209283019291909101906001016135b3565b5050505092915050565b600082601f8301126135fa578081fd5b813561360861359e82614643565b81815291506020808301908481018184028601820187101561362957600080fd5b60005b848110156136485781358452928201929082019060010161362c565b505050505092915050565b600082601f830112613663578081fd5b813561367161359e82614663565b915080825283602082850101111561368857600080fd5b8060208401602084013760009082016020015292915050565b6000602082840312156136b2578081fd5b8151610334816146e2565b600080604083850312156136cf578081fd5b82516136da816146e2565b60208401519092506136eb816146e2565b809150509250929050565b60008060006060848603121561370a578081fd5b8335613715816146e2565b92506020840135613725816146e2565b91506040840135613735816146e2565b809150509250925092565b600080600080600060a08688031215613757578283fd5b8535613762816146e2565b94506020860135613772816146e2565b93506040860135613782816146e2565b92506060860135613792816146e2565b9150608086013567ffffffffffffffff8111156137ad578182fd5b6137b9888289016135ea565b9150509295509295909350565b600080600080608085870312156137db578182fd5b84356137e6816146e2565b935060208501356137f6816146e2565b92506040850135613806816146e2565b9150606085013567ffffffffffffffff811115613821578182fd5b61382d878288016135ea565b91505092959194509250565b60008060006060848603121561384d578081fd5b8335613858816146e2565b92506020840135613868816146e2565b9150604084013567ffffffffffffffff811115613883578182fd5b61388f868287016135ea565b9150509250925092565b60008060008060008060c087890312156138b1578384fd5b86516138bc816146e2565b809650506020808801516138cf816146e2565b60408901519096506138e0816146e2565b60608901519095506138f1816146e2565b6080890151909450613902816146e2565b60a089015190935067ffffffffffffffff81111561391e578283fd5b8089018a601f82011261392f578384fd5b8051915061393f61359e83614643565b82815283810190828501858502840186018e101561395b578687fd5b8693505b84841015613986578051613972816146e2565b83526001939093019291850191850161395f565b5080955050505050509295509295509295565b600080604083850312156139ab578182fd5b823567ffffffffffffffff808211156139c2578384fd5b81850186601f8201126139d3578485fd5b803592506139e361359e84614643565b80848252602080830192508084018a828389028701011115613a03578889fd5b8894505b86851015613a2e578035613a1a816146e2565b845260019490940193928101928101613a07565b509096508701359350505080821115613a45578283fd5b50613a52858286016135ea565b9150509250929050565b60006020808385031215613a6e578182fd5b825167ffffffffffffffff811115613a84578283fd5b80840185601f820112613a95578384fd5b80519150613aa561359e83614643565b8281528381019082850185850284018601891015613ac1578687fd5b8693505b84841015613ae3578051835260019390930192918501918501613ac5565b50979650505050505050565b60008060208385031215613b01578182fd5b823567ffffffffffffffff80821115613b18578384fd5b81850186601f820112613b29578485fd5b8035925081831115613b39578485fd5b8660208085028301011115613b4c578485fd5b60200196919550909350505050565b600080600060608486031215613b6f578081fd5b67ffffffffffffffff8085351115613b85578182fd5b8435850186601f820112613b97578283fd5b613ba461359e8235614643565b8135815260208082019190808401865b8535811015613d3157813586016101c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828f03011215613bf3578889fd5b613bfe6101c061461c565b613c0a8e868401613575565b8152613c198e60408401613575565b85820152613c2a8e60608401613575565b6040820152613c3c8e60808401613575565b606082015260a0820135608082015260c082013560a082015260e082013560c082015261010082013560e082015261012082013561010082015261014082013561012082015261016082013589811115613c94578a8bfd5b613ca28f8783860101613653565b6101408301525061018082013589811115613cbb578a8bfd5b613cc98f8783860101613653565b610160830152506101a082013589811115613ce2578a8bfd5b613cf08f8783860101613653565b610180830152506101c082013589811115613d09578a8bfd5b613d178f8783860101613653565b6101a0830152508652509382019390820190600101613bb4565b509197508801359250505081811115613d48578283fd5b613d5487828801613580565b93505050613d658560408601613575565b90509250925092565b600060208284031215613d7f578081fd5b5051919050565b600060208284031215613d97578081fd5b815167ffffffffffffffff811115613dad578182fd5b80830184601f820112613dbe578283fd5b80519150613dce61359e83614663565b828152856020848401011115613de2578384fd5b6105288360208301602085016146a5565b600060208284031215613e04578081fd5b815161033481614732565b60008060808385031215613e21578182fd5b8251613e2c81614732565b915060608385037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0011215613e5f578081fd5b613e69606061461c565b6020840151613e77816146e2565b81526040840151613e8781614704565b60208201526060840151613e9a81614704565b6040820152919491935090915050565b60008060008084860360c0811215613ec0578283fd5b6060811215613ecd578283fd5b50613ed8606061461c565b8535613ee3816146e2565b81526020860135613ef381614704565b60208201526040860135613f0681614704565b604082015293506060850135613f1b81614732565b92506080850135613f2b81614732565b915060a085013567ffffffffffffffff811115613821578182fd5b600080600083850360a0811215613f5b578182fd5b6060811215613f68578182fd5b50613f73606061461c565b845160078110613f81578283fd5b815260208581015190820152604080860151908201526060850151608086015191945092508015158114613735578182fd5b73ffffffffffffffffffffffffffffffffffffffff169052565b6000815180845260208401935060208301825b82811015613ffe578151865260209586019590910190600101613fe0565b5093949350505050565b600081518084526140208160208601602086016146a5565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b805173ffffffffffffffffffffffffffffffffffffffff1682526020808201517fffffffff000000000000000000000000000000000000000000000000000000009081169184019190915260409182015116910152565b6000828483379101908152919050565b600082516140cb8184602087016146a5565b9190910192915050565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b600073ffffffffffffffffffffffffffffffffffffffff8086168352808516602084015250606060408301526105286060830184613fcd565b73ffffffffffffffffffffffffffffffffffffffff92831681529116602082015260400190565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152808516604084015250608060608301526141976080830184613fcd565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff9485168152928416602084015292166040820152606081019190915260800190565b73ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152604081019190915260600190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260ff8416606083015260a0608083015261425160a0830184614008565b979650505050505050565b60006080820173ffffffffffffffffffffffffffffffffffffffff87168352614284866146d5565b60208381018790526080604085015285519182905285019060a0840190835b818110156142c15783518352602093840193909201916001016142a3565b505083810360608501526142d58186613fcd565b98975050505050505050565b6000602080830181845280855180835260408601915060408482028701019250838701855b82811015614352577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452614340858351614008565b94509285019290850190600101614306565b5092979650505050505050565b6000602082526103346020830184613fcd565b6060810161437f856146d5565b938152602081019290925260409091015290565b606081016008851061437f57fe5b600f93840b81529190920b6020820152604081019190915260600190565b600f83900b8152608081016103346020830184614052565b60208082526025908201527f455243323042726964676553616d706c65722f494e56414c49445f544f4b454e60408201527f5f50414952000000000000000000000000000000000000000000000000000000606082015260800190565b60006144408287614052565b84600f0b606083015283600f0b608083015260c060a083015261419760c0830184613fcd565b60006040825261447a604083018551613fb3565b602084015161448c6060840182613fb3565b50604084015161449f6080840182613fb3565b5060608401516144b260a0840182613fb3565b50608084015160c083015260a084015160e083015260c0840151610100818185015260e086015191506101208281860152818701519250610140915082828601528087015192505061016082818601528187015192506101c091506101808281870152614523610200870185614008565b8289015194507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc092506101a08388830301818901526145628287614008565b838b015196508489820301868a015261457b8188614008565b955050808a0151955050505080858303016101e08601525061459d8183614008565b84810360208601526142518187614008565b90815260200190565b60006040820184835260406020840152808451808352606085019150602086019250835b8181101561461057835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016145dc565b50909695505050505050565b60405181810167ffffffffffffffff8111828210171561463b57600080fd5b604052919050565b600067ffffffffffffffff821115614659578081fd5b5060209081020190565b600067ffffffffffffffff821115614679578081fd5b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60005b838110156146c05781810151838201526020016146a8565b838111156146cf576000848401525b50505050565b600481106146df57fe5b50565b73ffffffffffffffffffffffffffffffffffffffff811681146146df57600080fd5b7fffffffff00000000000000000000000000000000000000000000000000000000811681146146df57600080fd5b80600f0b81146146df57600080fdfea365627a7a72315820b6fbcf4008958c944ebfec299da165bd697cf68b9711b57b06ab81852a3933ff6c6578706572696d656e74616cf564736f6c63430005110040" } } }, diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 85487ff583..561d230ec1 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add `IZeroEx` wrapper", "pr": 2626 + }, + { + "note": "Update `ERC20BridgeSampler` wrapper", + "pr": 2633 } ] }, diff --git a/packages/contract-wrappers/src/generated-wrappers/erc20_bridge_sampler.ts b/packages/contract-wrappers/src/generated-wrappers/erc20_bridge_sampler.ts index 43af87bf3e..51c5475c18 100644 --- a/packages/contract-wrappers/src/generated-wrappers/erc20_bridge_sampler.ts +++ b/packages/contract-wrappers/src/generated-wrappers/erc20_bridge_sampler.ts @@ -381,8 +381,22 @@ export class ERC20BridgeSamplerContract extends BaseContract { constant: true, inputs: [ { - name: 'curveAddress', - type: 'address', + name: 'curveInfo', + type: 'tuple', + components: [ + { + name: 'poolAddress', + type: 'address', + }, + { + name: 'sellQuoteFunctionSelector', + type: 'bytes4', + }, + { + name: 'buyQuoteFunctionSelector', + type: 'bytes4', + }, + ], }, { name: 'fromTokenIdx', @@ -450,20 +464,6 @@ export class ERC20BridgeSamplerContract extends BaseContract { name: 'makerTokenAmounts', type: 'uint256[]', }, - { - name: 'opts', - type: 'tuple', - components: [ - { - name: 'targetSlippageBps', - type: 'uint256', - }, - { - name: 'maxIterations', - type: 'uint256', - }, - ], - }, ], name: 'sampleBuysFromKyberNetwork', outputs: [ @@ -495,20 +495,6 @@ export class ERC20BridgeSamplerContract extends BaseContract { name: 'makerTokenAmounts', type: 'uint256[]', }, - { - name: 'opts', - type: 'tuple', - components: [ - { - name: 'targetSlippageBps', - type: 'uint256', - }, - { - name: 'maxIterations', - type: 'uint256', - }, - ], - }, ], name: 'sampleBuysFromLiquidityProviderRegistry', outputs: [ @@ -575,8 +561,22 @@ export class ERC20BridgeSamplerContract extends BaseContract { constant: true, inputs: [ { - name: 'curveAddress', - type: 'address', + name: 'curveInfo', + type: 'tuple', + components: [ + { + name: 'poolAddress', + type: 'address', + }, + { + name: 'sellQuoteFunctionSelector', + type: 'bytes4', + }, + { + name: 'buyQuoteFunctionSelector', + type: 'bytes4', + }, + ], }, { name: 'fromTokenIdx', @@ -1060,24 +1060,24 @@ export class ERC20BridgeSamplerContract extends BaseContract { } /** * Sample buy quotes from Curve. - * @param curveAddress Address of the Curve contract. + * @param curveInfo Curve information specific to this token pair. * @param fromTokenIdx Index of the taker token (what to sell). * @param toTokenIdx Index of the maker token (what to buy). * @param makerTokenAmounts Maker token buy amount for each sample. * @returns takerTokenAmounts Taker amounts sold at each maker token amount. */ public sampleBuysFromCurve( - curveAddress: string, + curveInfo: { poolAddress: string; sellQuoteFunctionSelector: string; buyQuoteFunctionSelector: string }, fromTokenIdx: BigNumber, toTokenIdx: BigNumber, makerTokenAmounts: BigNumber[], ): ContractFunctionObj { const self = (this as any) as ERC20BridgeSamplerContract; - assert.isString('curveAddress', curveAddress); + assert.isBigNumber('fromTokenIdx', fromTokenIdx); assert.isBigNumber('toTokenIdx', toTokenIdx); assert.isArray('makerTokenAmounts', makerTokenAmounts); - const functionSignature = 'sampleBuysFromCurve(address,int128,int128,uint256[])'; + const functionSignature = 'sampleBuysFromCurve((address,bytes4,bytes4),int128,int128,uint256[])'; return { async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { @@ -1092,7 +1092,7 @@ export class ERC20BridgeSamplerContract extends BaseContract { }, getABIEncodedTransactionData(): string { return self._strictEncodeArguments(functionSignature, [ - curveAddress.toLowerCase(), + curveInfo, fromTokenIdx, toTokenIdx, makerTokenAmounts, @@ -1142,21 +1142,18 @@ export class ERC20BridgeSamplerContract extends BaseContract { * @param takerToken Address of the taker token (what to sell). * @param makerToken Address of the maker token (what to buy). * @param makerTokenAmounts Maker token buy amount for each sample. - * @param opts `FakeBuyOptions` specifying target slippage and max iterations. * @returns takerTokenAmounts Taker amounts sold at each maker token amount. */ public sampleBuysFromKyberNetwork( takerToken: string, makerToken: string, makerTokenAmounts: BigNumber[], - opts: { targetSlippageBps: BigNumber; maxIterations: BigNumber }, ): ContractFunctionObj { const self = (this as any) as ERC20BridgeSamplerContract; assert.isString('takerToken', takerToken); assert.isString('makerToken', makerToken); assert.isArray('makerTokenAmounts', makerTokenAmounts); - - const functionSignature = 'sampleBuysFromKyberNetwork(address,address,uint256[],(uint256,uint256))'; + const functionSignature = 'sampleBuysFromKyberNetwork(address,address,uint256[])'; return { async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { @@ -1174,7 +1171,6 @@ export class ERC20BridgeSamplerContract extends BaseContract { takerToken.toLowerCase(), makerToken.toLowerCase(), makerTokenAmounts, - opts, ]); }, }; @@ -1185,7 +1181,6 @@ export class ERC20BridgeSamplerContract extends BaseContract { * @param takerToken Address of the taker token (what to sell). * @param makerToken Address of the maker token (what to buy). * @param makerTokenAmounts Maker token buy amount for each sample. - * @param opts `FakeBuyOptions` specifying target slippage and max iterations. * @returns takerTokenAmounts Taker amounts sold at each maker token amount. */ public sampleBuysFromLiquidityProviderRegistry( @@ -1193,16 +1188,13 @@ export class ERC20BridgeSamplerContract extends BaseContract { takerToken: string, makerToken: string, makerTokenAmounts: BigNumber[], - opts: { targetSlippageBps: BigNumber; maxIterations: BigNumber }, ): ContractFunctionObj { const self = (this as any) as ERC20BridgeSamplerContract; assert.isString('registryAddress', registryAddress); assert.isString('takerToken', takerToken); assert.isString('makerToken', makerToken); assert.isArray('makerTokenAmounts', makerTokenAmounts); - - const functionSignature = - 'sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[],(uint256,uint256))'; + const functionSignature = 'sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[])'; return { async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { @@ -1221,7 +1213,6 @@ export class ERC20BridgeSamplerContract extends BaseContract { takerToken.toLowerCase(), makerToken.toLowerCase(), makerTokenAmounts, - opts, ]); }, }; @@ -1294,24 +1285,24 @@ export class ERC20BridgeSamplerContract extends BaseContract { } /** * Sample sell quotes from Curve. - * @param curveAddress Address of the Curve contract. + * @param curveInfo Curve information specific to this token pair. * @param fromTokenIdx Index of the taker token (what to sell). * @param toTokenIdx Index of the maker token (what to buy). * @param takerTokenAmounts Taker token sell amount for each sample. * @returns makerTokenAmounts Maker amounts bought at each taker token amount. */ public sampleSellsFromCurve( - curveAddress: string, + curveInfo: { poolAddress: string; sellQuoteFunctionSelector: string; buyQuoteFunctionSelector: string }, fromTokenIdx: BigNumber, toTokenIdx: BigNumber, takerTokenAmounts: BigNumber[], ): ContractFunctionObj { const self = (this as any) as ERC20BridgeSamplerContract; - assert.isString('curveAddress', curveAddress); + assert.isBigNumber('fromTokenIdx', fromTokenIdx); assert.isBigNumber('toTokenIdx', toTokenIdx); assert.isArray('takerTokenAmounts', takerTokenAmounts); - const functionSignature = 'sampleSellsFromCurve(address,int128,int128,uint256[])'; + const functionSignature = 'sampleSellsFromCurve((address,bytes4,bytes4),int128,int128,uint256[])'; return { async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { @@ -1326,7 +1317,7 @@ export class ERC20BridgeSamplerContract extends BaseContract { }, getABIEncodedTransactionData(): string { return self._strictEncodeArguments(functionSignature, [ - curveAddress.toLowerCase(), + curveInfo, fromTokenIdx, toTokenIdx, takerTokenAmounts, From de1c71aaccb57a9d60eead78ae61e4ba5050da67 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 15:54:34 -0700 Subject: [PATCH 28/50] fix syntax --- packages/asset-swapper/src/swap_quoter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 2bccf271d4..81b50ece47 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -584,7 +584,7 @@ export class SwapQuoter { 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 - !opts.excludedSources.includes(ERC20BridgeSource.Native) && // Native liquidity is not excluded + !opts.excludedSources.includes(ERC20BridgeSource.Native) // Native liquidity is not excluded ) { if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) { throw new Error('RFQ-T requests must specify a taker address'); From 02f979bc7444a6e463bb3b15bb906e43ec7dbda4 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 15:55:56 -0700 Subject: [PATCH 29/50] linting --- packages/asset-swapper/CHANGELOG.json | 2 +- packages/asset-swapper/src/swap_quoter.ts | 7 ++++--- packages/asset-swapper/src/types.ts | 2 +- .../src/utils/market_operation_utils/types.ts | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 56659a8926..4d8fd9302a 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -555,4 +555,4 @@ } ] } -] \ No newline at end of file +] diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 81b50ece47..da81e44497 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -181,8 +181,8 @@ export class SwapQuoter { const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); const defaultCodeOverrides = samplerBytecode ? { - [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, - } + [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, + } : {}; const samplerOverrides = _.assign( { block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides }, @@ -565,7 +565,8 @@ export class SwapQuoter { // get batches of orders from different sources, awaiting sources in parallel const orderBatchPromises: Array> = []; - const skipOpenOrderbook = opts.excludedSources.includes(ERC20BridgeSource.Native) || + const skipOpenOrderbook = + opts.excludedSources.includes(ERC20BridgeSource.Native) || (opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true); if (!skipOpenOrderbook) { orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index e645ee87eb..dc496342b4 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -226,7 +226,7 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts { /** * Opts required to generate a SwapQuote with SwapQuoteCalculator */ -export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts { } +export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {} /** * A mapping from RFQ-T quote provider URLs to the trading pairs they support. diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index ab2f5a292c..570adc522d 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -65,7 +65,7 @@ export interface CurveInfo { } // Internal `fillData` field for `Fill` objects. -export interface FillData { } +export interface FillData {} // `FillData` for native fills. export interface NativeFillData extends FillData { @@ -163,7 +163,7 @@ export interface CollapsedFill { /** * A `CollapsedFill` wrapping a native order. */ -export interface NativeCollapsedFill extends CollapsedFill { } +export interface NativeCollapsedFill extends CollapsedFill {} /** * Optimized orders to fill. From 8260615b4e52093f6e1822f087ee1e2420a5f98e Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 16:02:16 -0700 Subject: [PATCH 30/50] fix merge conflict --- packages/asset-swapper/src/swap_quoter.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 822095450b..b6b20c900c 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -168,12 +168,8 @@ export class SwapQuoter { this.orderbook = orderbook; this.expiryBufferMs = expiryBufferMs; this.permittedOrderFeeTypes = permittedOrderFeeTypes; -<<<<<<< HEAD this._rfqtOptions = rfqt; -======= - this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : []; ->>>>>>> development this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId); this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider); this._protocolFeeUtils = ProtocolFeeUtils.getInstance( From d877d3686cddd12a6048deee69a6f35080d1bc6a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 16:03:09 -0700 Subject: [PATCH 31/50] linting --- packages/asset-swapper/src/swap_quoter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index b6b20c900c..da81e44497 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -181,8 +181,8 @@ export class SwapQuoter { const samplerBytecode = _.get(ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); const defaultCodeOverrides = samplerBytecode ? { - [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, - } + [this._contractAddresses.erc20BridgeSampler]: { code: samplerBytecode }, + } : {}; const samplerOverrides = _.assign( { block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides }, From f67d2b96ac3f4998325b7af1ee8c6e1071a76d30 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 16:25:24 -0700 Subject: [PATCH 32/50] fix up changelog --- packages/asset-swapper/CHANGELOG.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 4d8fd9302a..408bc11bb0 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -5,12 +5,7 @@ { "note": "Return quoteReport from SwapQuoter functions", "pr": 2627 - } - ] - }, - { - "version": "4.6.1", - "changes": [ + }, { "note": "Allow an empty override for sampler overrides", "pr": 2637 From 34138fc3b509de37087dca1de032885d1ac9a8a2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 23 Jul 2020 17:15:04 -0700 Subject: [PATCH 33/50] handle empty array in reduce --- packages/asset-swapper/src/swap_quoter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index da81e44497..92225e092b 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -605,7 +605,7 @@ export class SwapQuoter { 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); From 5afe2616a416233d08cfada0ce3662471ca3ec40 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 27 Jul 2020 15:07:52 +1000 Subject: [PATCH 34/50] feat: asset-swapper market depth (#2641) * feat: asset-swapper market depth * split promises into 2 * fix lint and docs * chore: refactor * rebase off development * CHANGELOG --- packages/asset-swapper/CHANGELOG.json | 4 + packages/asset-swapper/src/index.ts | 14 +- packages/asset-swapper/src/swap_quoter.ts | 95 +++++++- .../src/utils/market_operation_utils/index.ts | 225 +++++++++++------- .../utils/market_operation_utils/orders.ts | 19 +- .../src/utils/market_operation_utils/types.ts | 38 ++- 6 files changed, 282 insertions(+), 113 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 408bc11bb0..dd0ad365b3 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -17,6 +17,10 @@ { "note": "Support more varied curves", "pr": 2633 + }, + { + "note": "Adds `getBidAskLiquidityForMakerTakerAssetPairAsync` to return more detailed sample information", + "pr": 2641 } ] }, diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index f7ab148f4e..a7ffc0cf2c 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -64,30 +64,31 @@ export { SwapQuoteInfo, SwapQuoteOrdersBreakdown, SwapQuoteRequestOpts, - SwapQuoterRfqtOpts, SwapQuoterError, SwapQuoterOpts, + SwapQuoterRfqtOpts, } from './types'; -import { ERC20BridgeSource } from './utils/market_operation_utils/types'; export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; export { BalancerFillData, CollapsedFill, CurveFillData, + CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, FeeSchedule, FillData, GetMarketOrdersRfqtOpts, + LiquidityProviderFillData, + MarketDepth, + MarketDepthSide, + MultiBridgeFillData, NativeCollapsedFill, NativeFillData, OptimizedMarketOrder, UniswapV2FillData, - CurveFunctionSelectors, } from './utils/market_operation_utils/types'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; -export { QuoteRequestor } from './utils/quote_requestor'; -export { rfqtMocker } from './utils/rfqt_mocker'; export { BridgeReportSource, NativeOrderbookReportSource, @@ -95,4 +96,7 @@ export { QuoteReport, QuoteReportSource, } from './utils/quote_report_generator'; +export { QuoteRequestor } from './utils/quote_requestor'; +export { rfqtMocker } from './utils/rfqt_mocker'; +import { ERC20BridgeSource } from './utils/market_operation_utils/types'; export type Native = ERC20BridgeSource.Native; diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 92225e092b..8bb7281fd7 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -27,7 +27,12 @@ import { calculateLiquidity } from './utils/calculate_liquidity'; import { MarketOperationUtils } from './utils/market_operation_utils'; import { createDummyOrderForSampler } from './utils/market_operation_utils/orders'; import { DexOrderSampler } from './utils/market_operation_utils/sampler'; -import { ERC20BridgeSource } from './utils/market_operation_utils/types'; +import { + ERC20BridgeSource, + MarketDepth, + MarketDepthSide, + MarketSideLiquidity, +} from './utils/market_operation_utils/types'; import { orderPrunerUtils } from './utils/order_prune_utils'; import { OrderStateUtils } from './utils/order_state_utils'; import { ProtocolFeeUtils } from './utils/protocol_fee_utils'; @@ -393,6 +398,94 @@ export class SwapQuoter { return calculateLiquidity(ordersWithFillableAmounts); } + /** + * Returns the bids and asks liquidity for the entire market. + * For certain sources (like AMM's) it is recommended to provide a practical maximum takerAssetAmount. + * @param makerTokenAddress The address of the maker asset + * @param takerTokenAddress The address of the taker asset + * @param takerAssetAmount The amount to sell and buy for the bids and asks. + * + * @return An object that conforms to MarketDepth that contains all of the samples and liquidity + * information for the source. + */ + public async getBidAskLiquidityForMakerTakerAssetPairAsync( + makerTokenAddress: string, + takerTokenAddress: string, + takerAssetAmount: BigNumber, + options: Partial = {}, + ): Promise { + assert.isString('makerTokenAddress', makerTokenAddress); + assert.isString('takerTokenAddress', takerTokenAddress); + const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress); + const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress); + let [sellOrders, buyOrders] = + options.excludedSources && options.excludedSources.includes(ERC20BridgeSource.Native) + ? Promise.resolve([[], []]) + : await Promise.all([ + this.orderbook.getOrdersAsync(makerAssetData, takerAssetData), + this.orderbook.getOrdersAsync(takerAssetData, makerAssetData), + ]); + if (!sellOrders || sellOrders.length === 0) { + sellOrders = [ + { + metaData: {}, + order: createDummyOrderForSampler( + makerAssetData, + takerAssetData, + this._contractAddresses.uniswapBridge, + ), + }, + ]; + } + if (!buyOrders || buyOrders.length === 0) { + buyOrders = [ + { + metaData: {}, + order: createDummyOrderForSampler( + takerAssetData, + makerAssetData, + this._contractAddresses.uniswapBridge, + ), + }, + ]; + } + const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => { + const { dexQuotes, nativeOrders, orderFillableAmounts, side } = marketSideLiquidity; + return [ + ...dexQuotes, + nativeOrders.map((o, i) => { + const scaleFactor = orderFillableAmounts[i].div(o.takerAssetAmount); + return { + input: (side === MarketOperation.Sell ? o.takerAssetAmount : o.makerAssetAmount) + .times(scaleFactor) + .integerValue(), + output: (side === MarketOperation.Sell ? o.makerAssetAmount : o.takerAssetAmount) + .times(scaleFactor) + .integerValue(), + fillData: o, + source: ERC20BridgeSource.Native, + }; + }), + ]; + }; + const [bids, asks] = await Promise.all([ + this._marketOperationUtils.getMarketBuyLiquidityAsync( + (buyOrders || []).map(o => o.order), + takerAssetAmount, + options, + ), + this._marketOperationUtils.getMarketSellLiquidityAsync( + (sellOrders || []).map(o => o.order), + takerAssetAmount, + options, + ), + ]); + return { + bids: getMarketDepthSide(bids), + asks: getMarketDepthSide(asks), + }; + } + /** * Get the asset data of all assets that can be used to purchase makerAssetData in the order provider passed in at init. * 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 13ce139e20..caab906043 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -25,6 +25,7 @@ import { ERC20BridgeSource, FeeSchedule, GetMarketOrdersOpts, + MarketSideLiquidity, OptimizedMarketOrder, OptimizedOrdersAndQuoteReport, OrderDomain, @@ -74,18 +75,17 @@ export class MarketOperationUtils { } /** - * gets the orders required for a market sell operation by (potentially) merging native orders with - * generated bridge orders. + * Gets the liquidity available for a market sell operation * @param nativeOrders Native orders. * @param takerAmount Amount of taker asset to sell. * @param opts Options object. - * @return orders. + * @return MarketSideLiquidity. */ - public async getMarketSellOrdersAsync( + public async getMarketSellLiquidityAsync( nativeOrders: SignedOrder[], takerAmount: BigNumber, opts?: Partial, - ): Promise { + ): Promise { if (nativeOrders.length === 0) { throw new Error(AggregationError.EmptyOrders); } @@ -156,41 +156,40 @@ export class MarketOperationUtils { rfqtIndicativeQuotes, [balancerQuotes], ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); - return this._generateOptimizedOrdersAsync({ - orderFillableAmounts, - nativeOrders, - dexQuotes: dexQuotes.concat(balancerQuotes), - rfqtIndicativeQuotes, - liquidityProviderAddress, - multiBridgeAddress: this._multiBridge, - inputToken: takerToken, - outputToken: makerToken, + + // Attach the LiquidityProvider address to the sample fillData + (dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.LiquidityProvider) || []).forEach( + q => (q.fillData = { poolAddress: liquidityProviderAddress }), + ); + // Attach the MultiBridge address to the sample fillData + (dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach( + q => (q.fillData = { poolAddress: this._multiBridge }), + ); + return { side: MarketOperation.Sell, inputAmount: takerAmount, + inputToken: takerToken, + outputToken: makerToken, + dexQuotes: dexQuotes.concat(balancerQuotes), + nativeOrders, + orderFillableAmounts, ethToOutputRate: ethToMakerAssetRate, - bridgeSlippage: _opts.bridgeSlippage, - maxFallbackSlippage: _opts.maxFallbackSlippage, - excludedSources: _opts.excludedSources, - feeSchedule: _opts.feeSchedule, - allowFallback: _opts.allowFallback, - shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, - quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, - }); + rfqtIndicativeQuotes, + }; } /** - * gets the orders required for a market buy operation by (potentially) merging native orders with - * generated bridge orders. + * Gets the liquidity available for a market buy operation * @param nativeOrders Native orders. * @param makerAmount Amount of maker asset to buy. * @param opts Options object. - * @return object with optimized orders and a QuoteReport + * @return MarketSideLiquidity. */ - public async getMarketBuyOrdersAsync( + public async getMarketBuyLiquidityAsync( nativeOrders: SignedOrder[], makerAmount: BigNumber, opts?: Partial, - ): Promise { + ): Promise { if (nativeOrders.length === 0) { throw new Error(AggregationError.EmptyOrders); } @@ -260,19 +259,68 @@ export class MarketOperationUtils { rfqtIndicativeQuotes, [balancerQuotes], ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); - - return this._generateOptimizedOrdersAsync({ - orderFillableAmounts, - nativeOrders, - dexQuotes: dexQuotes.concat(balancerQuotes), - rfqtIndicativeQuotes, - liquidityProviderAddress, - multiBridgeAddress: this._multiBridge, - inputToken: makerToken, - outputToken: takerToken, + // Attach the LiquidityProvider address to the sample fillData + (dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.LiquidityProvider) || []).forEach( + q => (q.fillData = { poolAddress: liquidityProviderAddress }), + ); + // Attach the MultiBridge address to the sample fillData + (dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach( + q => (q.fillData = { poolAddress: this._multiBridge }), + ); + return { side: MarketOperation.Buy, inputAmount: makerAmount, + inputToken: makerToken, + outputToken: takerToken, + dexQuotes: dexQuotes.concat(balancerQuotes), + nativeOrders, + orderFillableAmounts, ethToOutputRate: ethToTakerAssetRate, + rfqtIndicativeQuotes, + }; + } + + /** + * gets the orders required for a market sell operation by (potentially) merging native orders with + * generated bridge orders. + * @param nativeOrders Native orders. + * @param takerAmount Amount of taker asset to sell. + * @param opts Options object. + * @return object with optimized orders and a QuoteReport + */ + public async getMarketSellOrdersAsync( + nativeOrders: SignedOrder[], + takerAmount: BigNumber, + opts?: Partial, + ): Promise { + const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; + const marketSideLiquidity = await this.getMarketSellLiquidityAsync(nativeOrders, takerAmount, _opts); + return this._generateOptimizedOrdersAsync(marketSideLiquidity, { + bridgeSlippage: _opts.bridgeSlippage, + maxFallbackSlippage: _opts.maxFallbackSlippage, + excludedSources: _opts.excludedSources, + feeSchedule: _opts.feeSchedule, + allowFallback: _opts.allowFallback, + shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, + }); + } + + /** + * gets the orders required for a market buy operation by (potentially) merging native orders with + * generated bridge orders. + * @param nativeOrders Native orders. + * @param makerAmount Amount of maker asset to buy. + * @param opts Options object. + * @return object with optimized orders and a QuoteReport + */ + public async getMarketBuyOrdersAsync( + nativeOrders: SignedOrder[], + makerAmount: BigNumber, + opts?: Partial, + ): Promise { + const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; + const marketSideLiquidity = await this.getMarketBuyLiquidityAsync(nativeOrders, makerAmount, _opts); + return this._generateOptimizedOrdersAsync(marketSideLiquidity, { bridgeSlippage: _opts.bridgeSlippage, maxFallbackSlippage: _opts.maxFallbackSlippage, excludedSources: _opts.excludedSources, @@ -351,23 +399,28 @@ export class MarketOperationUtils { const dexQuotes = batchDexQuotes[i]; const makerAmount = makerAmounts[i]; try { - return (await this._generateOptimizedOrdersAsync({ - orderFillableAmounts, - nativeOrders, - dexQuotes, - rfqtIndicativeQuotes: [], - inputToken: makerToken, - outputToken: takerToken, - side: MarketOperation.Buy, - inputAmount: makerAmount, - ethToOutputRate: ethToTakerAssetRate, - bridgeSlippage: _opts.bridgeSlippage, - maxFallbackSlippage: _opts.maxFallbackSlippage, - excludedSources: _opts.excludedSources, - feeSchedule: _opts.feeSchedule, - allowFallback: _opts.allowFallback, - shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, - })).optimizedOrders; + const { optimizedOrders } = await this._generateOptimizedOrdersAsync( + { + side: MarketOperation.Buy, + nativeOrders, + orderFillableAmounts, + dexQuotes, + inputAmount: makerAmount, + ethToOutputRate: ethToTakerAssetRate, + rfqtIndicativeQuotes: [], + inputToken: makerToken, + outputToken: takerToken, + }, + { + bridgeSlippage: _opts.bridgeSlippage, + maxFallbackSlippage: _opts.maxFallbackSlippage, + excludedSources: _opts.excludedSources, + feeSchedule: _opts.feeSchedule, + allowFallback: _opts.allowFallback, + shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, + }, + ); + return optimizedOrders; } catch (e) { // It's possible for one of the pairs to have no path // rather than throw NO_OPTIMAL_PATH we return undefined @@ -377,40 +430,42 @@ export class MarketOperationUtils { ); } - private async _generateOptimizedOrdersAsync(opts: { - side: MarketOperation; - inputToken: string; - outputToken: string; - inputAmount: BigNumber; - nativeOrders: SignedOrder[]; - orderFillableAmounts: BigNumber[]; - dexQuotes: DexSample[][]; - rfqtIndicativeQuotes: RFQTIndicativeQuote[]; - runLimit?: number; - ethToOutputRate?: BigNumber; - bridgeSlippage?: number; - maxFallbackSlippage?: number; - excludedSources?: ERC20BridgeSource[]; - feeSchedule?: FeeSchedule; - allowFallback?: boolean; - shouldBatchBridgeOrders?: boolean; - liquidityProviderAddress?: string; - multiBridgeAddress?: string; - quoteRequestor?: QuoteRequestor; - }): Promise { - const { inputToken, outputToken, side, inputAmount } = opts; + private async _generateOptimizedOrdersAsync( + marketSideLiquidity: MarketSideLiquidity, + opts: { + runLimit?: number; + bridgeSlippage?: number; + maxFallbackSlippage?: number; + excludedSources?: ERC20BridgeSource[]; + feeSchedule?: FeeSchedule; + allowFallback?: boolean; + shouldBatchBridgeOrders?: boolean; + quoteRequestor?: QuoteRequestor; + }, + ): Promise { + const { + inputToken, + outputToken, + side, + inputAmount, + nativeOrders, + orderFillableAmounts, + rfqtIndicativeQuotes, + dexQuotes, + ethToOutputRate, + } = marketSideLiquidity; const maxFallbackSlippage = opts.maxFallbackSlippage || 0; // Convert native orders and dex quotes into fill paths. const paths = createFillPaths({ side, // Augment native orders with their fillable amounts. orders: [ - ...createSignedOrdersWithFillableAmounts(side, opts.nativeOrders, opts.orderFillableAmounts), - ...createSignedOrdersFromRfqtIndicativeQuotes(opts.rfqtIndicativeQuotes), + ...createSignedOrdersWithFillableAmounts(side, nativeOrders, orderFillableAmounts), + ...createSignedOrdersFromRfqtIndicativeQuotes(rfqtIndicativeQuotes), ], - dexQuotes: opts.dexQuotes, + dexQuotes, targetInput: inputAmount, - ethToOutputRate: opts.ethToOutputRate, + ethToOutputRate, excludedSources: opts.excludedSources, feeSchedule: opts.feeSchedule, }); @@ -458,15 +513,13 @@ export class MarketOperationUtils { orderDomain: this._orderDomain, contractAddresses: this.contractAddresses, bridgeSlippage: opts.bridgeSlippage || 0, - liquidityProviderAddress: opts.liquidityProviderAddress, - multiBridgeAddress: opts.multiBridgeAddress, shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders, }); const quoteReport = new QuoteReportGenerator( - opts.side, - _.flatten(opts.dexQuotes), - opts.nativeOrders, - opts.orderFillableAmounts, + side, + _.flatten(dexQuotes), + nativeOrders, + orderFillableAmounts, _.flatten(optimizedOrders.map(o => o.fills)), opts.quoteRequestor, ).generateReport(); diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index abeba32e73..f07a61a0a7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -24,6 +24,8 @@ import { CurveFillData, ERC20BridgeSource, Fill, + LiquidityProviderFillData, + MultiBridgeFillData, NativeCollapsedFill, OptimizedMarketOrder, OrderDomain, @@ -143,8 +145,6 @@ export interface CreateOrderFromPathOpts { contractAddresses: ContractAddresses; bridgeSlippage: number; shouldBatchBridgeOrders: boolean; - liquidityProviderAddress?: string; - multiBridgeAddress?: string; } // Convert sell fills into orders. @@ -177,7 +177,8 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts return orders; } -function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrderFromPathOpts): string { +function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPathOpts): string { + const source = fill.source; switch (source) { case ERC20BridgeSource.Eth2Dai: return opts.contractAddresses.eth2DaiBridge; @@ -192,15 +193,9 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrder case ERC20BridgeSource.Balancer: return opts.contractAddresses.balancerBridge; case ERC20BridgeSource.LiquidityProvider: - if (opts.liquidityProviderAddress === undefined) { - throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.'); - } - return opts.liquidityProviderAddress; + return (fill.fillData as LiquidityProviderFillData).poolAddress; case ERC20BridgeSource.MultiBridge: - if (opts.multiBridgeAddress === undefined) { - throw new Error('Cannot create a MultiBridge order without a MultiBridge address.'); - } - return opts.multiBridgeAddress; + return (fill.fillData as MultiBridgeFillData).poolAddress; default: break; } @@ -209,7 +204,7 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrder function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): OptimizedMarketOrder { const [makerToken, takerToken] = getMakerTakerTokens(opts); - const bridgeAddress = getBridgeAddressFromSource(fill.source, opts); + const bridgeAddress = getBridgeAddressFromFill(fill, opts); let makerAssetData; switch (fill.source) { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 570adc522d..e432f5497e 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -1,4 +1,6 @@ import { ERC20BridgeSamplerContract } from '@0x/contract-wrappers'; +import { RFQTIndicativeQuote } from '@0x/quote-server'; +import { MarketOperation, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types'; @@ -86,6 +88,14 @@ export interface UniswapV2FillData extends FillData { tokenAddressPath: string[]; } +export interface LiquidityProviderFillData extends FillData { + poolAddress: string; +} + +export interface MultiBridgeFillData extends FillData { + poolAddress: string; +} + /** * Represents an individual DEX sample from the sampler contract. */ @@ -256,16 +266,26 @@ export interface SourceQuoteOperation ext 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; } + +export type MarketDepthSide = Array>>; + +export interface MarketDepth { + bids: MarketDepthSide; + asks: MarketDepthSide; +} + +export interface MarketSideLiquidity { + side: MarketOperation; + inputAmount: BigNumber; + inputToken: string; + outputToken: string; + dexQuotes: Array>>; + nativeOrders: SignedOrder[]; + orderFillableAmounts: BigNumber[]; + ethToOutputRate: BigNumber; + rfqtIndicativeQuotes: RFQTIndicativeQuote[]; +} From ae2a6fb68571ec942d7662500d3cdbdad95998e7 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 27 Jul 2020 01:09:49 -0400 Subject: [PATCH 35/50] asset-swapper: Quicker path-finding (#2640) * `@0x/asset-swapper`: Speed up path optimizer. * `@0x/asset-swapper`: address my own review comment * `@0x/asset-swapper`: Update changelog Co-authored-by: Lawrence Forman Co-authored-by: Jacob Evans --- packages/asset-swapper/CHANGELOG.json | 4 ++ .../src/utils/market_operation_utils/fills.ts | 35 +++-------- .../market_operation_utils/path_optimizer.ts | 62 ++++++++++++------- .../src/utils/market_operation_utils/types.ts | 4 ++ .../test/market_operation_utils_test.ts | 30 +++------ 5 files changed, 65 insertions(+), 70 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index dd0ad365b3..8c9b3d45d7 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -18,6 +18,10 @@ "note": "Support more varied curves", "pr": 2633 }, + { + "note": "Make path optimization go faster", + "pr": 2640 + }, { "note": "Adds `getBidAskLiquidityForMakerTakerAssetPairAsync` to return more detailed sample information", "pr": 2641 diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index 6908eca747..eb7b0aacff 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0x/utils'; +import { BigNumber, hexUtils } from '@0x/utils'; import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; import { fillableAmountsUtils } from '../../utils/fillable_amounts_utils'; @@ -56,6 +56,7 @@ function nativeOrdersToPath( ethToOutputRate: BigNumber, fees: FeeSchedule, ): Fill[] { + const sourcePathId = hexUtils.random(); // Create a single path from all orders. let path: Fill[] = []; for (const order of orders) { @@ -84,6 +85,7 @@ function nativeOrdersToPath( continue; } path.push({ + sourcePathId, input: clippedInput, output: clippedOutput, rate, @@ -114,6 +116,7 @@ function dexQuotesToPaths( ): Fill[][] { const paths: Fill[][] = []; for (let quote of dexQuotes) { + const sourcePathId = hexUtils.random(); const path: Fill[] = []; // Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves // We need not worry about Kyber fills going to UniswapReserve as the input amount @@ -136,6 +139,7 @@ function dexQuotesToPaths( const adjustedRate = side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput); path.push({ + sourcePathId, input, output, rate, @@ -261,35 +265,12 @@ export function collapsePath(path: Fill[]): CollapsedFill[] { return collapsed; } -export function getFallbackSourcePaths(optimalPath: Fill[], allPaths: Fill[][]): Fill[][] { - const optimalSources: ERC20BridgeSource[] = []; - for (const fill of optimalPath) { - if (!optimalSources.includes(fill.source)) { - optimalSources.push(fill.source); - } - } - const fallbackPaths: Fill[][] = []; - for (const path of allPaths) { - if (optimalSources.includes(path[0].source)) { - continue; - } - // HACK(dorothy-zbornak): We *should* be filtering out paths that - // conflict with the optimal path (i.e., Kyber conflicts), but in - // practice we often end up not being able to find a fallback path - // because we've lost 2 major liquiduty sources. The end result is - // we end up with many more reverts than what would be actually caused - // by conflicts. - fallbackPaths.push(path); - } - return fallbackPaths; -} - export function getPathAdjustedRate(side: MarketOperation, path: Fill[], targetInput: BigNumber): BigNumber { - const [input, output] = getPathAdjustedSize(path, targetInput); - if (input.eq(0) || output.eq(0)) { + const [, output] = getPathAdjustedSize(path, targetInput); + if (output.eq(0)) { return ZERO_AMOUNT; } - return side === MarketOperation.Sell ? output.div(input) : input.div(output); + return side === MarketOperation.Sell ? output.div(targetInput) : targetInput.div(output); } export function getPathAdjustedSlippage( diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts index f4870e5d2f..dd5627fa10 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts @@ -3,16 +3,12 @@ import { BigNumber } from '@0x/utils'; import { MarketOperation } from '../../types'; import { ZERO_AMOUNT } from './constants'; -import { getPathAdjustedSize, getPathSize, isValidPath } from './fills'; +import { getPathAdjustedRate, getPathSize, isValidPath } from './fills'; import { Fill } from './types'; // tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs -const RUN_LIMIT_DECAY_FACTOR = 0.8; -// Used to yield the event loop when performing CPU intensive tasks -// tislint:disable-next-line:no-inferred-empty-object-type -const setImmediateAsync = async (delay: number = 0) => - new Promise(resolve => setImmediate(() => resolve(), delay)); +const RUN_LIMIT_DECAY_FACTOR = 0.5; /** * Find the optimal mixture of paths that maximizes (for sells) or minimizes @@ -22,16 +18,19 @@ export async function findOptimalPathAsync( side: MarketOperation, paths: Fill[][], targetInput: BigNumber, - runLimit: number = 2 ** 15, + runLimit: number = 2 ** 8, ): Promise { - // Sort paths in descending order by adjusted output amount. + // Sort paths by descending adjusted rate. const sortedPaths = paths .slice(0) - .sort((a, b) => getPathAdjustedSize(b, targetInput)[1].comparedTo(getPathAdjustedSize(a, targetInput)[1])); + .sort((a, b) => + getPathAdjustedRate(side, b, targetInput).comparedTo(getPathAdjustedRate(side, a, targetInput)), + ); let optimalPath = sortedPaths[0] || []; for (const [i, path] of sortedPaths.slice(1).entries()) { optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit * RUN_LIMIT_DECAY_FACTOR ** i); - await setImmediateAsync(); + // Yield to event loop. + await Promise.resolve(); } return isPathComplete(optimalPath, targetInput) ? optimalPath : undefined; } @@ -43,9 +42,10 @@ function mixPaths( targetInput: BigNumber, maxSteps: number, ): Fill[] { - let bestPath: Fill[] = []; - let bestPathInput = ZERO_AMOUNT; - let bestPathRate = ZERO_AMOUNT; + const _maxSteps = Math.max(maxSteps, 16); + let bestPath: Fill[] = pathA; + let bestPathInput = getPathSize(pathA, targetInput)[0]; + let bestPathRate = getPathAdjustedRate(side, pathA, targetInput); let steps = 0; const _isBetterPath = (input: BigNumber, rate: BigNumber) => { if (bestPathInput.lt(targetInput)) { @@ -57,7 +57,7 @@ function mixPaths( }; const _walk = (path: Fill[], input: BigNumber, output: BigNumber, allFills: Fill[]) => { steps += 1; - const rate = getRate(side, input, output); + const rate = getRate(side, targetInput, output); if (_isBetterPath(input, rate)) { bestPath = path; bestPathInput = input; @@ -65,13 +65,11 @@ function mixPaths( } const remainingInput = targetInput.minus(input); if (remainingInput.gt(0)) { - for (let i = 0; i < allFills.length; ++i) { + for (let i = 0; i < allFills.length && steps < _maxSteps; ++i) { const fill = allFills[i]; - if (steps + 1 >= maxSteps) { - break; - } - const childPath = [...path, fill]; - if (!isValidPath(childPath, true)) { + const nextPath = [...path, fill]; + // Only walk valid paths. + if (!isValidPath(nextPath, true)) { continue; } // Remove this fill from the next list of candidate fills. @@ -79,7 +77,7 @@ function mixPaths( nextAllFills.splice(i, 1); // Recurse. _walk( - childPath, + nextPath, input.plus(BigNumber.min(remainingInput, fill.input)), output.plus( // Clip the output of the next fill to the remaining @@ -91,7 +89,27 @@ function mixPaths( } } }; - _walk(bestPath, ZERO_AMOUNT, ZERO_AMOUNT, [...pathA, ...pathB].sort((a, b) => b.rate.comparedTo(a.rate))); + const allPaths = [...pathA, ...pathB]; + const sources = allPaths.filter(f => f.index === 0).map(f => f.sourcePathId); + const rateBySource = Object.assign( + {}, + ...sources.map(s => ({ + [s]: getPathAdjustedRate(side, allPaths.filter(f => f.sourcePathId === s), targetInput), + })), + ); + _walk( + [], + ZERO_AMOUNT, + ZERO_AMOUNT, + // Sort subpaths by rate and keep fills contiguous to improve our + // chances of walking ideal, valid paths first. + allPaths.sort((a, b) => { + if (a.sourcePathId !== b.sourcePathId) { + return rateBySource[b.sourcePathId].comparedTo(rateBySource[a.sourcePathId]); + } + return a.index - b.index; + }), + ); return bestPath; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index e432f5497e..a2b27ee401 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -120,6 +120,10 @@ export enum FillFlags { * Represents a node on a fill path. */ export interface Fill { + // Unique ID of the original source path this fill belongs to. + // This is generated when the path is generated and is useful to distinguish + // paths that have the same `source` IDs but are distinct (e.g., Curves). + sourcePathId: string; // See `FillFlags`. flags: FillFlags; // Input fill amount (taker asset amount in a sell, maker asset amount in a buy). diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 4392762120..42e54e214d 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -703,15 +703,9 @@ describe('MarketOperationUtils tests', () => { ); const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); - const firstSources = [ - ERC20BridgeSource.Native, - ERC20BridgeSource.Native, - ERC20BridgeSource.Native, - ERC20BridgeSource.Uniswap, - ]; - const secondSources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber]; - expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); - expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); + const firstSources = orderSources.slice(0, 4); + const secondSources = orderSources.slice(4); + expect(_.intersection(firstSources, secondSources)).to.be.length(0); }); it('does not create a fallback if below maxFallbackSlippage', async () => { @@ -819,9 +813,9 @@ describe('MarketOperationUtils tests', () => { expect(improvedOrders).to.be.length(3); const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ - [ERC20BridgeSource.Uniswap], + [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Curve], [ERC20BridgeSource.Native], - [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve], + [ERC20BridgeSource.Eth2Dai], ]); }); }); @@ -1091,15 +1085,9 @@ describe('MarketOperationUtils tests', () => { ); const improvedOrders = improvedOrdersResponse.optimizedOrders; const orderSources = improvedOrders.map(o => o.fills[0].source); - const firstSources = [ - ERC20BridgeSource.Native, - ERC20BridgeSource.Native, - ERC20BridgeSource.Native, - ERC20BridgeSource.Uniswap, - ]; - const secondSources = [ERC20BridgeSource.Eth2Dai]; - expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); - expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); + const firstSources = orderSources.slice(0, 4); + const secondSources = orderSources.slice(4); + expect(_.intersection(firstSources, secondSources)).to.be.length(0); }); it('does not create a fallback if below maxFallbackSlippage', async () => { @@ -1145,7 +1133,7 @@ describe('MarketOperationUtils tests', () => { const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ [ERC20BridgeSource.Native], - [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap], + [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai], ]); }); }); From 466d962c07167f545142c5f763bacf1a4df6db05 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 28 Jul 2020 12:49:21 +1000 Subject: [PATCH 36/50] fix: Redeploy ERC20BridgeSampler on Kovan (#2644) --- packages/contract-addresses/CHANGELOG.json | 4 ++++ packages/contract-addresses/addresses.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 21bfc6ae26..820e55ebc1 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Update `CurveBridge` address on all networks", "pr": 2633 + }, + { + "note": "Redeploy ERC20BridgeSampler on Kovan", + "pr": 2644 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index ef3c29d503..555ba98f8f 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -163,7 +163,7 @@ "uniswapBridge": "0x0e85f89f29998df65402391478e5924700c0079d", "uniswapV2Bridge": "0x0000000000000000000000000000000000000000", "eth2DaiBridge": "0x2d47147429b474d2e4f83e658015858a1312ed5b", - "erc20BridgeSampler": "0xccc9769c1a58766e79423a34b2cc5052d65c1983", + "erc20BridgeSampler": "0x6c1b8901bdd522653334b33a4003e7ab4170320e", "kyberBridge": "0xaecfa25920f892b6eb496e1f6e84037f59da7f44", "chaiBridge": "0x0000000000000000000000000000000000000000", "dydxBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", From 766888ec417fd5d974773e7a7228c05664c6bfb1 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 28 Jul 2020 13:39:21 -0700 Subject: [PATCH 37/50] Send in quoteRequestor to fix RFQT tracking --- packages/asset-swapper/src/utils/market_operation_utils/index.ts | 1 + 1 file changed, 1 insertion(+) 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 caab906043..6c2363fca6 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -302,6 +302,7 @@ export class MarketOperationUtils { feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, + quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, }); } From 84a55a2d840187da5dce7bb890244b11b41eca3e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 30 Jul 2020 13:07:21 -0400 Subject: [PATCH 38/50] `@0x/contract-wrappers`: add `exchangeProxy` to `ContractWrappers` tyoe. --- packages/contract-wrappers/CHANGELOG.json | 4 ++++ packages/contract-wrappers/src/contract_wrappers.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 561d230ec1..99cfbdac98 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Update `ERC20BridgeSampler` wrapper", "pr": 2633 + }, + { + "note": "Add `exchangeProxy` to `ContractWrappers` type.", + "pr": 2649 } ] }, diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index a7e14f3304..52665c075c 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -11,6 +11,7 @@ import { ERC20TokenContract } from './generated-wrappers/erc20_token'; import { ERC721TokenContract } from './generated-wrappers/erc721_token'; import { ExchangeContract } from './generated-wrappers/exchange'; import { ForwarderContract } from './generated-wrappers/forwarder'; +import { IZeroExContract } from './generated-wrappers/i_zero_ex'; import { StakingContract } from './generated-wrappers/staking'; import { WETH9Contract } from './generated-wrappers/weth9'; import { ContractWrappersConfig } from './types'; @@ -49,6 +50,10 @@ export class ContractWrappers { * An instance of the StakingContract class containing methods for interacting with the Staking contracts. */ public staking: StakingContract; + /** + * An instance of the IZeroExContract class containing methods for interacting with the Exchange Proxy. + */ + public exchangeProxy: IZeroExContract; private readonly _web3Wrapper: Web3Wrapper; /** @@ -73,6 +78,7 @@ export class ContractWrappers { ForwarderContract, StakingContract, WETH9Contract, + IZeroExContract, ]; contractsArray.forEach(contract => { this._web3Wrapper.abiDecoder.addABI(contract.ABI(), contract.contractName); @@ -87,6 +93,7 @@ export class ContractWrappers { this.staking = new StakingContract(contractAddresses.stakingProxy, this.getProvider()); this.devUtils = new DevUtilsContract(contractAddresses.devUtils, this.getProvider()); this.coordinator = new CoordinatorContract(contractAddresses.coordinator, this.getProvider()); + this.exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, this.getProvider()); this.contractAddresses = contractAddresses; } /** From 053b55fbbfc4a721514881367e450b1a51c8bed0 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 30 Jul 2020 13:10:13 -0400 Subject: [PATCH 39/50] `@0x/order-utils`: Add gitpkg --- packages/order-utils/CHANGELOG.json | 9 +++++++++ packages/order-utils/package.json | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 412b297e27..e6d884c4a9 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "10.3.1", + "changes": [ + { + "note": "Add gitpkg.", + "pr": 2649 + } + ] + }, { "version": "10.3.0", "changes": [ diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index 494cb9bf6a..744c67dffb 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "yarn tsc -b", "build:ci": "yarn build", + "publish:private": "yarn clean && yarn build && gitpkg publish", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", "test:circleci": "yarn test:coverage", @@ -29,6 +30,9 @@ "assets": [] } }, + "gitpkg": { + "registry": "git@github.com:0xProject/gitpkg-registry.git" + }, "license": "Apache-2.0", "repository": { "type": "git", @@ -52,6 +56,7 @@ "@types/web3-provider-engine": "^14.0.0", "chai": "^4.0.1", "ethereum-types": "^3.2.0", + "gitpkg": "https://github.com/0xProject/gitpkg.git", "make-promises-safe": "^1.1.0", "mocha": "^6.2.0", "npm-run-all": "^4.1.2", From f966b6f4dfa4e9dc06b19acad9b60ddd00c77946 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 30 Jul 2020 11:30:49 -0400 Subject: [PATCH 40/50] `@0x/ethereum-types`: Make `ConstructorAbi.payable` optional. --- packages/ethereum-types/CHANGELOG.json | 9 +++++++++ packages/ethereum-types/src/index.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json index c17ca5e8c5..7262e20b92 100644 --- a/packages/ethereum-types/CHANGELOG.json +++ b/packages/ethereum-types/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.3.0", + "changes": [ + { + "note": "Make `payable` field in `ConstructorAbi` optional", + "pr": 2648 + } + ] + }, { "version": "3.2.0", "changes": [ diff --git a/packages/ethereum-types/src/index.ts b/packages/ethereum-types/src/index.ts index 8565ea50ec..87be4b8e66 100644 --- a/packages/ethereum-types/src/index.ts +++ b/packages/ethereum-types/src/index.ts @@ -101,7 +101,7 @@ export interface ConstructorAbi { // from JSON files, and this value has type `string` not type `'constructor'` type: string; inputs: DataItem[]; - payable: boolean; + payable?: boolean; stateMutability: ConstructorStateMutability; } From 222fd5d8227328b64e4f483dccd893fde6c6a2fa Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 3 Aug 2020 16:26:37 +1000 Subject: [PATCH 41/50] fix: Deploy UniswapV2 Bridge on Kovan (#2652) * fix: Deploy UniswapV2 Bridge on Kovan * CHANGELOG --- packages/contract-addresses/CHANGELOG.json | 6 +++++- packages/contract-addresses/addresses.json | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 820e55ebc1..3b64d634ed 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -7,8 +7,12 @@ "pr": 2633 }, { - "note": "Redeploy ERC20BridgeSampler on Kovan", + "note": "Redeploy `ERC20BridgeSampler` on Kovan", "pr": 2644 + }, + { + "note": "Deploy `UniswapV2Bridge` on Kovan", + "pr": 2652 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 555ba98f8f..fbfb520df2 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -161,9 +161,9 @@ "stakingProxy": "0xbab9145f1d57cd4bb0c9aa2d1ece0a5b6e734d34", "erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64", "uniswapBridge": "0x0e85f89f29998df65402391478e5924700c0079d", - "uniswapV2Bridge": "0x0000000000000000000000000000000000000000", + "uniswapV2Bridge": "0x7b3530a635d099de0534dc27e46cd7c57578c3c8", "eth2DaiBridge": "0x2d47147429b474d2e4f83e658015858a1312ed5b", - "erc20BridgeSampler": "0x6c1b8901bdd522653334b33a4003e7ab4170320e", + "erc20BridgeSampler": "0xcf9e66851f274aa4721e54526117876d90d51aa1", "kyberBridge": "0xaecfa25920f892b6eb496e1f6e84037f59da7f44", "chaiBridge": "0x0000000000000000000000000000000000000000", "dydxBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", From 788bdba8cd5db02ebcac3bf9b22fc340b4e91772 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 4 Aug 2020 15:09:17 +1000 Subject: [PATCH 42/50] fix: asset-swapper source collapse regression (#2654) fixes source collapse regression where a split on the same source was collapsed into a single fill. This should be kept distinct as separate fills. --- packages/asset-swapper/CHANGELOG.json | 4 ++++ .../src/utils/market_operation_utils/fills.ts | 3 ++- .../src/utils/market_operation_utils/types.ts | 4 ++++ .../asset-swapper/test/quote_report_generator_test.ts | 11 ++++++----- packages/asset-swapper/test/quote_simulation_test.ts | 5 +++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 8c9b3d45d7..10fc717e7a 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -25,6 +25,10 @@ { "note": "Adds `getBidAskLiquidityForMakerTakerAssetPairAsync` to return more detailed sample information", "pr": 2641 + }, + { + "note": "Fix regression where a split on the same source was collapsed into a single fill", + "pr": 2654 } ] }, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index eb7b0aacff..05e9da0d72 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -247,7 +247,7 @@ export function collapsePath(path: Fill[]): CollapsedFill[] { if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) { const prevFill = collapsed[collapsed.length - 1]; // If the last fill is from the same source, merge them. - if (prevFill.source === source) { + if (prevFill.sourcePathId === fill.sourcePathId) { prevFill.input = prevFill.input.plus(fill.input); prevFill.output = prevFill.output.plus(fill.output); prevFill.subFills.push(fill); @@ -255,6 +255,7 @@ export function collapsePath(path: Fill[]): CollapsedFill[] { } } collapsed.push({ + sourcePathId: fill.sourcePathId, source: fill.source, fillData: fill.fillData, input: fill.input, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index a2b27ee401..f36f27f0ec 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -151,6 +151,10 @@ export interface Fill { * Represents continguous fills on a path that have been merged together. */ export interface CollapsedFill { + // Unique ID of the original source path this fill belongs to. + // This is generated when the path is generated and is useful to distinguish + // paths that have the same `source` IDs but are distinct (e.g., Curves). + sourcePathId: string; /** * The source DEX. */ diff --git a/packages/asset-swapper/test/quote_report_generator_test.ts b/packages/asset-swapper/test/quote_report_generator_test.ts index a4a1a6783f..e90b19eaf5 100644 --- a/packages/asset-swapper/test/quote_report_generator_test.ts +++ b/packages/asset-swapper/test/quote_report_generator_test.ts @@ -1,7 +1,7 @@ // tslint:disable:custom-no-magic-numbers import { orderHashUtils } from '@0x/order-utils'; import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; +import { BigNumber, hexUtils } from '@0x/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; @@ -31,6 +31,7 @@ const expect = chai.expect; const collapsedFillFromNativeOrder = (order: SignedOrder): NativeCollapsedFill => { return { + sourcePathId: hexUtils.random(), source: ERC20BridgeSource.Native, input: order.takerAssetAmount, output: order.makerAssetAmount, @@ -102,8 +103,8 @@ describe('QuoteReportGenerator', async () => { ]; // generate path - const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [] }; - const kyber2Fill: CollapsedFill = { ...kyberSample2, subFills: [] }; + const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [], sourcePathId: hexUtils.random() }; + const kyber2Fill: CollapsedFill = { ...kyberSample2, subFills: [], sourcePathId: hexUtils.random() }; const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2); const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2); const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, kyber2Fill]; @@ -274,8 +275,8 @@ describe('QuoteReportGenerator', async () => { // generate path const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1); - const uniswap1Fill: CollapsedFill = { ...uniswapSample1, subFills: [] }; - const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [] }; + const uniswap1Fill: CollapsedFill = { ...uniswapSample1, subFills: [], sourcePathId: hexUtils.random() }; + const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [], sourcePathId: hexUtils.random() }; const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, kyber1Fill]; const orderReport = new QuoteReportGenerator( diff --git a/packages/asset-swapper/test/quote_simulation_test.ts b/packages/asset-swapper/test/quote_simulation_test.ts index 8ec591e687..c934f23d79 100644 --- a/packages/asset-swapper/test/quote_simulation_test.ts +++ b/packages/asset-swapper/test/quote_simulation_test.ts @@ -1,6 +1,6 @@ import { constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; import { assetDataUtils } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; +import { BigNumber, hexUtils } from '@0x/utils'; import * as _ from 'lodash'; import { MarketOperation } from '../src/types'; @@ -155,7 +155,7 @@ describe('quote_simulation tests', async () => { signature: '0x', }; } - + const nativeSourcePathId = hexUtils.random(); function createOrderCollapsedFills(input: BigNumber, output: BigNumber, count: number): CollapsedFill[] { const inputs = subdivideAmount(input, count); const outputs = subdivideAmount(output, count); @@ -163,6 +163,7 @@ describe('quote_simulation tests', async () => { const subFillInputs = subdivideAmount(inputs[i], count); const subFillOutputs = subdivideAmount(outputs[i], count); return { + sourcePathId: nativeSourcePathId, source: ERC20BridgeSource.Native, input: inputs[i], output: outputs[i], From 2b39ae4800b96138521a106eb3c63b7a8f5e7e96 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Mon, 3 Aug 2020 11:39:04 -0700 Subject: [PATCH 43/50] Redeploy/verify testnet contracts :roll_eyes: --- contracts/asset-proxy/src/index.ts | 1 + .../contracts/src/DeploymentConstants.sol | 110 +++-- packages/contract-addresses/addresses.json | 102 ++--- packages/migrations/src/migration.ts | 421 ++++++++++-------- 4 files changed, 368 insertions(+), 266 deletions(-) diff --git a/contracts/asset-proxy/src/index.ts b/contracts/asset-proxy/src/index.ts index b95851265b..7e31c510b3 100644 --- a/contracts/asset-proxy/src/index.ts +++ b/contracts/asset-proxy/src/index.ts @@ -18,6 +18,7 @@ export { TestDydxBridgeContract, TestStaticCallTargetContract, UniswapBridgeContract, + DexForwarderBridgeContract, } from './wrappers'; export { ERC20Wrapper } from './erc20_wrapper'; diff --git a/contracts/utils/contracts/src/DeploymentConstants.sol b/contracts/utils/contracts/src/DeploymentConstants.sol index 7950ccc0e5..0f387fbf90 100644 --- a/contracts/utils/contracts/src/DeploymentConstants.sol +++ b/contracts/utils/contracts/src/DeploymentConstants.sol @@ -51,33 +51,89 @@ contract DeploymentConstants { /// @dev Mainnet address of the GST Collector address constant private GST_COLLECTOR_ADDRESS = 0x000000D3b08566BE75A6DB803C03C85C0c1c5B96; - // Kovan addresses ///////////////////////////////////////////////////////// - // /// @dev Kovan address of the WETH contract. - // address constant private WETH_ADDRESS = 0xd0A1E359811322d97991E03f863a0C30C2cF029C; - // /// @dev Kovan address of the KyberNetworkProxy contract. - // address constant private KYBER_NETWORK_PROXY_ADDRESS = 0x692f391bCc85cefCe8C237C01e1f636BbD70EA4D; - // /// @dev Kovan address of the `UniswapExchangeFactory` contract. - // address constant private UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30; - // /// @dev Kovan address of the `UniswapV2Router01` contract. - // address constant private UNISWAP_V2_ROUTER_01_ADDRESS = 0xf164fC0Ec4E93095b804a4795bBe1e041497b92a; - // /// @dev Kovan address of the Eth2Dai `MatchingMarket` contract. - // address constant private ETH2DAI_ADDRESS = 0xe325acB9765b02b8b418199bf9650972299235F4; - // /// @dev Kovan address of the `ERC20BridgeProxy` contract - // address constant private ERC20_BRIDGE_PROXY_ADDRESS = 0xFb2DD2A1366dE37f7241C83d47DA58fd503E2C64; - // /// @dev Kovan address of the `Chai` contract - // address constant private CHAI_ADDRESS = address(0); - // /// @dev Kovan address of the `Dai` (multi-collateral) contract - // address constant private DAI_ADDRESS = 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa; - // /// @dev Kovan address of the 0x DevUtils contract. - // address constant private DEV_UTILS_ADDRESS = 0x9402639A828BdF4E9e4103ac3B69E1a6E522eB59; - // /// @dev Kyber ETH pseudo-address. - // address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - // /// @dev Kovan address of the dYdX contract. - // address constant private DYDX_ADDRESS = address(0); - // /// @dev Kovan address of the GST2 contract - // address constant private GST_ADDRESS = address(0); - // /// @dev Kovan address of the GST Collector - // address constant private GST_COLLECTOR_ADDRESS = address(0); + /* // Ropsten addresses /////////////////////////////////////////////////////// + /// @dev Mainnet address of the WETH contract. + address constant private WETH_ADDRESS = 0xc778417E063141139Fce010982780140Aa0cD5Ab; + /// @dev Mainnet address of the KyberNetworkProxy contract. + address constant private KYBER_NETWORK_PROXY_ADDRESS = address(0); + /// @dev Mainnet address of the `UniswapExchangeFactory` contract. + address constant private UNISWAP_EXCHANGE_FACTORY_ADDRESS = address(0); + /// @dev Mainnet address of the `UniswapV2Router01` contract. + address constant private UNISWAP_V2_ROUTER_01_ADDRESS = address(0); + /// @dev Mainnet address of the Eth2Dai `MatchingMarket` contract. + address constant private ETH2DAI_ADDRESS = address(0); + /// @dev Mainnet address of the `ERC20BridgeProxy` contract + address constant private ERC20_BRIDGE_PROXY_ADDRESS = 0xb344afeD348de15eb4a9e180205A2B0739628339; + ///@dev Mainnet address of the `Dai` (multi-collateral) contract + address constant private DAI_ADDRESS = address(0); + /// @dev Mainnet address of the `Chai` contract + address constant private CHAI_ADDRESS = address(0); + /// @dev Mainnet address of the 0x DevUtils contract. + address constant private DEV_UTILS_ADDRESS = 0xC812AF3f3fBC62F76ea4262576EC0f49dB8B7f1c; + /// @dev Kyber ETH pseudo-address. + address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev Mainnet address of the dYdX contract. + address constant private DYDX_ADDRESS = address(0); + /// @dev Mainnet address of the GST2 contract + address constant private GST_ADDRESS = address(0); + /// @dev Mainnet address of the GST Collector + address constant private GST_COLLECTOR_ADDRESS = address(0); */ + + /* // Rinkeby addresses /////////////////////////////////////////////////////// + /// @dev Mainnet address of the WETH contract. + address constant private WETH_ADDRESS = 0xc778417E063141139Fce010982780140Aa0cD5Ab; + /// @dev Mainnet address of the KyberNetworkProxy contract. + address constant private KYBER_NETWORK_PROXY_ADDRESS = address(0); + /// @dev Mainnet address of the `UniswapExchangeFactory` contract. + address constant private UNISWAP_EXCHANGE_FACTORY_ADDRESS = address(0); + /// @dev Mainnet address of the `UniswapV2Router01` contract. + address constant private UNISWAP_V2_ROUTER_01_ADDRESS = address(0); + /// @dev Mainnet address of the Eth2Dai `MatchingMarket` contract. + address constant private ETH2DAI_ADDRESS = address(0); + /// @dev Mainnet address of the `ERC20BridgeProxy` contract + address constant private ERC20_BRIDGE_PROXY_ADDRESS = 0xA2AA4bEFED748Fba27a3bE7Dfd2C4b2c6DB1F49B; + ///@dev Mainnet address of the `Dai` (multi-collateral) contract + address constant private DAI_ADDRESS = address(0); + /// @dev Mainnet address of the `Chai` contract + address constant private CHAI_ADDRESS = address(0); + /// @dev Mainnet address of the 0x DevUtils contract. + address constant private DEV_UTILS_ADDRESS = 0x46B5BC959e8A754c0256FFF73bF34A52Ad5CdfA9; + /// @dev Kyber ETH pseudo-address. + address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev Mainnet address of the dYdX contract. + address constant private DYDX_ADDRESS = address(0); + /// @dev Mainnet address of the GST2 contract + address constant private GST_ADDRESS = address(0); + /// @dev Mainnet address of the GST Collector + address constant private GST_COLLECTOR_ADDRESS = address(0); */ + + /* // Kovan addresses ///////////////////////////////////////////////////////// + /// @dev Kovan address of the WETH contract. + address constant private WETH_ADDRESS = 0xd0A1E359811322d97991E03f863a0C30C2cF029C; + /// @dev Kovan address of the KyberNetworkProxy contract. + address constant private KYBER_NETWORK_PROXY_ADDRESS = 0x692f391bCc85cefCe8C237C01e1f636BbD70EA4D; + /// @dev Kovan address of the `UniswapExchangeFactory` contract. + address constant private UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30; + /// @dev Kovan address of the `UniswapV2Router01` contract. + address constant private UNISWAP_V2_ROUTER_01_ADDRESS = 0xf164fC0Ec4E93095b804a4795bBe1e041497b92a; + /// @dev Kovan address of the Eth2Dai `MatchingMarket` contract. + address constant private ETH2DAI_ADDRESS = 0xe325acB9765b02b8b418199bf9650972299235F4; + /// @dev Kovan address of the `ERC20BridgeProxy` contract + address constant private ERC20_BRIDGE_PROXY_ADDRESS = 0x3577552C1Fb7A44aD76BeEB7aB53251668A21F8D; + /// @dev Kovan address of the `Chai` contract + address constant private CHAI_ADDRESS = address(0); + /// @dev Kovan address of the `Dai` (multi-collateral) contract + address constant private DAI_ADDRESS = 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa; + /// @dev Kovan address of the 0x DevUtils contract. + address constant private DEV_UTILS_ADDRESS = 0x9402639A828BdF4E9e4103ac3B69E1a6E522eB59; + /// @dev Kyber ETH pseudo-address. + address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev Kovan address of the dYdX contract. + address constant private DYDX_ADDRESS = address(0); + /// @dev Kovan address of the GST2 contract + address constant private GST_ADDRESS = address(0); + /// @dev Kovan address of the GST Collector + address constant private GST_COLLECTOR_ADDRESS = address(0); */ /// @dev Overridable way to get the `KyberNetworkProxy` address. /// @return kyberAddress The `IKyberNetworkProxy` address. diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index fbfb520df2..18793fd4bb 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -47,25 +47,25 @@ } }, "3": { - "erc20Proxy": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa", - "erc721Proxy": "0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4", + "erc20Proxy": "0xf1ec7d0ba42f15fb5c9e3adbe86431973e44764c", + "erc721Proxy": "0x070efeb7e5ffa3d1a59d03a219539551ae60ba43", "zrxToken": "0xff67881f8d12f372d91baae9752eb3631ff0ed00", "etherToken": "0xc778417e063141139fce010982780140aa0cd5ab", "exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831", - "exchange": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64", + "exchange": "0x5d8c9ba74607d2cbc4176882a42d4ace891c1c00", "assetProxyOwner": "0x0000000000000000000000000000000000000000", "zeroExGovernor": "0x53993733d41a88ae86f77a18a024e5548ee26579", - "forwarder": "0x2127a60bedfba1c01857b09b8f24094049c48493", - "coordinatorRegistry": "0x403cc23e88c17c4652fb904784d1af640a6722d9", - "coordinator": "0x6ff734d96104965c9c1b0108f83abc46e6e501df", - "multiAssetProxy": "0xab8fbd189c569ccdee3a4d929bb7f557be4028f6", - "staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1", - "erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d", - "devUtils": "0x88e746ad9ab158210266e7765adbe1756c73cf84", - "zrxVault": "0xffd161026865ad8b4ab28a76840474935eec4dfa", - "staking": "0x986b588e472b712385579d172494fe2685669504", - "stakingProxy": "0xfaabcee42ab6b9c649794ac6c133711071897ee9", - "erc20BridgeProxy": "0x599b340b5045436a99b1f0c718d30f5a0c8519dd", + "forwarder": "0xd5abddda4ba89c0120edc0ca8a95ed1ad0bf9fc3", + "coordinatorRegistry": "0xf8becacec90bfc361c0a2c720839e08405a72f6d", + "coordinator": "0xc2e2f8faf4bf649123b6f94103646cb4a0331006", + "multiAssetProxy": "0x7b70a148e20b348c320208df84fdd642aab49fd0", + "staticCallProxy": "0xaa460127562482faa5df42f2c39a025cd4a1cc0a", + "erc1155Proxy": "0x7f10d80f2659aaae790ab03da12be11c4e6008c3", + "devUtils": "0xc812af3f3fbc62f76ea4262576ec0f49db8b7f1c", + "zrxVault": "0x38bbb9fb54a6b6d0376948bf3b2a7ed1e8aea6e8", + "staking": "0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01", + "stakingProxy": "0x6acab4c9c4e3a0c78435fdb5ad1719c95460a668", + "erc20BridgeProxy": "0xb344afed348de15eb4a9e180205a2b0739628339", "uniswapBridge": "0x0000000000000000000000000000000000000000", "uniswapV2Bridge": "0x0000000000000000000000000000000000000000", "eth2DaiBridge": "0x0000000000000000000000000000000000000000", @@ -74,11 +74,11 @@ "chaiBridge": "0x0000000000000000000000000000000000000000", "dydxBridge": "0x0000000000000000000000000000000000000000", "godsUnchainedValidator": "0xd4690a51044db77d91d7aa8f7a3a5ad5da331af0", - "broker": "0x4aa817c6f383c8e8ae77301d18ce48efb16fd2be", + "broker": "0x4022e3982f326455f0905de3dbc4449999baf2dc", "chainlinkStopLimit": "0x67a094cf028221ffdd93fc658f963151d05e2a74", "curveBridge": "0x1796cd592d19e3bcd744fbb025bb61a6d8cb2c09", "maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", - "dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", + "dexForwarderBridge": "0x3261ea1411a1a840aed708896f779e1b837c917e", "multiBridge": "0x0000000000000000000000000000000000000000", "balancerBridge": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", @@ -89,30 +89,30 @@ "transformers": { "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x43bea7eaca21a14a411274fb365707080ff25f80", + "fillQuoteTransformer": "0xfadbeff43a07dedeb69eda5590be1b78be3d1044", "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" } }, "4": { "exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831", - "exchange": "0x198805e9682fceec29413059b68550f92868c129", - "erc20Proxy": "0x2f5ae4f6106e89b4147651688a92256885c5f410", - "erc721Proxy": "0x7656d773e11ff7383a14dcf09a9c50990481cd10", + "exchange": "0xf8becacec90bfc361c0a2c720839e08405a72f6d", + "erc20Proxy": "0x070efeb7e5ffa3d1a59d03a219539551ae60ba43", + "erc721Proxy": "0x7f10d80f2659aaae790ab03da12be11c4e6008c3", "zrxToken": "0x8080c7e4b81ecf23aa6f877cfbfd9b0c228c6ffa", "etherToken": "0xc778417e063141139fce010982780140aa0cd5ab", "assetProxyOwner": "0x0000000000000000000000000000000000000000", "zeroExGovernor": "0x3f46b98061a3e1e1f41dff296ec19402c298f8a9", - "forwarder": "0x18571835c95a6d79b2f5c45b676ccd16f5fa34a1", - "coordinatorRegistry": "0x1084b6a398e47907bae43fec3ff4b677db6e4fee", - "coordinator": "0x70c5385ee5ee4629ef72abd169e888c8b4a12238", - "multiAssetProxy": "0xb34cde0ad3a83d04abebc0b66e75196f22216621", - "staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1", - "erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d", - "devUtils": "0x9402639a828bdf4e9e4103ac3b69e1a6e522eb59", - "zrxVault": "0xa5bf6ac73bc40790fc6ffc9dbbbce76c9176e224", - "staking": "0x7cbe3c09cba24f26db24d9100ee915fa9fa21f5b", - "stakingProxy": "0xc6ad5277ea225ac05e271eb14a7ebb480cd9dd9f", - "erc20BridgeProxy": "0x31b8653642110f17bdb1f719901d7e7d49b08141", + "forwarder": "0xe30f6166fe1cd5f0048abeed3d20360feb4a1fd8", + "coordinatorRegistry": "0xc2e2f8faf4bf649123b6f94103646cb4a0331006", + "coordinator": "0xf1ec7d0ba42f15fb5c9e3adbe86431973e44764c", + "multiAssetProxy": "0xb344afed348de15eb4a9e180205a2b0739628339", + "staticCallProxy": "0x7b70a148e20b348c320208df84fdd642aab49fd0", + "erc1155Proxy": "0xaa460127562482faa5df42f2c39a025cd4a1cc0a", + "devUtils": "0x46b5bc959e8a754c0256fff73bf34a52ad5cdfa9", + "zrxVault": "0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01", + "staking": "0x6acab4c9c4e3a0c78435fdb5ad1719c95460a668", + "stakingProxy": "0x781ee6683595f823208be6540a279f940e6af196", + "erc20BridgeProxy": "0xa2aa4befed748fba27a3be7dfd2c4b2c6db1f49b", "uniswapBridge": "0x0000000000000000000000000000000000000000", "uniswapV2Bridge": "0x0000000000000000000000000000000000000000", "eth2DaiBridge": "0x0000000000000000000000000000000000000000", @@ -121,7 +121,7 @@ "chaiBridge": "0x0000000000000000000000000000000000000000", "dydxBridge": "0x0000000000000000000000000000000000000000", "godsUnchainedValidator": "0x0000000000000000000000000000000000000000", - "broker": "0x0000000000000000000000000000000000000000", + "broker": "0x0dd2d6cabbd8ae7d2fe6840fa597a44b1a7e4747", "chainlinkStopLimit": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", "curveBridge": "0x1796cd592d19e3bcd744fbb025bb61a6d8cb2c09", "maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", @@ -136,43 +136,43 @@ "transformers": { "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x2013735f6df965494a0fbc292f84dd44debaba3e", + "fillQuoteTransformer": "0x454cC891dc428Be81d1d6Fd3dd7026a752FBFBc9", "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" } }, "42": { - "erc20Proxy": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e", - "erc721Proxy": "0x2a9127c745688a165106c11cd4d647d2220af821", + "erc20Proxy": "0xaa460127562482faa5df42f2c39a025cd4a1cc0a", + "erc721Proxy": "0x7b70a148e20b348c320208df84fdd642aab49fd0", "zrxToken": "0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa", "etherToken": "0xd0a1e359811322d97991e03f863a0c30c2cf029c", "exchangeV2": "0x30589010550762d2f0d06f650d8e8b6ade6dbf4b", - "exchange": "0x4eacd0af335451709e1e7b570b8ea68edec8bc97", + "exchange": "0xf1ec7d0ba42f15fb5c9e3adbe86431973e44764c", "assetProxyOwner": "0x0000000000000000000000000000000000000000", "zeroExGovernor": "0x6ff734d96104965c9c1b0108f83abc46e6e501df", - "forwarder": "0x01c0ecf5d1a22de07a2de84c322bfa2b5435990e", - "coordinatorRegistry": "0x09fb99968c016a3ff537bf58fb3d9fe55a7975d5", - "coordinator": "0xd29e59e51e8ab5f94121efaeebd935ca4214e257", - "multiAssetProxy": "0xf6313a772c222f51c28f2304c0703b8cf5428fd8", - "staticCallProxy": "0x48e94bdb9033640d45ea7c721e25f380f8bffa43", - "erc1155Proxy": "0x64517fa2b480ba3678a2a3c0cf08ef7fd4fad36f", - "devUtils": "0x9402639a828bdf4e9e4103ac3b69e1a6e522eb59", - "zrxVault": "0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291", - "staking": "0x32b06d5611a03737a5f1834a24ccd641033fd89c", - "stakingProxy": "0xbab9145f1d57cd4bb0c9aa2d1ece0a5b6e734d34", - "erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64", + "forwarder": "0x0f64646a5154ae5e58b6dd87ede7b04f508d76f8", + "coordinatorRegistry": "0x070efeb7e5ffa3d1a59d03a219539551ae60ba43", + "coordinator": "0x7f10d80f2659aaae790ab03da12be11c4e6008c3", + "multiAssetProxy": "0x58a01e826e60731247e7de8b446ed4c8535a099c", + "staticCallProxy": "0xa2aa4befed748fba27a3be7dfd2c4b2c6db1f49b", + "erc1155Proxy": "0xb344afed348de15eb4a9e180205a2b0739628339", + "devUtils": "0xc67ae71928568a180b3aad1339dedcf3076876fe", + "zrxVault": "0x781ee6683595f823208be6540a279f940e6af196", + "staking": "0x73ea24041e03a012c51a45c307e0ba376af0238c", + "stakingProxy": "0xe94cb304b3f515be7c95fedcfa249a84995fd748", + "erc20BridgeProxy": "0x3577552c1fb7a44ad76beeb7ab53251668a21f8d", "uniswapBridge": "0x0e85f89f29998df65402391478e5924700c0079d", "uniswapV2Bridge": "0x7b3530a635d099de0534dc27e46cd7c57578c3c8", "eth2DaiBridge": "0x2d47147429b474d2e4f83e658015858a1312ed5b", "erc20BridgeSampler": "0xcf9e66851f274aa4721e54526117876d90d51aa1", "kyberBridge": "0xaecfa25920f892b6eb496e1f6e84037f59da7f44", "chaiBridge": "0x0000000000000000000000000000000000000000", - "dydxBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", + "dydxBridge": "0xc213707de0454008758071c2edc1365621b8a5c5", "godsUnchainedValidator": "0x0000000000000000000000000000000000000000", - "broker": "0x0000000000000000000000000000000000000000", + "broker": "0xcdeb6d90ee7c96b4c713f7bb4f8604981f7ebe9d", "chainlinkStopLimit": "0x0000000000000000000000000000000000000000", "curveBridge": "0x81c0ab53a7352d2e97f682a37cba44e54647eefb", "maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74", - "dexForwarderBridge": "0xf220eb0b29e18bbc8ebc964e915b7547c7b4de4f", + "dexForwarderBridge": "0x985d1a95c6a86a3bf85c4d425af984abceaf01de", "multiBridge": "0x0000000000000000000000000000000000000000", "balancerBridge": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", @@ -183,7 +183,7 @@ "transformers": { "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0xbc33dd7a09da8ca943517a0fb786bcf0192f8be2", + "fillQuoteTransformer": "0xA8c8Cf29699F223766F47FE79e2B7eB1a90e08C8", "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" } }, diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 0646511e52..7f8f972e88 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -14,13 +14,10 @@ import { CoordinatorRegistryContract, } from '@0x/contracts-coordinator'; import { artifacts as devUtilsArtifacts, DevUtilsContract } from '@0x/contracts-dev-utils'; -import { artifacts as erc1155Artifacts, ERC1155MintableContract } from '@0x/contracts-erc1155'; -import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20'; -import { - artifacts as erc20BridgeSamplerArtifacts, - ERC20BridgeSamplerContract, -} from '@0x/contracts-erc20-bridge-sampler'; -import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721'; +import { artifacts as erc1155Artifacts } from '@0x/contracts-erc1155'; +import { artifacts as erc20Artifacts } from '@0x/contracts-erc20'; +import { artifacts as erc20BridgeSamplerArtifacts } from '@0x/contracts-erc20-bridge-sampler'; +import { artifacts as erc721Artifacts } from '@0x/contracts-erc721'; import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange'; import { artifacts as forwarderArtifacts, ForwarderContract } from '@0x/contracts-exchange-forwarder'; import { @@ -38,13 +35,13 @@ import { ITransformERC20Contract, PayTakerTransformerContract, WethTransformerContract, + ZeroExContract, } from '@0x/contracts-zero-ex'; -import { Web3ProviderEngine } from '@0x/subproviders'; +import { Web3ProviderEngine, ZeroExProvider } from '@0x/subproviders'; import { BigNumber, providerUtils } from '@0x/utils'; import { SupportedProvider, TxData } from 'ethereum-types'; import { constants } from './utils/constants'; -import { erc20TokenInfo, erc721TokenInfo } from './utils/token_info'; const allArtifacts = { ...assetProxyArtifacts, @@ -61,6 +58,53 @@ const allArtifacts = { }; const { NULL_ADDRESS } = constants; +const NULL_ADDRESSES = { + erc20Proxy: NULL_ADDRESS, + erc721Proxy: NULL_ADDRESS, + erc1155Proxy: NULL_ADDRESS, + zrxToken: NULL_ADDRESS, + etherToken: NULL_ADDRESS, + exchange: NULL_ADDRESS, + assetProxyOwner: NULL_ADDRESS, + erc20BridgeProxy: NULL_ADDRESS, + zeroExGovernor: NULL_ADDRESS, + forwarder: NULL_ADDRESS, + coordinatorRegistry: NULL_ADDRESS, + coordinator: NULL_ADDRESS, + multiAssetProxy: NULL_ADDRESS, + staticCallProxy: NULL_ADDRESS, + devUtils: NULL_ADDRESS, + exchangeV2: NULL_ADDRESS, + zrxVault: NULL_ADDRESS, + staking: NULL_ADDRESS, + stakingProxy: NULL_ADDRESS, + uniswapBridge: NULL_ADDRESS, + eth2DaiBridge: NULL_ADDRESS, + kyberBridge: NULL_ADDRESS, + erc20BridgeSampler: NULL_ADDRESS, + chaiBridge: NULL_ADDRESS, + dydxBridge: NULL_ADDRESS, + curveBridge: NULL_ADDRESS, + uniswapV2Bridge: NULL_ADDRESS, + godsUnchainedValidator: NULL_ADDRESS, + broker: NULL_ADDRESS, + chainlinkStopLimit: NULL_ADDRESS, + maximumGasPrice: NULL_ADDRESS, + dexForwarderBridge: NULL_ADDRESS, + multiBridge: NULL_ADDRESS, + balancerBridge: NULL_ADDRESS, + exchangeProxyGovernor: NULL_ADDRESS, + exchangeProxy: NULL_ADDRESS, + exchangeProxyAllowanceTarget: NULL_ADDRESS, + exchangeProxyTransformerDeployer: NULL_ADDRESS, + exchangeProxyFlashWallet: NULL_ADDRESS, + transformers: { + wethTransformer: NULL_ADDRESS, + payTakerTransformer: NULL_ADDRESS, + fillQuoteTransformer: NULL_ADDRESS, + affiliateFeeTransformer: NULL_ADDRESS, + }, +}; /** * Creates and deploys all the contracts that are required for the latest @@ -75,7 +119,135 @@ export async function runMigrationsAsync( ): Promise { const provider = providerUtils.standardizeOrThrow(supportedProvider); const chainId = new BigNumber(await providerUtils.getChainIdAsync(provider)); + const { exchangeV2, etherToken, zrxToken } = getContractAddressesForChainOrThrow(chainId.toNumber()); + // Exchange + const exchange = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeArtifacts.Exchange, + provider, + txDefaults, + allArtifacts, + chainId, + ); + + // Dev Utils + const devUtils = await DevUtilsContract.deployWithLibrariesFrom0xArtifactAsync( + devUtilsArtifacts.DevUtils, + devUtilsArtifacts, + provider, + txDefaults, + allArtifacts, + exchange.address, + NULL_ADDRESS, + NULL_ADDRESS, + ); + + // CoordinatorRegistry + const coordinatorRegistry = await CoordinatorRegistryContract.deployFrom0xArtifactAsync( + coordinatorArtifacts.CoordinatorRegistry, + provider, + txDefaults, + allArtifacts, + ); + + // Coordinator + const coordinator = await CoordinatorContract.deployFrom0xArtifactAsync( + coordinatorArtifacts.Coordinator, + provider, + txDefaults, + allArtifacts, + exchange.address, + chainId, + ); + + const [ + erc20Proxy, + erc721Proxy, + erc1155Proxy, + staticCallProxy, + multiAssetProxy, + erc20BridgeProxy, + ] = await _migrateAssetProxiesAsync(exchange, provider, txDefaults); + + const [zrxVault, stakingLogic, stakingProxy] = await _migrateStakingAsync( + exchange, + erc20Proxy, + zrxToken, + etherToken, + provider, + txDefaults, + ); + + // Forwarder + // Deployed after Exchange and Staking is configured as it queries + // in the constructor + const forwarder = await ForwarderContract.deployFrom0xArtifactAsync( + forwarderArtifacts.Forwarder, + provider, + txDefaults, + allArtifacts, + exchange.address, + exchangeV2, + etherToken, + ); + + const [ + exchangeProxy, + fillQuoteTransformer, + payTakerTransformer, + wethTransformer, + affiliateFeeTransformer, + exchangeProxyFlashWalletAddress, + exchangeProxyAllowanceTargetAddress, + ] = await _migrateExchangeProxyAsync(exchange, etherToken, provider, txDefaults); + + const contractAddresses = { + ...NULL_ADDRESSES, + erc20Proxy: erc20Proxy.address, + erc721Proxy: erc721Proxy.address, + erc1155Proxy: erc1155Proxy.address, + zrxToken, + etherToken, + exchange: exchange.address, + erc20BridgeProxy: erc20BridgeProxy.address, + forwarder: forwarder.address, + coordinatorRegistry: coordinatorRegistry.address, + coordinator: coordinator.address, + multiAssetProxy: multiAssetProxy.address, + staticCallProxy: staticCallProxy.address, + devUtils: devUtils.address, + exchangeV2: exchangeV2, + zrxVault: zrxVault.address, + staking: stakingLogic.address, + stakingProxy: stakingProxy.address, + exchangeProxy: exchangeProxy.address, + exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress, + exchangeProxyTransformerDeployer: txDefaults.from, + exchangeProxyFlashWallet: exchangeProxyFlashWalletAddress, + transformers: { + wethTransformer: wethTransformer.address, + payTakerTransformer: payTakerTransformer.address, + fillQuoteTransformer: fillQuoteTransformer.address, + affiliateFeeTransformer: affiliateFeeTransformer.address, + }, + }; + return contractAddresses; +} + +async function _migrateAssetProxiesAsync( + exchange: ExchangeContract, + provider: ZeroExProvider, + txDefaults: TxData, +): Promise< + [ + ERC20ProxyContract, + ERC721ProxyContract, + ERC1155ProxyContract, + StaticCallProxyContract, + MultiAssetProxyContract, + ERC20BridgeProxyContract + ] +> { // Proxies const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync( assetProxyArtifacts.ERC20Proxy, @@ -90,62 +262,6 @@ export async function runMigrationsAsync( allArtifacts, ); - // ZRX - const zrxToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( - erc20Artifacts.DummyERC20Token, - provider, - txDefaults, - allArtifacts, - '0x Protocol Token', - 'ZRX', - new BigNumber(18), - new BigNumber(1000000000000000000000000000), - ); - - // Ether token - const etherToken = await WETH9Contract.deployFrom0xArtifactAsync( - erc20Artifacts.WETH9, - provider, - txDefaults, - allArtifacts, - ); - - // Exchange - const exchange = await ExchangeContract.deployFrom0xArtifactAsync( - exchangeArtifacts.Exchange, - provider, - txDefaults, - allArtifacts, - chainId, - ); - - // Dummy ERC20 tokens - for (const token of erc20TokenInfo) { - const totalSupply = new BigNumber(1000000000000000000000000000); - // tslint:disable-next-line:no-unused-variable - const dummyErc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync( - erc20Artifacts.DummyERC20Token, - provider, - txDefaults, - allArtifacts, - token.name, - token.symbol, - token.decimals, - totalSupply, - ); - } - - // ERC721 - // tslint:disable-next-line:no-unused-variable - const cryptoKittieToken = await DummyERC721TokenContract.deployFrom0xArtifactAsync( - erc721Artifacts.DummyERC721Token, - provider, - txDefaults, - allArtifacts, - erc721TokenInfo[0].name, - erc721TokenInfo[0].symbol, - ); - // 1155 Asset Proxy const erc1155Proxy = await ERC1155ProxyContract.deployFrom0xArtifactAsync( assetProxyArtifacts.ERC1155Proxy, @@ -168,19 +284,29 @@ export async function runMigrationsAsync( allArtifacts, ); + const erc20BridgeProxy = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync( + assetProxyArtifacts.ERC20BridgeProxy, + provider, + txDefaults, + allArtifacts, + ); + await erc20Proxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); await erc721Proxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); await erc1155Proxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); await multiAssetProxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); + await erc20BridgeProxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); // MultiAssetProxy await erc20Proxy.addAuthorizedAddress(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); await erc721Proxy.addAuthorizedAddress(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); await erc1155Proxy.addAuthorizedAddress(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); + await erc20BridgeProxy.addAuthorizedAddress(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); await multiAssetProxy.registerAssetProxy(erc20Proxy.address).awaitTransactionSuccessAsync(txDefaults); await multiAssetProxy.registerAssetProxy(erc721Proxy.address).awaitTransactionSuccessAsync(txDefaults); await multiAssetProxy.registerAssetProxy(erc1155Proxy.address).awaitTransactionSuccessAsync(txDefaults); await multiAssetProxy.registerAssetProxy(staticCallProxy.address).awaitTransactionSuccessAsync(txDefaults); + await multiAssetProxy.registerAssetProxy(erc20BridgeProxy.address).awaitTransactionSuccessAsync(txDefaults); // Register the Asset Proxies to the Exchange await exchange.registerAssetProxy(erc20Proxy.address).awaitTransactionSuccessAsync(txDefaults); @@ -188,64 +314,26 @@ export async function runMigrationsAsync( await exchange.registerAssetProxy(erc1155Proxy.address).awaitTransactionSuccessAsync(txDefaults); await exchange.registerAssetProxy(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); await exchange.registerAssetProxy(staticCallProxy.address).awaitTransactionSuccessAsync(txDefaults); - - // CoordinatorRegistry - const coordinatorRegistry = await CoordinatorRegistryContract.deployFrom0xArtifactAsync( - coordinatorArtifacts.CoordinatorRegistry, - provider, - txDefaults, - allArtifacts, - ); - - // Coordinator - const coordinator = await CoordinatorContract.deployFrom0xArtifactAsync( - coordinatorArtifacts.Coordinator, - provider, - txDefaults, - allArtifacts, - exchange.address, - chainId, - ); - - // Dev Utils - const devUtils = await DevUtilsContract.deployWithLibrariesFrom0xArtifactAsync( - devUtilsArtifacts.DevUtils, - devUtilsArtifacts, - provider, - txDefaults, - allArtifacts, - exchange.address, - NULL_ADDRESS, - NULL_ADDRESS, - ); - - // tslint:disable-next-line:no-unused-variable - const erc1155DummyToken = await ERC1155MintableContract.deployFrom0xArtifactAsync( - erc1155Artifacts.ERC1155Mintable, - provider, - txDefaults, - allArtifacts, - ); - - const erc20BridgeProxy = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync( - assetProxyArtifacts.ERC20BridgeProxy, - provider, - txDefaults, - allArtifacts, - ); await exchange.registerAssetProxy(erc20BridgeProxy.address).awaitTransactionSuccessAsync(txDefaults); - await erc20BridgeProxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); - await erc20BridgeProxy.addAuthorizedAddress(multiAssetProxy.address).awaitTransactionSuccessAsync(txDefaults); - await multiAssetProxy.registerAssetProxy(erc20BridgeProxy.address).awaitTransactionSuccessAsync(txDefaults); - const zrxProxy = erc20Proxy.address; + return [erc20Proxy, erc721Proxy, erc1155Proxy, staticCallProxy, multiAssetProxy, erc20BridgeProxy]; +} + +async function _migrateStakingAsync( + exchange: ExchangeContract, + erc20Proxy: ERC20ProxyContract, + zrxTokenAddress: string, + etherTokenAddress: string, + provider: ZeroExProvider, + txDefaults: TxData, +): Promise<[ZrxVaultContract, TestStakingContract, StakingProxyContract]> { const zrxVault = await ZrxVaultContract.deployFrom0xArtifactAsync( stakingArtifacts.ZrxVault, provider, txDefaults, allArtifacts, - zrxProxy, - zrxToken.address, + erc20Proxy.address, + zrxTokenAddress, ); // Note we use TestStakingContract as the deployed bytecode of a StakingContract @@ -255,7 +343,7 @@ export async function runMigrationsAsync( provider, txDefaults, allArtifacts, - etherToken.address, + etherTokenAddress, zrxVault.address, ); @@ -281,29 +369,25 @@ export async function runMigrationsAsync( await stakingLogic.addAuthorizedAddress(txDefaults.from).awaitTransactionSuccessAsync(txDefaults); await stakingLogic.addExchangeAddress(exchange.address).awaitTransactionSuccessAsync(txDefaults); - // Forwarder - // Deployed after Exchange and Staking is configured as it queries - // in the constructor - const { exchangeV2: exchangeV2Address } = getContractAddressesForChainOrThrow(chainId.toNumber()); - const forwarder = await ForwarderContract.deployFrom0xArtifactAsync( - forwarderArtifacts.Forwarder, - provider, - txDefaults, - allArtifacts, - exchange.address, - exchangeV2Address || NULL_ADDRESS, - etherToken.address, - ); - - const erc20BridgeSampler = await ERC20BridgeSamplerContract.deployFrom0xArtifactAsync( - erc20BridgeSamplerArtifacts.ERC20BridgeSampler, - provider, - txDefaults, - allArtifacts, - ); - - // Exchange Proxy ////////////////////////////////////////////////////////// + return [zrxVault, stakingLogic, stakingProxy]; +} +async function _migrateExchangeProxyAsync( + exchange: ExchangeContract, + etherTokenAddress: string, + provider: ZeroExProvider, + txDefaults: TxData, +): Promise< + [ + ZeroExContract, + FillQuoteTransformerContract, + PayTakerTransformerContract, + WethTransformerContract, + AffiliateFeeTransformerContract, + string, + string + ] +> { const exchangeProxy = await fullMigrateExchangeProxyAsync(txDefaults.from, provider, txDefaults); const exchangeProxyAllowanceTargetAddress = await new ITokenSpenderContract( exchangeProxy.address, @@ -335,7 +419,7 @@ export async function runMigrationsAsync( provider, txDefaults, allArtifacts, - etherToken.address, + etherTokenAddress, ); const affiliateFeeTransformer = await AffiliateFeeTransformerContract.deployFrom0xArtifactAsync( exchangeProxyArtifacts.AffiliateFeeTransformer, @@ -344,54 +428,15 @@ export async function runMigrationsAsync( allArtifacts, ); - const contractAddresses = { - erc20Proxy: erc20Proxy.address, - erc721Proxy: erc721Proxy.address, - erc1155Proxy: erc1155Proxy.address, - zrxToken: zrxToken.address, - etherToken: etherToken.address, - exchange: exchange.address, - assetProxyOwner: NULL_ADDRESS, - erc20BridgeProxy: erc20BridgeProxy.address, - zeroExGovernor: NULL_ADDRESS, - forwarder: forwarder.address, - coordinatorRegistry: coordinatorRegistry.address, - coordinator: coordinator.address, - multiAssetProxy: multiAssetProxy.address, - staticCallProxy: staticCallProxy.address, - devUtils: devUtils.address, - exchangeV2: exchangeV2Address || NULL_ADDRESS, - zrxVault: zrxVault.address, - staking: stakingLogic.address, - stakingProxy: stakingProxy.address, - uniswapBridge: NULL_ADDRESS, - eth2DaiBridge: NULL_ADDRESS, - kyberBridge: NULL_ADDRESS, - erc20BridgeSampler: erc20BridgeSampler.address, - chaiBridge: NULL_ADDRESS, - dydxBridge: NULL_ADDRESS, - curveBridge: NULL_ADDRESS, - uniswapV2Bridge: NULL_ADDRESS, - godsUnchainedValidator: NULL_ADDRESS, - broker: NULL_ADDRESS, - chainlinkStopLimit: NULL_ADDRESS, - maximumGasPrice: NULL_ADDRESS, - dexForwarderBridge: NULL_ADDRESS, - multiBridge: NULL_ADDRESS, - balancerBridge: NULL_ADDRESS, - exchangeProxyGovernor: NULL_ADDRESS, - exchangeProxy: exchangeProxy.address, - exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress, - exchangeProxyTransformerDeployer: txDefaults.from, - exchangeProxyFlashWallet: exchangeProxyFlashWalletAddress, - transformers: { - wethTransformer: wethTransformer.address, - payTakerTransformer: payTakerTransformer.address, - fillQuoteTransformer: fillQuoteTransformer.address, - affiliateFeeTransformer: affiliateFeeTransformer.address, - }, - }; - return contractAddresses; + return [ + exchangeProxy, + fillQuoteTransformer, + payTakerTransformer, + wethTransformer, + affiliateFeeTransformer, + exchangeProxyFlashWalletAddress, + exchangeProxyAllowanceTargetAddress, + ]; } let _cachedContractAddresses: ContractAddresses; From b10522479c5fe2b70480817e9af7812be39a4ff6 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 4 Aug 2020 11:13:09 -0700 Subject: [PATCH 44/50] Update changelogs --- contracts/asset-proxy/CHANGELOG.json | 4 ++++ contracts/utils/CHANGELOG.json | 9 +++++++++ packages/contract-addresses/CHANGELOG.json | 4 ++++ packages/migrations/CHANGELOG.json | 4 ++++ 4 files changed, 21 insertions(+) diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index 37402629cf..bdd0538064 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Update `CurveBridge` to support more varied curves", "pr": 2633 + }, + { + "note": "Export DexForwarderBridgeContract", + "pr": 2656 } ] }, diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index 51e56d1a17..a64aa17943 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.5.2", + "changes": [ + { + "note": "Add Ropsten and Rinkeby addresses to `DeploymentConstants`", + "pr": 2656 + } + ] + }, { "version": "4.5.1", "changes": [ diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 3b64d634ed..0031849696 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -13,6 +13,10 @@ { "note": "Deploy `UniswapV2Bridge` on Kovan", "pr": 2652 + }, + { + "note": "Redeploy previously unverified contracts on testnets", + "pr": 2656 } ] }, diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 02e7c3a6e0..e2b39f9841 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Change test protocol fee to 70000.", "pr": 2637 + }, + { + "note": "Refactor `migration.ts` a little", + "pr": 2656 } ] }, From 2c73bbd689e40eb97fb3ff8e40b7904c39f4ab30 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 4 Aug 2020 11:30:43 -0700 Subject: [PATCH 45/50] appease CI --- .../test/swap_quote_consumer_utils_test.ts | 1 + packages/migrations/src/migration.ts | 93 ++++++++++++++----- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts b/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts index 3fe2fe4919..a4022a5407 100644 --- a/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts +++ b/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts @@ -121,6 +121,7 @@ describe('swapQuoteConsumerUtils', () => { swapQuoteConsumer = new SwapQuoteConsumer(provider, { chainId, + contractAddresses, }); }); after(async () => { diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 7f8f972e88..67af1f6f78 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -15,9 +15,9 @@ import { } from '@0x/contracts-coordinator'; import { artifacts as devUtilsArtifacts, DevUtilsContract } from '@0x/contracts-dev-utils'; import { artifacts as erc1155Artifacts } from '@0x/contracts-erc1155'; -import { artifacts as erc20Artifacts } from '@0x/contracts-erc20'; +import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20'; import { artifacts as erc20BridgeSamplerArtifacts } from '@0x/contracts-erc20-bridge-sampler'; -import { artifacts as erc721Artifacts } from '@0x/contracts-erc721'; +import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721'; import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange'; import { artifacts as forwarderArtifacts, ForwarderContract } from '@0x/contracts-exchange-forwarder'; import { @@ -42,6 +42,7 @@ import { BigNumber, providerUtils } from '@0x/utils'; import { SupportedProvider, TxData } from 'ethereum-types'; import { constants } from './utils/constants'; +import { erc20TokenInfo, erc721TokenInfo } from './utils/token_info'; const allArtifacts = { ...assetProxyArtifacts, @@ -119,7 +120,28 @@ export async function runMigrationsAsync( ): Promise { const provider = providerUtils.standardizeOrThrow(supportedProvider); const chainId = new BigNumber(await providerUtils.getChainIdAsync(provider)); - const { exchangeV2, etherToken, zrxToken } = getContractAddressesForChainOrThrow(chainId.toNumber()); + const { exchangeV2 } = getContractAddressesForChainOrThrow(chainId.toNumber()); + + const zrxToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( + erc20Artifacts.DummyERC20Token, + provider, + txDefaults, + allArtifacts, + '0x Protocol Token', + 'ZRX', + new BigNumber(18), + new BigNumber(1000000000000000000000000000), + ); + + // Ether token + const etherToken = await WETH9Contract.deployFrom0xArtifactAsync( + erc20Artifacts.WETH9, + provider, + txDefaults, + allArtifacts, + ); + + await _migrateDummyTokensAsync(provider, txDefaults); // Exchange const exchange = await ExchangeContract.deployFrom0xArtifactAsync( @@ -130,18 +152,6 @@ export async function runMigrationsAsync( chainId, ); - // Dev Utils - const devUtils = await DevUtilsContract.deployWithLibrariesFrom0xArtifactAsync( - devUtilsArtifacts.DevUtils, - devUtilsArtifacts, - provider, - txDefaults, - allArtifacts, - exchange.address, - NULL_ADDRESS, - NULL_ADDRESS, - ); - // CoordinatorRegistry const coordinatorRegistry = await CoordinatorRegistryContract.deployFrom0xArtifactAsync( coordinatorArtifacts.CoordinatorRegistry, @@ -169,11 +179,23 @@ export async function runMigrationsAsync( erc20BridgeProxy, ] = await _migrateAssetProxiesAsync(exchange, provider, txDefaults); + // Dev Utils + const devUtils = await DevUtilsContract.deployWithLibrariesFrom0xArtifactAsync( + devUtilsArtifacts.DevUtils, + devUtilsArtifacts, + provider, + txDefaults, + allArtifacts, + exchange.address, + NULL_ADDRESS, + NULL_ADDRESS, + ); + const [zrxVault, stakingLogic, stakingProxy] = await _migrateStakingAsync( exchange, erc20Proxy, - zrxToken, - etherToken, + zrxToken.address, + etherToken.address, provider, txDefaults, ); @@ -188,7 +210,7 @@ export async function runMigrationsAsync( allArtifacts, exchange.address, exchangeV2, - etherToken, + etherToken.address, ); const [ @@ -199,15 +221,15 @@ export async function runMigrationsAsync( affiliateFeeTransformer, exchangeProxyFlashWalletAddress, exchangeProxyAllowanceTargetAddress, - ] = await _migrateExchangeProxyAsync(exchange, etherToken, provider, txDefaults); + ] = await _migrateExchangeProxyAsync(exchange, etherToken.address, provider, txDefaults); const contractAddresses = { ...NULL_ADDRESSES, erc20Proxy: erc20Proxy.address, erc721Proxy: erc721Proxy.address, erc1155Proxy: erc1155Proxy.address, - zrxToken, - etherToken, + zrxToken: zrxToken.address, + etherToken: etherToken.address, exchange: exchange.address, erc20BridgeProxy: erc20BridgeProxy.address, forwarder: forwarder.address, @@ -216,7 +238,6 @@ export async function runMigrationsAsync( multiAssetProxy: multiAssetProxy.address, staticCallProxy: staticCallProxy.address, devUtils: devUtils.address, - exchangeV2: exchangeV2, zrxVault: zrxVault.address, staking: stakingLogic.address, stakingProxy: stakingProxy.address, @@ -439,6 +460,33 @@ async function _migrateExchangeProxyAsync( ]; } +async function _migrateDummyTokensAsync(provider: ZeroExProvider, txDefaults: TxData): Promise { + // Dummy ERC20 tokens + for (const token of erc20TokenInfo) { + const totalSupply = new BigNumber(1000000000000000000000000000); + await DummyERC20TokenContract.deployFrom0xArtifactAsync( + erc20Artifacts.DummyERC20Token, + provider, + txDefaults, + allArtifacts, + token.name, + token.symbol, + token.decimals, + totalSupply, + ); + } + + // Dummy ERC721 token + await DummyERC721TokenContract.deployFrom0xArtifactAsync( + erc721Artifacts.DummyERC721Token, + provider, + txDefaults, + allArtifacts, + erc721TokenInfo[0].name, + erc721TokenInfo[0].symbol, + ); +} + let _cachedContractAddresses: ContractAddresses; /** @@ -459,3 +507,4 @@ export async function runMigrationsOnceAsync( _cachedContractAddresses = await runMigrationsAsync(provider, txDefaults); return _cachedContractAddresses; } +// tslint:disable-next-line: max-file-line-count From 341c5782e576555ec3029f20c830a4fc719f50c4 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Wed, 5 Aug 2020 22:39:21 -0700 Subject: [PATCH 46/50] `@0x/asset-swapper`: Add affiliate fee support to EP swap quote consumer --- packages/asset-swapper/src/constants.ts | 21 ++++++++- packages/asset-swapper/src/index.ts | 1 + .../exchange_proxy_swap_quote_consumer.ts | 45 +++++++++++++++---- packages/asset-swapper/src/types.ts | 8 ++++ .../src/utils/swap_quote_calculator.ts | 8 +--- ...exchange_proxy_swap_quote_consumer_test.ts | 35 ++++++++++++++- .../src/transformer_data_encoders.ts | 2 +- 7 files changed, 101 insertions(+), 19 deletions(-) diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index c7dc690e32..d24ff0154a 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -1,6 +1,7 @@ import { BigNumber } from '@0x/utils'; import { + ExchangeProxyContractOpts, ExtensionContractType, ForwarderExtensionContractOpts, OrderPrunerOpts, @@ -20,6 +21,7 @@ const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; const MAINNET_CHAIN_ID = 1; const ONE_SECOND_MS = 1000; const DEFAULT_PER_PAGE = 1000; +const ZERO_AMOUNT = new BigNumber(0); const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = { expiryBufferMs: 120000, // 2 minutes @@ -60,8 +62,23 @@ const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts = { extensionContractOpts: DEFAULT_FORWARDER_EXTENSION_CONTRACT_OPTS, }; +const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = { + isFromETH: false, + isToETH: false, + affiliateFee: { + recipient: NULL_ADDRESS, + buyTokenFeeAmount: ZERO_AMOUNT, + sellTokenFeeAmount: ZERO_AMOUNT, + }, +}; + const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS; +const DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts = { + useExtensionContract: ExtensionContractType.ExchangeProxy, + extensionContractOpts: DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS, +}; + const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, }; @@ -74,7 +91,7 @@ export const constants = { ETH_GAS_STATION_API_URL, PROTOCOL_FEE_MULTIPLIER, NULL_BYTES, - ZERO_AMOUNT: new BigNumber(0), + ZERO_AMOUNT, NULL_ADDRESS, MAINNET_CHAIN_ID, DEFAULT_ORDER_PRUNER_OPTS, @@ -85,6 +102,8 @@ export const constants = { DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS, DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS, DEFAULT_SWAP_QUOTE_REQUEST_OPTS, + DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS, + DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS, DEFAULT_PER_PAGE, DEFAULT_RFQT_REQUEST_OPTS, NULL_ERC20_ASSET_DATA, diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index a7ffc0cf2c..1f75620bd2 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -40,6 +40,7 @@ export { InsufficientAssetLiquidityError } from './errors'; export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer'; export { SwapQuoter } from './swap_quoter'; export { + AffiliateFee, CalldataInfo, ExchangeProxyContractOpts, ExtensionContractType, diff --git a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts index 89dc4a95d6..5fb2c44f63 100644 --- a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts @@ -2,6 +2,7 @@ import { ContractAddresses } from '@0x/contract-addresses'; import { ITransformERC20Contract } from '@0x/contract-wrappers'; import { assetDataUtils, + encodeAffiliateFeeTransformerData, encodeFillQuoteTransformerData, encodePayTakerTransformerData, encodeWethTransformerData, @@ -18,6 +19,7 @@ import * as _ from 'lodash'; import { constants } from '../constants'; import { CalldataInfo, + ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote, @@ -41,6 +43,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { wethTransformer: number; payTakerTransformer: number; fillQuoteTransformer: number; + affiliateFeeTransformer: number; }; private readonly _transformFeature: ITransformERC20Contract; @@ -70,6 +73,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { contractAddresses.transformers.fillQuoteTransformer, contractAddresses.exchangeProxyTransformerDeployer, ), + affiliateFeeTransformer: findTransformerNonce( + contractAddresses.transformers.affiliateFeeTransformer, + contractAddresses.exchangeProxyTransformerDeployer, + ), }; } @@ -78,14 +85,11 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { opts: Partial = {}, ): Promise { assert.isValidSwapQuote('quote', quote); - const { isFromETH, isToETH } = { - ...constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS, - extensionContractOpts: { - isFromETH: false, - isToETH: false, - }, - ...opts, - }.extensionContractOpts; + // tslint:disable-next-line:no-object-literal-type-assertion + const { affiliateFee, isFromETH, isToETH } = { + ...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS, + ...opts.extensionContractOpts, + } as ExchangeProxyContractOpts; const sellToken = getTokenFromAssetData(quote.takerAssetData); const buyToken = getTokenFromAssetData(quote.makerAssetData); @@ -129,6 +133,28 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { }); } + // This transformer pays affiliate fees. + const { buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee; + + if (buyTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) { + transforms.push({ + deploymentNonce: this.transformerNonces.affiliateFeeTransformer, + data: encodeAffiliateFeeTransformerData({ + fees: [ + { + token: isToETH ? ETH_TOKEN_ADDRESS : buyToken, + amount: buyTokenFeeAmount, + recipient: feeRecipient, + }, + ], + }), + }); + } + + if (sellTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) { + throw new Error('Affiliate fees denominated in sell token are not yet supported'); + } + // The final transformer will send all funds to the taker. transforms.push({ deploymentNonce: this.transformerNonces.payTakerTransformer, @@ -138,12 +164,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { }), }); + const minBuyAmount = BigNumber.max(0, quote.worstCaseQuoteInfo.makerAssetAmount.minus(buyTokenFeeAmount)); const calldataHexString = this._transformFeature .transformERC20( isFromETH ? ETH_TOKEN_ADDRESS : sellToken, isToETH ? ETH_TOKEN_ADDRESS : buyToken, sellAmount, - quote.worstCaseQuoteInfo.makerAssetAmount, + minBuyAmount, transforms, ) .getABIEncodedTransactionData(); diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index dc496342b4..bd193ef406 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -124,13 +124,21 @@ export interface ForwarderExtensionContractOpts { feeRecipient: string; } +export interface AffiliateFee { + recipient: string; + buyTokenFeeAmount: BigNumber; + sellTokenFeeAmount: BigNumber; +} + /** * @param isFromETH Whether the input token is ETH. * @param isToETH Whether the output token is ETH. + * @param affiliateFee Fee denominated in taker or maker asset to send to specified recipient. */ export interface ExchangeProxyContractOpts { isFromETH: boolean; isToETH: boolean; + affiliateFee: AffiliateFee; } export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote; diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index 47646007bd..23f406edd8 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -16,13 +16,7 @@ import { import { MarketOperationUtils } from './market_operation_utils'; import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; -import { - FeeSchedule, - FillData, - GetMarketOrdersOpts, - OptimizedMarketOrder, - OptimizedOrdersAndQuoteReport, -} from './market_operation_utils/types'; +import { FeeSchedule, FillData, GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types'; import { isSupportedAssetDataInOrders } from './utils'; import { QuoteReport } from './quote_report_generator'; diff --git a/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts b/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts index 156e09e905..03052506fc 100644 --- a/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts +++ b/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts @@ -2,6 +2,7 @@ import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses'; import { constants as contractConstants, getRandomInteger, Numberish, randomAddress } from '@0x/contracts-test-utils'; import { assetDataUtils, + decodeAffiliateFeeTransformerData, decodeFillQuoteTransformerData, decodePayTakerTransformerData, decodeWethTransformerData, @@ -28,7 +29,7 @@ chaiSetup.configure(); const expect = chai.expect; const { NULL_ADDRESS } = constants; -const { MAX_UINT256 } = contractConstants; +const { MAX_UINT256, ZERO_AMOUNT } = contractConstants; // tslint:disable: custom-no-magic-numbers @@ -265,5 +266,37 @@ describe('ExchangeProxySwapQuoteConsumer', () => { expect(wethTransformerData.amount).to.bignumber.eq(MAX_UINT256); expect(wethTransformerData.token).to.eq(contractAddresses.etherToken); }); + it('Appends an affiliate fee transformer after the fill if a buy token affiliate feeĀ is provided', async () => { + const quote = getRandomSellQuote(); + const affiliateFee = { + recipient: randomAddress(), + buyTokenFeeAmount: getRandomAmount(), + sellTokenFeeAmount: ZERO_AMOUNT, + }; + const callInfo = await consumer.getCalldataOrThrowAsync(quote, { + extensionContractOpts: { affiliateFee }, + }); + const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs; + expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq( + consumer.transformerNonces.affiliateFeeTransformer, + ); + const affiliateFeeTransformerData = decodeAffiliateFeeTransformerData(callArgs.transformations[1].data); + expect(affiliateFeeTransformerData.fees).to.deep.equal([ + { token: MAKER_TOKEN, amount: affiliateFee.buyTokenFeeAmount, recipient: affiliateFee.recipient }, + ]); + }); + it('Throws if a sell token affiliate fee is provided', async () => { + const quote = getRandomSellQuote(); + const affiliateFee = { + recipient: randomAddress(), + buyTokenFeeAmount: ZERO_AMOUNT, + sellTokenFeeAmount: getRandomAmount(), + }; + expect( + consumer.getCalldataOrThrowAsync(quote, { + extensionContractOpts: { affiliateFee }, + }), + ).to.eventually.be.rejectedWith('Affiliate fees denominated in sell token are not yet supported'); + }); }); }); diff --git a/packages/order-utils/src/transformer_data_encoders.ts b/packages/order-utils/src/transformer_data_encoders.ts index 00a7831f61..cb3bb17d11 100644 --- a/packages/order-utils/src/transformer_data_encoders.ts +++ b/packages/order-utils/src/transformer_data_encoders.ts @@ -183,5 +183,5 @@ export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerD * ABI-decode a `AffiliateFeeTransformer.TransformData` type. */ export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData { - return affiliateFeeTransformerDataEncoder.decode(encoded).data; + return affiliateFeeTransformerDataEncoder.decode(encoded); } From e166e9762af6b8b4614a7bd96c3648a878adb94c Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Thu, 6 Aug 2020 09:38:33 -0700 Subject: [PATCH 47/50] update changelogs --- packages/asset-swapper/CHANGELOG.json | 4 ++++ packages/order-utils/CHANGELOG.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 10fc717e7a..72ebb1b118 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -29,6 +29,10 @@ { "note": "Fix regression where a split on the same source was collapsed into a single fill", "pr": 2654 + }, + { + "note": "Add support for buy token affiliate fees", + "pr": 2658 } ] }, diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index e6d884c4a9..be2bacc702 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add gitpkg.", "pr": 2649 + }, + { + "note": "Fix `decodeAffiliateFeeTransformerData`", + "pr": 2658 } ] }, From c03f1586e6a352403f1e6d01cd28e02e06544fc4 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 6 Aug 2020 23:03:52 -0400 Subject: [PATCH 48/50] asset-swapper: Fix optimization of buy paths (#2655) * `@0x/asset-swapper`: Fix optimization of buy paths * `@0x/asset-swapper`: Fix optimization of buy paths * `@0x/asset-swapper`: Optimize the optimizer. * `@0x/asset-swapper`: Remove unused `Fill` fields Co-authored-by: Lawrence Forman --- packages/asset-swapper/CHANGELOG.json | 4 + .../src/utils/market_operation_utils/fills.ts | 62 ++++++++--- .../market_operation_utils/path_optimizer.ts | 105 ++++++++++-------- .../src/utils/market_operation_utils/types.ts | 4 - .../test/market_operation_utils_test.ts | 10 +- 5 files changed, 115 insertions(+), 70 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 72ebb1b118..0a03df5f06 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -33,6 +33,10 @@ { "note": "Add support for buy token affiliate fees", "pr": 2658 + }, + { + "note": "Fix optimization of buy paths", + "pr": 2655 } ] }, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index 05e9da0d72..ccc015b33e 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -58,7 +58,7 @@ function nativeOrdersToPath( ): Fill[] { const sourcePathId = hexUtils.random(); // Create a single path from all orders. - let path: Fill[] = []; + let path: Array = []; for (const order of orders) { const makerAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterOrderFees(order); const takerAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterOrderFees(order); @@ -67,7 +67,6 @@ function nativeOrdersToPath( const penalty = ethToOutputRate.times( fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(), ); - const rate = makerAmount.div(takerAmount); // targetInput can be less than the order size // whilst the penalty is constant, it affects the adjusted output // only up until the target has been exhausted. @@ -86,11 +85,10 @@ function nativeOrdersToPath( } path.push({ sourcePathId, - input: clippedInput, - output: clippedOutput, - rate, adjustedRate, adjustedOutput, + input: clippedInput, + output: clippedOutput, flags: 0, index: 0, // TBD parent: undefined, // TBD @@ -135,15 +133,11 @@ function dexQuotesToPaths( ? ethToOutputRate.times(fee) : ZERO_AMOUNT; const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); - const rate = side === MarketOperation.Sell ? output.div(input) : input.div(output); - const adjustedRate = side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput); path.push({ sourcePathId, input, output, - rate, - adjustedRate, adjustedOutput, source, fillData, @@ -193,8 +187,12 @@ export function getPathAdjustedSize(path: Fill[], targetInput: BigNumber = POSIT for (const fill of path) { if (input.plus(fill.input).gte(targetInput)) { const di = targetInput.minus(input); - input = input.plus(di); - output = output.plus(fill.adjustedOutput.times(di.div(fill.input))); + if (di.gt(0)) { + input = input.plus(di); + // Penalty does not get interpolated. + const penalty = fill.adjustedOutput.minus(fill.output); + output = output.plus(fill.output.times(di.div(fill.input)).plus(penalty)); + } break; } else { input = input.plus(fill.input); @@ -223,6 +221,10 @@ export function isValidPath(path: Fill[], skipDuplicateCheck: boolean = false): } flags |= path[i].flags; } + return arePathFlagsAllowed(flags); +} + +export function arePathFlagsAllowed(flags: number): boolean { const multiBridgeConflict = FillFlags.MultiBridge | FillFlags.ConflictsWithMultiBridge; return (flags & multiBridgeConflict) !== multiBridgeConflict; } @@ -266,12 +268,14 @@ export function collapsePath(path: Fill[]): CollapsedFill[] { return collapsed; } +export function getPathAdjustedCompleteRate(side: MarketOperation, path: Fill[], targetInput: BigNumber): BigNumber { + const [input, output] = getPathAdjustedSize(path, targetInput); + return getCompleteRate(side, input, output, targetInput); +} + export function getPathAdjustedRate(side: MarketOperation, path: Fill[], targetInput: BigNumber): BigNumber { - const [, output] = getPathAdjustedSize(path, targetInput); - if (output.eq(0)) { - return ZERO_AMOUNT; - } - return side === MarketOperation.Sell ? output.div(targetInput) : targetInput.div(output); + const [input, output] = getPathAdjustedSize(path, targetInput); + return getRate(side, input, output); } export function getPathAdjustedSlippage( @@ -287,3 +291,29 @@ export function getPathAdjustedSlippage( const rateChange = maxRate.minus(totalRate); return rateChange.div(maxRate).toNumber(); } + +export function getCompleteRate( + side: MarketOperation, + input: BigNumber, + output: BigNumber, + targetInput: BigNumber, +): BigNumber { + if (input.eq(0) || output.eq(0) || targetInput.eq(0)) { + return ZERO_AMOUNT; + } + // Penalize paths that fall short of the entire input amount by a factor of + // input / targetInput => (i / t) + if (side === MarketOperation.Sell) { + // (o / i) * (i / t) => (o / t) + return output.div(targetInput); + } + // (i / o) * (i / t) + return input.div(output).times(input.div(targetInput)); +} + +export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber { + if (input.eq(0) || output.eq(0)) { + return ZERO_AMOUNT; + } + return side === MarketOperation.Sell ? output.div(input) : input.div(output); +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts index dd5627fa10..f649c59e07 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts @@ -3,10 +3,18 @@ import { BigNumber } from '@0x/utils'; import { MarketOperation } from '../../types'; import { ZERO_AMOUNT } from './constants'; -import { getPathAdjustedRate, getPathSize, isValidPath } from './fills'; +import { + arePathFlagsAllowed, + getCompleteRate, + getPathAdjustedCompleteRate, + getPathAdjustedRate, + getPathAdjustedSize, + getPathSize, + isValidPath, +} from './fills'; import { Fill } from './types'; -// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs +// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise const RUN_LIMIT_DECAY_FACTOR = 0.5; @@ -20,11 +28,13 @@ export async function findOptimalPathAsync( targetInput: BigNumber, runLimit: number = 2 ** 8, ): Promise { - // Sort paths by descending adjusted rate. + // Sort paths by descending adjusted completed rate. const sortedPaths = paths .slice(0) .sort((a, b) => - getPathAdjustedRate(side, b, targetInput).comparedTo(getPathAdjustedRate(side, a, targetInput)), + getPathAdjustedCompleteRate(side, b, targetInput).comparedTo( + getPathAdjustedCompleteRate(side, a, targetInput), + ), ); let optimalPath = sortedPaths[0] || []; for (const [i, path] of sortedPaths.slice(1).entries()) { @@ -42,11 +52,12 @@ function mixPaths( targetInput: BigNumber, maxSteps: number, ): Fill[] { - const _maxSteps = Math.max(maxSteps, 16); - let bestPath: Fill[] = pathA; - let bestPathInput = getPathSize(pathA, targetInput)[0]; - let bestPathRate = getPathAdjustedRate(side, pathA, targetInput); + const _maxSteps = Math.max(maxSteps, 32); let steps = 0; + // We assume pathA is the better of the two initially. + let bestPath: Fill[] = pathA; + let [bestPathInput, bestPathOutput] = getPathAdjustedSize(pathA, targetInput); + let bestPathRate = getCompleteRate(side, bestPathInput, bestPathOutput, targetInput); const _isBetterPath = (input: BigNumber, rate: BigNumber) => { if (bestPathInput.lt(targetInput)) { return input.gt(bestPathInput); @@ -55,64 +66,77 @@ function mixPaths( } return false; }; - const _walk = (path: Fill[], input: BigNumber, output: BigNumber, allFills: Fill[]) => { + const _walk = (path: Fill[], input: BigNumber, output: BigNumber, flags: number, remainingFills: Fill[]) => { steps += 1; - const rate = getRate(side, targetInput, output); + const rate = getCompleteRate(side, input, output, targetInput); if (_isBetterPath(input, rate)) { bestPath = path; bestPathInput = input; + bestPathOutput = output; bestPathRate = rate; } const remainingInput = targetInput.minus(input); if (remainingInput.gt(0)) { - for (let i = 0; i < allFills.length && steps < _maxSteps; ++i) { - const fill = allFills[i]; - const nextPath = [...path, fill]; + for (let i = 0; i < remainingFills.length && steps < _maxSteps; ++i) { + const fill = remainingFills[i]; // Only walk valid paths. - if (!isValidPath(nextPath, true)) { + if (!isValidNextPathFill(path, flags, fill)) { continue; } // Remove this fill from the next list of candidate fills. - const nextAllFills = allFills.slice(); - nextAllFills.splice(i, 1); + const nextRemainingFills = remainingFills.slice(); + nextRemainingFills.splice(i, 1); // Recurse. _walk( - nextPath, + [...path, fill], input.plus(BigNumber.min(remainingInput, fill.input)), output.plus( // Clip the output of the next fill to the remaining // input. clipFillAdjustedOutput(fill, remainingInput), ), - nextAllFills, + flags | fill.flags, + nextRemainingFills, ); } } }; - const allPaths = [...pathA, ...pathB]; - const sources = allPaths.filter(f => f.index === 0).map(f => f.sourcePathId); + const allFills = [...pathA, ...pathB]; + const sources = allFills.filter(f => f.index === 0).map(f => f.sourcePathId); const rateBySource = Object.assign( {}, ...sources.map(s => ({ - [s]: getPathAdjustedRate(side, allPaths.filter(f => f.sourcePathId === s), targetInput), + [s]: getPathAdjustedRate(side, allFills.filter(f => f.sourcePathId === s), targetInput), })), ); - _walk( - [], - ZERO_AMOUNT, - ZERO_AMOUNT, - // Sort subpaths by rate and keep fills contiguous to improve our - // chances of walking ideal, valid paths first. - allPaths.sort((a, b) => { - if (a.sourcePathId !== b.sourcePathId) { - return rateBySource[b.sourcePathId].comparedTo(rateBySource[a.sourcePathId]); - } - return a.index - b.index; - }), - ); + // Sort subpaths by rate and keep fills contiguous to improve our + // chances of walking ideal, valid paths first. + const sortedFills = allFills.sort((a, b) => { + if (a.sourcePathId !== b.sourcePathId) { + return rateBySource[b.sourcePathId].comparedTo(rateBySource[a.sourcePathId]); + } + return a.index - b.index; + }); + _walk([], ZERO_AMOUNT, ZERO_AMOUNT, 0, sortedFills); + if (!isValidPath(bestPath)) { + throw new Error('nooope'); + } return bestPath; } +function isValidNextPathFill(path: Fill[], pathFlags: number, fill: Fill): boolean { + if (path.length === 0) { + return !fill.parent; + } + if (path[path.length - 1] === fill.parent) { + return true; + } + if (fill.parent) { + return false; + } + return arePathFlagsAllowed(pathFlags | fill.flags); +} + function isPathComplete(path: Fill[], targetInput: BigNumber): boolean { const [input] = getPathSize(path); return input.gte(targetInput); @@ -122,16 +146,7 @@ function clipFillAdjustedOutput(fill: Fill, remainingInput: BigNumber): BigNumbe if (fill.input.lte(remainingInput)) { return fill.adjustedOutput; } + // Penalty does not get interpolated. const penalty = fill.adjustedOutput.minus(fill.output); - return remainingInput.times(fill.rate).plus(penalty); -} - -function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber { - if (input.eq(0) || output.eq(0)) { - return ZERO_AMOUNT; - } - if (side === MarketOperation.Sell) { - return output.div(input); - } - return input.div(output); + return remainingInput.times(fill.output.div(fill.input)).plus(penalty); } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index f36f27f0ec..3772daf538 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -130,10 +130,6 @@ export interface Fill { input: BigNumber; // Output fill amount (maker asset amount in a sell, taker asset amount in a buy). output: BigNumber; - // The maker/taker rate. - rate: BigNumber; - // The maker/taker rate, adjusted by fees. - adjustedRate: BigNumber; // The output fill amount, ajdusted by fees. adjustedOutput: BigNumber; // Fill that must precede this one. This enforces certain fills to be contiguous. diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 42e54e214d..bbf8d39729 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -813,9 +813,9 @@ describe('MarketOperationUtils tests', () => { expect(improvedOrders).to.be.length(3); const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ - [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Curve], + [ERC20BridgeSource.Uniswap], [ERC20BridgeSource.Native], - [ERC20BridgeSource.Eth2Dai], + [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve], ]); }); }); @@ -1114,8 +1114,8 @@ describe('MarketOperationUtils tests', () => { it('batches contiguous bridge sources', async () => { const rates: RatesBySource = {}; rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01]; - rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01]; - rates[ERC20BridgeSource.Uniswap] = [0.48, 0.47, 0.01, 0.01]; + rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.02, 0.01, 0.01]; + rates[ERC20BridgeSource.Uniswap] = [0.48, 0.01, 0.01, 0.01]; replaceSamplerOps({ getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), }); @@ -1133,7 +1133,7 @@ describe('MarketOperationUtils tests', () => { const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); expect(orderFillSources).to.deep.eq([ [ERC20BridgeSource.Native], - [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai], + [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap], ]); }); }); From 71cde281b90a2f6f9032d88432f7f7a3c4d68d97 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Sun, 9 Aug 2020 16:38:59 +1000 Subject: [PATCH 49/50] fix: asset-swapper depth buy scale (#2659) --- packages/asset-swapper/CHANGELOG.json | 4 ++++ packages/asset-swapper/src/swap_quoter.ts | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 0a03df5f06..c6b3f7f75b 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -37,6 +37,10 @@ { "note": "Fix optimization of buy paths", "pr": 2655 + }, + { + "note": "Fix depth buy scale", + "pr": 2659 } ] }, diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 8bb7281fd7..08d9ab83f3 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -454,7 +454,11 @@ export class SwapQuoter { return [ ...dexQuotes, nativeOrders.map((o, i) => { - const scaleFactor = orderFillableAmounts[i].div(o.takerAssetAmount); + // When sell order fillable amount is taker + // When buy order fillable amount is maker + const scaleFactor = orderFillableAmounts[i].div( + side === MarketOperation.Sell ? o.takerAssetAmount : o.makerAssetAmount, + ); return { input: (side === MarketOperation.Sell ? o.takerAssetAmount : o.makerAssetAmount) .times(scaleFactor) From d0e9081852d0c277b9867e6473512e0d5a7ac2f7 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 10 Aug 2020 20:41:17 +1000 Subject: [PATCH 50/50] feat: asset-swapper use ethToInputRate when ethToOutputRate is unavailable (#2660) --- packages/asset-swapper/CHANGELOG.json | 4 +++ .../src/utils/market_operation_utils/fills.ts | 14 +++++--- .../src/utils/market_operation_utils/index.ts | 33 +++++++++++++++++-- .../src/utils/market_operation_utils/types.ts | 1 + 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index c6b3f7f75b..985e62fc27 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -41,6 +41,10 @@ { "note": "Fix depth buy scale", "pr": 2659 + }, + { + "note": "Adjust fill by ethToInputRate when ethToOutputRate is 0", + "pr": 2660 } ] }, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index ccc015b33e..f06445c091 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -17,6 +17,7 @@ export function createFillPaths(opts: { dexQuotes?: DexSample[][]; targetInput?: BigNumber; ethToOutputRate?: BigNumber; + ethToInputRate?: BigNumber; excludedSources?: ERC20BridgeSource[]; feeSchedule?: FeeSchedule; }): Fill[][] { @@ -26,8 +27,9 @@ export function createFillPaths(opts: { const orders = opts.orders || []; const dexQuotes = opts.dexQuotes || []; const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT; + const ethToInputRate = opts.ethToInputRate || ZERO_AMOUNT; // Create native fill paths. - const nativePath = nativeOrdersToPath(side, orders, opts.targetInput, ethToOutputRate, feeSchedule); + const nativePath = nativeOrdersToPath(side, orders, opts.targetInput, ethToOutputRate, ethToInputRate, feeSchedule); // Create DEX fill paths. const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, feeSchedule); return filterPaths([...dexPaths, nativePath].map(p => clipPathToInput(p, opts.targetInput)), excludedSources); @@ -54,6 +56,7 @@ function nativeOrdersToPath( orders: SignedOrderWithFillableAmounts[], targetInput: BigNumber = POSITIVE_INF, ethToOutputRate: BigNumber, + ethToInputRate: BigNumber, fees: FeeSchedule, ): Fill[] { const sourcePathId = hexUtils.random(); @@ -64,9 +67,10 @@ function nativeOrdersToPath( const takerAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterOrderFees(order); const input = side === MarketOperation.Sell ? takerAmount : makerAmount; const output = side === MarketOperation.Sell ? makerAmount : takerAmount; - const penalty = ethToOutputRate.times( - fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(), - ); + const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(); + const outputPenalty = !ethToOutputRate.isZero() + ? ethToOutputRate.times(fee) + : ethToInputRate.times(fee).times(output.dividedToIntegerBy(input)); // targetInput can be less than the order size // whilst the penalty is constant, it affects the adjusted output // only up until the target has been exhausted. @@ -76,7 +80,7 @@ function nativeOrdersToPath( // scale the clipped output inline with the input const clippedOutput = clippedInput.dividedBy(input).times(output); const adjustedOutput = - side === MarketOperation.Sell ? clippedOutput.minus(penalty) : clippedOutput.plus(penalty); + side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty); const adjustedRate = side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput); // Skip orders with rates that are <= 0. 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 6c2363fca6..85bfef8633 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -1,4 +1,5 @@ import { ContractAddresses } from '@0x/contract-addresses'; +import { ZERO_AMOUNT } from '@0x/order-utils'; import { RFQTIndicativeQuote } from '@0x/quote-server'; import { SignedOrder } from '@0x/types'; import { BigNumber, NULL_ADDRESS } from '@0x/utils'; @@ -114,6 +115,17 @@ export class MarketOperationUtils { this._liquidityProviderRegistry, this._multiBridge, ), + // Get ETH -> taker token price. + await DexOrderSampler.ops.getMedianSellRateAsync( + difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), + takerToken, + this._wethAddress, + ONE_ETHER, + this._wethAddress, + this._sampler.balancerPoolsCache, + this._liquidityProviderRegistry, + this._multiBridge, + ), // Get sell quotes for taker -> maker. await DexOrderSampler.ops.getSellQuotesAsync( difference( @@ -152,7 +164,7 @@ export class MarketOperationUtils { .then(async r => this._sampler.executeAsync(r)); const [ - [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes], + [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes], rfqtIndicativeQuotes, [balancerQuotes], ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); @@ -174,6 +186,7 @@ export class MarketOperationUtils { nativeOrders, orderFillableAmounts, ethToOutputRate: ethToMakerAssetRate, + ethToInputRate: ethToTakerAssetRate, rfqtIndicativeQuotes, }; } @@ -207,6 +220,17 @@ export class MarketOperationUtils { makerToken, takerToken, ), + // Get ETH -> maker token price. + await DexOrderSampler.ops.getMedianSellRateAsync( + difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), + makerToken, + this._wethAddress, + ONE_ETHER, + this._wethAddress, + this._sampler.balancerPoolsCache, + this._liquidityProviderRegistry, + this._multiBridge, + ), // Get ETH -> taker token price. await DexOrderSampler.ops.getMedianSellRateAsync( difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), @@ -255,7 +279,7 @@ export class MarketOperationUtils { _opts, ); const [ - [orderFillableAmounts, liquidityProviderAddress, ethToTakerAssetRate, dexQuotes], + [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes], rfqtIndicativeQuotes, [balancerQuotes], ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); @@ -276,6 +300,7 @@ export class MarketOperationUtils { nativeOrders, orderFillableAmounts, ethToOutputRate: ethToTakerAssetRate, + ethToInputRate: ethToMakerAssetRate, rfqtIndicativeQuotes, }; } @@ -388,6 +413,7 @@ export class MarketOperationUtils { const batchOrderFillableAmounts = executeResults.splice(0, batchNativeOrders.length) as BigNumber[][]; const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[]; const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][]; + const ethToInputRate = ZERO_AMOUNT; return Promise.all( batchNativeOrders.map(async (nativeOrders, i) => { @@ -408,6 +434,7 @@ export class MarketOperationUtils { dexQuotes, inputAmount: makerAmount, ethToOutputRate: ethToTakerAssetRate, + ethToInputRate, rfqtIndicativeQuotes: [], inputToken: makerToken, outputToken: takerToken, @@ -454,6 +481,7 @@ export class MarketOperationUtils { rfqtIndicativeQuotes, dexQuotes, ethToOutputRate, + ethToInputRate, } = marketSideLiquidity; const maxFallbackSlippage = opts.maxFallbackSlippage || 0; // Convert native orders and dex quotes into fill paths. @@ -467,6 +495,7 @@ export class MarketOperationUtils { dexQuotes, targetInput: inputAmount, ethToOutputRate, + ethToInputRate, excludedSources: opts.excludedSources, feeSchedule: opts.feeSchedule, }); diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 3772daf538..1f2c500ac9 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -291,5 +291,6 @@ export interface MarketSideLiquidity { nativeOrders: SignedOrder[]; orderFillableAmounts: BigNumber[]; ethToOutputRate: BigNumber; + ethToInputRate: BigNumber; rfqtIndicativeQuotes: RFQTIndicativeQuote[]; }