Merge pull request #2157 from 0xProject/feature/orderbook-removing-smart-routing-logic

Refactored asset-swapper smart logic and renamed options
This commit is contained in:
David Sun 2019-09-26 09:18:05 -04:00 committed by GitHub
commit 97eabc6c03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 65 deletions

View File

@ -33,10 +33,11 @@ export {
SwapQuote, SwapQuote,
SwapQuoteConsumerOpts, SwapQuoteConsumerOpts,
CalldataInfo, CalldataInfo,
ConsumerType, ExtensionContractType,
SwapQuoteGetOutputOpts, SwapQuoteGetOutputOpts,
SwapQuoteExecutionOpts, SwapQuoteExecutionOpts,
SwapQuoteInfo, SwapQuoteInfo,
GetExtensionContractTypeOpts,
SwapQuoteExecutionOptsBase, SwapQuoteExecutionOptsBase,
SwapQuoteGetOutputOptsBase, SwapQuoteGetOutputOptsBase,
ForwarderSwapQuoteExecutionOpts, ForwarderSwapQuoteExecutionOpts,

View File

@ -6,7 +6,8 @@ import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { import {
CalldataInfo, CalldataInfo,
ConsumerType, ExtensionContractType,
GetExtensionContractTypeOpts,
SmartContractParams, SmartContractParams,
SmartContractParamsInfo, SmartContractParamsInfo,
SwapQuote, SwapQuote,
@ -25,9 +26,9 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
public readonly networkId: number; public readonly networkId: number;
private readonly _contractWrappers: ContractWrappers;
private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer; private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer; private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
private readonly _contractWrappers: ContractWrappers;
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) { constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
const { networkId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); const { networkId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
@ -36,12 +37,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
const provider = providerUtils.standardizeOrThrow(supportedProvider); const provider = providerUtils.standardizeOrThrow(supportedProvider);
this.provider = provider; this.provider = provider;
this.networkId = networkId; this.networkId = networkId;
this._contractWrappers = new ContractWrappers(this.provider, {
networkId,
});
this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, options); this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, options);
this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, options); this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, options);
this._contractWrappers = new ContractWrappers(this.provider, {
networkId,
});
} }
/** /**
@ -54,7 +55,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
opts: Partial<SwapQuoteGetOutputOpts> = {}, opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> { ): Promise<CalldataInfo> {
assert.isValidSwapQuote('quote', quote); assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); const consumer = await this._getConsumerForSwapQuoteAsync(opts);
return consumer.getCalldataOrThrowAsync(quote, opts); return consumer.getCalldataOrThrowAsync(quote, opts);
} }
@ -68,7 +69,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
opts: Partial<SwapQuoteGetOutputOpts> = {}, opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<SmartContractParamsInfo<SmartContractParams>> { ): Promise<SmartContractParamsInfo<SmartContractParams>> {
assert.isValidSwapQuote('quote', quote); assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); const consumer = await this._getConsumerForSwapQuoteAsync(opts);
return consumer.getSmartContractParamsOrThrowAsync(quote, opts); return consumer.getSmartContractParamsOrThrowAsync(quote, opts);
} }
@ -82,25 +83,26 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
opts: Partial<SwapQuoteExecutionOpts> = {}, opts: Partial<SwapQuoteExecutionOpts> = {},
): Promise<string> { ): Promise<string> {
assert.isValidSwapQuote('quote', quote); assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); const consumer = await this._getConsumerForSwapQuoteAsync(opts);
return consumer.executeSwapQuoteOrThrowAsync(quote, opts); return consumer.executeSwapQuoteOrThrowAsync(quote, opts);
} }
private async _getConsumerForSwapQuoteAsync( public async getOptimalExtensionContractTypeAsync(
quote: SwapQuote, quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts>, opts: Partial<GetExtensionContractTypeOpts> = {},
): Promise<SwapQuoteConsumerBase<SmartContractParams>> { ): Promise<ExtensionContractType> {
const useConsumerType = return swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(
opts.useConsumerType ||
(await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync(
quote, quote,
this._contractWrappers, this._contractWrappers,
this.provider, this.provider,
opts, opts,
)); );
if (useConsumerType === ConsumerType.Exchange) { }
return this._exchangeConsumer;
} else if (useConsumerType === ConsumerType.Forwarder) { private async _getConsumerForSwapQuoteAsync(
opts: Partial<SwapQuoteGetOutputOpts>,
): Promise<SwapQuoteConsumerBase<SmartContractParams>> {
if (opts.useExtensionContract === ExtensionContractType.Forwarder) {
return this._forwarderConsumer; return this._forwarderConsumer;
} }
return this._exchangeConsumer; return this._exchangeConsumer;

View File

@ -86,9 +86,9 @@ export interface ExchangeMarketSellSmartContractParams extends SmartContractPara
/** /**
* Represents the varying smart contracts that can consume a valid swap quote * Represents the varying smart contracts that can consume a valid swap quote
*/ */
export enum ConsumerType { export enum ExtensionContractType {
Forwarder = 'FORWARDER', Forwarder = 'FORWARDER',
Exchange = 'EXCHANGE', None = 'NONE',
} }
/** /**
@ -183,13 +183,17 @@ export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOptsB
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote; export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
export interface GetExtensionContractTypeOpts {
takerAddress?: string;
ethAmount?: BigNumber;
}
/** /**
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider. * takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
* useConsumerType: If provided, defaults the SwapQuoteConsumer to create output consumed by ConsumerType. * useConsumerType: If provided, defaults the SwapQuoteConsumer to create output consumed by ConsumerType.
*/ */
export interface SwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts { export interface SwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts {
takerAddress?: string; useExtensionContract: ExtensionContractType;
useConsumerType?: ConsumerType;
} }
export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOptsBase {} export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOptsBase {}

View File

@ -8,11 +8,11 @@ import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { import {
ConsumerType, ExtensionContractType,
GetExtensionContractTypeOpts,
SwapQuote, SwapQuote,
SwapQuoteConsumerError, SwapQuoteConsumerError,
SwapQuoteExecutionOpts, SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
} from '../types'; } from '../types';
import { assert } from './assert'; import { assert } from './assert';
@ -79,12 +79,12 @@ export const swapQuoteConsumerUtils = {
return optimizedOrder; return optimizedOrder;
}); });
}, },
async getConsumerTypeForSwapQuoteAsync( async getExtensionContractTypeForSwapQuoteAsync(
quote: SwapQuote, quote: SwapQuote,
contractWrappers: ContractWrappers, contractWrappers: ContractWrappers,
provider: Provider, provider: Provider,
opts: Partial<SwapQuoteGetOutputOpts>, opts: Partial<GetExtensionContractTypeOpts>,
): Promise<ConsumerType> { ): Promise<ExtensionContractType> {
const wethAssetData = assetDataUtils.encodeERC20AssetData(contractWrappers.contractAddresses.etherToken); const wethAssetData = assetDataUtils.encodeERC20AssetData(contractWrappers.contractAddresses.etherToken);
if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) { if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) {
if (opts.takerAddress !== undefined) { if (opts.takerAddress !== undefined) {
@ -102,14 +102,14 @@ export const swapQuoteConsumerUtils = {
); );
if (isEnoughEthAndWethBalance[1]) { if (isEnoughEthAndWethBalance[1]) {
// should be more gas efficient to use exchange consumer, so if possible use it. // should be more gas efficient to use exchange consumer, so if possible use it.
return ConsumerType.Exchange; return ExtensionContractType.None;
} else if (isEnoughEthAndWethBalance[0] && !isEnoughEthAndWethBalance[1]) { } else if (isEnoughEthAndWethBalance[0] && !isEnoughEthAndWethBalance[1]) {
return ConsumerType.Forwarder; return ExtensionContractType.Forwarder;
} }
// Note: defaulting to forwarderConsumer if takerAddress is null or not enough balance of either wEth or Eth // Note: defaulting to forwarderConsumer if takerAddress is null or not enough balance of either wEth or Eth
return ConsumerType.Forwarder; return ExtensionContractType.Forwarder;
} else { } else {
return ConsumerType.Exchange; return ExtensionContractType.None;
} }
}, },
}; };

View File

@ -7,7 +7,7 @@ import * as chai from 'chai';
import 'mocha'; import 'mocha';
import { SwapQuote, SwapQuoteConsumer } from '../src'; import { SwapQuote, SwapQuoteConsumer } from '../src';
import { ConsumerType } from '../src/types'; import { ExtensionContractType } from '../src/types';
import { chaiSetup } from './utils/chai_setup'; import { chaiSetup } from './utils/chai_setup';
import { migrateOnceAsync } from './utils/migrate'; import { migrateOnceAsync } from './utils/migrate';
@ -116,18 +116,18 @@ describe('SwapQuoteConsumer', () => {
// * Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert) // * Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
// * Does not test the validity of the state change performed by the forwarder smart contract // * Does not test the validity of the state change performed by the forwarder smart contract
// */ // */
// it('should perform an asset swap with Forwarder contract when provided corresponding useConsumerType option', async () => { // it('should perform an asset swap with Forwarder contract when provided corresponding useExtensionContract option', async () => {
// let makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress); // let makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress);
// let takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // let takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress);
// expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); // expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
// expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT); // expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
// await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress, useConsumerType: ConsumerType.Forwarder }); // await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress, useExtensionContract: ConsumerType.Forwarder });
// makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress); // makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress);
// takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress);
// expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); // expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
// expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT); // expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
// }); // });
// it('should perform an asset swap with Exchange contract when provided corresponding useConsumerType option', async () => { // it('should perform an asset swap with Exchange contract when provided corresponding useExtensionContract option', async () => {
// let makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress); // let makerBalance = await erc20TokenContract.balanceOf.callAsync(makerAddress);
// let takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // let takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress);
// expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); // expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
@ -143,15 +143,15 @@ describe('SwapQuoteConsumer', () => {
describe('getSmartContractParamsOrThrow', () => { describe('getSmartContractParamsOrThrow', () => {
describe('valid swap quote', async () => { describe('valid swap quote', async () => {
// TODO(david) Check for valid MethodAbi // TODO(david) Check for valid MethodAbi
it('should provide correct and optimized smart contract params for Forwarder contract when provided corresponding useConsumerType option', async () => { it('should provide correct and optimized smart contract params for Forwarder contract when provided corresponding useExtensionContract option', async () => {
const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, { const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, {
useConsumerType: ConsumerType.Forwarder, useExtensionContract: ExtensionContractType.Forwarder,
}); });
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address); expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
}); });
it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useConsumerType option', async () => { it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useExtensionContract option', async () => {
const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, { const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, {
useConsumerType: ConsumerType.Exchange, useExtensionContract: ExtensionContractType.None,
}); });
expect(toAddress).to.deep.equal(contractWrappers.exchange.address); expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
}); });
@ -160,15 +160,15 @@ describe('SwapQuoteConsumer', () => {
describe('getCalldataOrThrow', () => { describe('getCalldataOrThrow', () => {
describe('valid swap quote', async () => { describe('valid swap quote', async () => {
it('should provide correct and optimized calldata options for Forwarder contract when provided corresponding useConsumerType option', async () => { it('should provide correct and optimized calldata options for Forwarder contract when provided corresponding useExtensionContract option', async () => {
const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, { const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, {
useConsumerType: ConsumerType.Forwarder, useExtensionContract: ExtensionContractType.Forwarder,
}); });
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address); expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
}); });
it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useConsumerType option', async () => { it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useExtensionContract option', async () => {
const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, { const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, {
useConsumerType: ConsumerType.Exchange, useExtensionContract: ExtensionContractType.None,
}); });
expect(toAddress).to.deep.equal(contractWrappers.exchange.address); expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
}); });

View File

@ -6,9 +6,8 @@ import { BigNumber } from '@0x/utils';
import * as chai from 'chai'; import * as chai from 'chai';
import 'mocha'; import 'mocha';
import { SwapQuote } from '../src'; import { SwapQuote, SwapQuoteConsumer } from '../src';
import { ConsumerType } from '../src/types'; import { ExtensionContractType } from '../src/types';
import { swapQuoteConsumerUtils } from '../src/utils/swap_quote_consumer_utils';
import { chaiSetup } from './utils/chai_setup'; import { chaiSetup } from './utils/chai_setup';
import { migrateOnceAsync } from './utils/migrate'; import { migrateOnceAsync } from './utils/migrate';
@ -39,6 +38,7 @@ describe('swapQuoteConsumerUtils', () => {
let takerAssetData: string; let takerAssetData: string;
let wethAssetData: string; let wethAssetData: string;
let contractAddresses: ContractAddresses; let contractAddresses: ContractAddresses;
let swapQuoteConsumer: SwapQuoteConsumer;
const networkId = TESTRPC_NETWORK_ID; const networkId = TESTRPC_NETWORK_ID;
before(async () => { before(async () => {
@ -57,6 +57,10 @@ describe('swapQuoteConsumerUtils', () => {
assetDataUtils.encodeERC20AssetData(takerTokenAddress), assetDataUtils.encodeERC20AssetData(takerTokenAddress),
assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken), assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken),
]; ];
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
networkId,
});
}); });
after(async () => { after(async () => {
await blockchainLifecycle.revertAsync(); await blockchainLifecycle.revertAsync();
@ -127,44 +131,36 @@ describe('swapQuoteConsumerUtils', () => {
}); });
it('should return exchange consumer if takerAsset is not wEth', async () => { it('should return exchange consumer if takerAsset is not wEth', async () => {
const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
exchangeSwapQuote, exchangeSwapQuote,
contractWrappers,
provider,
{ takerAddress }, { takerAddress },
); );
expect(consumerType).to.equal(ConsumerType.Exchange); expect(extensionContractType).to.equal(ExtensionContractType.None);
}); });
it('should return forwarder consumer if takerAsset is wEth and have enough eth balance', async () => { it('should return forwarder consumer if takerAsset is wEth and have enough eth balance', async () => {
const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
forwarderSwapQuote, forwarderSwapQuote,
contractWrappers,
provider,
{ takerAddress }, { takerAddress },
); );
expect(consumerType).to.equal(ConsumerType.Forwarder); expect(extensionContractType).to.equal(ExtensionContractType.Forwarder);
}); });
it('should return exchange consumer if takerAsset is wEth and taker has enough weth', async () => { it('should return exchange consumer if takerAsset is wEth and taker has enough weth', async () => {
const etherInWei = new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI); const etherInWei = new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI);
await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress });
const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
forwarderSwapQuote, forwarderSwapQuote,
contractWrappers,
provider,
{ takerAddress }, { takerAddress },
); );
expect(consumerType).to.equal(ConsumerType.Exchange); expect(extensionContractType).to.equal(ExtensionContractType.None);
}); });
it('should return forwarder consumer if takerAsset is wEth and takerAddress has no available balance in either weth or eth (defaulting behavior)', async () => { it('should return forwarder consumer if takerAsset is wEth and takerAddress has no available balance in either weth or eth (defaulting behavior)', async () => {
const etherInWei = new BigNumber(50).multipliedBy(ONE_ETH_IN_WEI); const etherInWei = new BigNumber(50).multipliedBy(ONE_ETH_IN_WEI);
await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress });
const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
largeForwarderSwapQuote, largeForwarderSwapQuote,
contractWrappers,
provider,
{ takerAddress }, { takerAddress },
); );
expect(consumerType).to.equal(ConsumerType.Forwarder); expect(extensionContractType).to.equal(ExtensionContractType.Forwarder);
}); });
}); });
}); });