From b60db6ac72461f42caa06615a61227f5064450b5 Mon Sep 17 00:00:00 2001 From: David Sun Date: Sat, 14 Sep 2019 16:38:31 -0400 Subject: [PATCH 1/2] removed smart logic and renamed options --- packages/asset-swapper/src/index.ts | 2 +- .../smart_swap_quote_consumer.ts | 66 +++++++ .../quote_consumers/swap_quote_consumer.ts | 27 +-- packages/asset-swapper/src/types.ts | 17 +- .../src/utils/swap_quote_consumer_utils.ts | 18 +- .../test/swap_quote_consumer_test.ts | 10 +- .../test/swap_quote_consumer_utils_test.ts | 170 ------------------ 7 files changed, 99 insertions(+), 211 deletions(-) create mode 100644 packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts delete mode 100644 packages/asset-swapper/test/swap_quote_consumer_utils_test.ts diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 327982d53a..8f619ff9a4 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -33,7 +33,7 @@ export { SwapQuote, SwapQuoteConsumerOpts, CalldataInfo, - ConsumerType, + ExtensionContractType, SwapQuoteGetOutputOpts, SwapQuoteExecutionOpts, SwapQuoteInfo, diff --git a/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts new file mode 100644 index 0000000000..8f4d70b8d1 --- /dev/null +++ b/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts @@ -0,0 +1,66 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { SupportedProvider } from '@0x/web3-wrapper'; + +import { + CalldataInfo, + SmartContractParams, + SmartContractParamsInfo, + SmartSwapQuoteExecutionOpts, + SmartSwapQuoteGetOutputOpts, + SwapQuote, + SwapQuoteConsumerOpts, +} from '../types'; +import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; + +import { SwapQuoteConsumer } from './swap_quote_consumer'; + +export class SmartSwapQuoteConsumer extends SwapQuoteConsumer { + + private readonly _contractWrappers: ContractWrappers; + + constructor(supportedProvider: SupportedProvider, options: Partial = {}) { + super(supportedProvider, options); + this._contractWrappers = new ContractWrappers(this.provider, { + networkId: this.networkId, + }); + } + + /** + * Given a SwapQuote, returns 'CalldataInfo' for a 0x exchange call. See type definition of CalldataInfo for more information. + * @param quote An object that conforms to SwapQuote. See type definition for more information. + * @param opts Options for getting SmartContractParams. See type definition for more information. + */ + public async getCalldataOrThrowAsync( + quote: SwapQuote, + opts: Partial = {}, + ): Promise { + const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); + return super.getCalldataOrThrowAsync(quote, { useExtensionContract }); + } + + /** + * Given a SwapQuote, returns 'SmartContractParamsInfo' for a 0x exchange call. See type definition of SmartContractParamsInfo for more information. + * @param quote An object that conforms to SwapQuote. See type definition for more information. + * @param opts Options for getting SmartContractParams. See type definition for more information. + */ + public async getSmartContractParamsOrThrowAsync( + quote: SwapQuote, + opts: Partial = {}, + ): Promise> { + const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); + return super.getSmartContractParamsOrThrowAsync(quote, { useExtensionContract }); + } + + /** + * Given a SwapQuote and desired rate (in takerAsset), attempt to execute the swap. + * @param quote An object that conforms to SwapQuote. See type definition for more information. + * @param opts Options for getting CalldataInfo. See type definition for more information. + */ + public async executeSwapQuoteOrThrowAsync( + quote: SwapQuote, + opts: Partial = {}, + ): Promise { + const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); + return super.executeSwapQuoteOrThrowAsync(quote, { useExtensionContract }); + } +} diff --git a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts index 7c24c36f5a..eddd84b155 100644 --- a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts @@ -1,4 +1,3 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; import { providerUtils } from '@0x/utils'; import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper'; import * as _ from 'lodash'; @@ -6,7 +5,7 @@ import * as _ from 'lodash'; import { constants } from '../constants'; import { CalldataInfo, - ConsumerType, + ExtensionContractType, SmartContractParams, SmartContractParamsInfo, SwapQuote, @@ -16,7 +15,6 @@ import { SwapQuoteGetOutputOpts, } from '../types'; import { assert } from '../utils/assert'; -import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { ExchangeSwapQuoteConsumer } from './exchange_swap_quote_consumer'; import { ForwarderSwapQuoteConsumer } from './forwarder_swap_quote_consumer'; @@ -25,7 +23,6 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase = {}, ): Promise { assert.isValidSwapQuote('quote', quote); - const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); + const consumer = await this._getConsumerForSwapQuoteAsync(opts); return consumer.getCalldataOrThrowAsync(quote, opts); } @@ -68,7 +62,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase = {}, ): Promise> { assert.isValidSwapQuote('quote', quote); - const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); + const consumer = await this._getConsumerForSwapQuoteAsync(opts); return consumer.getSmartContractParamsOrThrowAsync(quote, opts); } @@ -82,25 +76,14 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase = {}, ): Promise { assert.isValidSwapQuote('quote', quote); - const consumer = await this._getConsumerForSwapQuoteAsync(quote, opts); + const consumer = await this._getConsumerForSwapQuoteAsync(opts); return consumer.executeSwapQuoteOrThrowAsync(quote, opts); } private async _getConsumerForSwapQuoteAsync( - quote: SwapQuote, opts: Partial, ): Promise> { - const useConsumerType = - opts.useConsumerType || - (await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( - quote, - this._contractWrappers, - this.provider, - opts, - )); - if (useConsumerType === ConsumerType.Exchange) { - return this._exchangeConsumer; - } else if (useConsumerType === ConsumerType.Forwarder) { + if (opts.useExtensionContract === ExtensionContractType.Forwarder) { return this._forwarderConsumer; } return this._exchangeConsumer; diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index c77e48ad8b..51a7bf7371 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -1,6 +1,7 @@ import { MarketOperation, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { MethodAbi } from 'ethereum-types'; +import { ForwarderSwapQuoteConsumer } from './quote_consumers/forwarder_swap_quote_consumer'; /** * makerAssetData: The assetData representing the desired makerAsset. @@ -86,9 +87,9 @@ export interface ExchangeMarketSellSmartContractParams extends SmartContractPara /** * Represents the varying smart contracts that can consume a valid swap quote */ -export enum ConsumerType { +export enum ExtensionContractType { Forwarder = 'FORWARDER', - Exchange = 'EXCHANGE', + None = 'NONE', } /** @@ -183,13 +184,16 @@ export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOptsB export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote; +export interface SmartSwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts { + takerAddress?: string; +} + /** * 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. */ export interface SwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts { - takerAddress?: string; - useConsumerType?: ConsumerType; + useExtensionContract: ExtensionContractType; } export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOptsBase {} @@ -199,6 +203,11 @@ export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOu */ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts, ForwarderSwapQuoteExecutionOpts {} +/** + * Represents the options for executing a swap quote with SmartSwapQuoteConsumer + */ +export interface SmartSwapQuoteExecutionOpts extends SmartSwapQuoteGetOutputOpts, ForwarderSwapQuoteExecutionOpts {} + /** * takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). * makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). diff --git a/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts b/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts index 8420f2ad93..58648dffc5 100644 --- a/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts +++ b/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts @@ -8,11 +8,11 @@ import * as _ from 'lodash'; import { constants } from '../constants'; import { - ConsumerType, + ExtensionContractType, + SmartSwapQuoteGetOutputOpts, SwapQuote, SwapQuoteConsumerError, SwapQuoteExecutionOpts, - SwapQuoteGetOutputOpts, } from '../types'; import { assert } from './assert'; @@ -79,12 +79,12 @@ export const swapQuoteConsumerUtils = { return optimizedOrder; }); }, - async getConsumerTypeForSwapQuoteAsync( + async getExtensionContractTypeForSwapQuoteAsync( quote: SwapQuote, contractWrappers: ContractWrappers, provider: Provider, - opts: Partial, - ): Promise { + opts: Partial, + ): Promise { const wethAssetData = assetDataUtils.encodeERC20AssetData(contractWrappers.contractAddresses.etherToken); if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) { if (opts.takerAddress !== undefined) { @@ -102,14 +102,14 @@ export const swapQuoteConsumerUtils = { ); if (isEnoughEthAndWethBalance[1]) { // 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]) { - return ConsumerType.Forwarder; + return ExtensionContractType.Forwarder; } // Note: defaulting to forwarderConsumer if takerAddress is null or not enough balance of either wEth or Eth - return ConsumerType.Forwarder; + return ExtensionContractType.Forwarder; } else { - return ConsumerType.Exchange; + return ExtensionContractType.None; } }, }; diff --git a/packages/asset-swapper/test/swap_quote_consumer_test.ts b/packages/asset-swapper/test/swap_quote_consumer_test.ts index 68bcbed02e..8706c5b1b4 100644 --- a/packages/asset-swapper/test/swap_quote_consumer_test.ts +++ b/packages/asset-swapper/test/swap_quote_consumer_test.ts @@ -7,7 +7,7 @@ import * as chai from 'chai'; import 'mocha'; import { SwapQuote, SwapQuoteConsumer } from '../src'; -import { ConsumerType } from '../src/types'; +import { ExtensionContractType } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { migrateOnceAsync } from './utils/migrate'; @@ -145,13 +145,13 @@ describe('SwapQuoteConsumer', () => { // TODO(david) Check for valid MethodAbi it('should provide correct and optimized smart contract params for Forwarder contract when provided corresponding useConsumerType option', async () => { const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, { - useConsumerType: ConsumerType.Forwarder, + useExtensionContract: ExtensionContractType.Forwarder, }); 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 () => { const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, { - useConsumerType: ConsumerType.Exchange, + useExtensionContract: ExtensionContractType.None, }); expect(toAddress).to.deep.equal(contractWrappers.exchange.address); }); @@ -162,13 +162,13 @@ describe('SwapQuoteConsumer', () => { describe('valid swap quote', async () => { it('should provide correct and optimized calldata options for Forwarder contract when provided corresponding useConsumerType option', async () => { const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, { - useConsumerType: ConsumerType.Forwarder, + useExtensionContract: ExtensionContractType.Forwarder, }); 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 () => { const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, { - useConsumerType: ConsumerType.Exchange, + useExtensionContract: ExtensionContractType.None, }); expect(toAddress).to.deep.equal(contractWrappers.exchange.address); }); diff --git a/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts b/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts deleted file mode 100644 index 821e318ba1..0000000000 --- a/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { ContractAddresses, ContractWrappers } from '@0x/contract-wrappers'; -import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { MarketOperation, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import 'mocha'; - -import { SwapQuote } from '../src'; -import { ConsumerType } from '../src/types'; -import { swapQuoteConsumerUtils } from '../src/utils/swap_quote_consumer_utils'; - -import { chaiSetup } from './utils/chai_setup'; -import { migrateOnceAsync } from './utils/migrate'; -import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000); -const TESTRPC_NETWORK_ID = 50; -const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value => - value.multipliedBy(ONE_ETH_IN_WEI), -); -const LARGE_FILLABLE_AMOUNTS = [new BigNumber(20), new BigNumber(20), new BigNumber(20)].map(value => - value.multipliedBy(ONE_ETH_IN_WEI), -); - -describe('swapQuoteConsumerUtils', () => { - let contractWrappers: ContractWrappers; - let userAddresses: string[]; - let makerAddress: string; - let takerAddress: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAssetData: string; - let takerAssetData: string; - let wethAssetData: string; - let contractAddresses: ContractAddresses; - - const networkId = TESTRPC_NETWORK_ID; - before(async () => { - contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - const config = { - networkId, - contractAddresses, - }; - contractWrappers = new ContractWrappers(provider, config); - [takerAddress, makerAddress] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData, wethAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken), - ]; - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('getConsumerTypeForSwapQuoteAsync', () => { - let forwarderOrders: SignedOrder[]; - let exchangeOrders: SignedOrder[]; - let largeForwarderOrders: SignedOrder[]; - let forwarderSwapQuote: SwapQuote; - let exchangeSwapQuote: SwapQuote; - let largeForwarderSwapQuote: SwapQuote; - - beforeEach(async () => { - exchangeOrders = await getSignedOrdersWithNoFeesAsync( - provider, - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - FILLABLE_AMOUNTS, - ); - - forwarderOrders = await getSignedOrdersWithNoFeesAsync( - provider, - makerAssetData, - wethAssetData, - makerAddress, - takerAddress, - FILLABLE_AMOUNTS, - ); - - largeForwarderOrders = await getSignedOrdersWithNoFeesAsync( - provider, - makerAssetData, - wethAssetData, - makerAddress, - takerAddress, - LARGE_FILLABLE_AMOUNTS, - ); - - forwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees( - makerAssetData, - wethAssetData, - forwarderOrders, - MarketOperation.Sell, - ); - - largeForwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees( - makerAssetData, - wethAssetData, - largeForwarderOrders, - MarketOperation.Sell, - ); - - exchangeSwapQuote = getFullyFillableSwapQuoteWithNoFees( - makerAssetData, - takerAssetData, - exchangeOrders, - MarketOperation.Sell, - ); - }); - - it('should return exchange consumer if takerAsset is not wEth', async () => { - const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( - exchangeSwapQuote, - contractWrappers, - provider, - { takerAddress }, - ); - expect(consumerType).to.equal(ConsumerType.Exchange); - }); - it('should return forwarder consumer if takerAsset is wEth and have enough eth balance', async () => { - const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( - forwarderSwapQuote, - contractWrappers, - provider, - { takerAddress }, - ); - expect(consumerType).to.equal(ConsumerType.Forwarder); - }); - 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); - await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); - const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( - forwarderSwapQuote, - contractWrappers, - provider, - { takerAddress }, - ); - expect(consumerType).to.equal(ConsumerType.Exchange); - }); - 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); - await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); - const consumerType = await swapQuoteConsumerUtils.getConsumerTypeForSwapQuoteAsync( - largeForwarderSwapQuote, - contractWrappers, - provider, - { takerAddress }, - ); - expect(consumerType).to.equal(ConsumerType.Forwarder); - }); - }); -}); From e333ab18c7bc38e826016b2c834ef1ef57d2cf05 Mon Sep 17 00:00:00 2001 From: David Sun Date: Sat, 21 Sep 2019 13:07:50 -0400 Subject: [PATCH 2/2] removed smart quote consumer in favor of utils --- packages/asset-swapper/src/index.ts | 1 + .../smart_swap_quote_consumer.ts | 66 ------- .../quote_consumers/swap_quote_consumer.ts | 19 ++ packages/asset-swapper/src/types.ts | 9 +- .../src/utils/swap_quote_consumer_utils.ts | 4 +- .../test/swap_quote_consumer_test.ts | 14 +- .../test/swap_quote_consumer_utils_test.ts | 166 ++++++++++++++++++ 7 files changed, 197 insertions(+), 82 deletions(-) delete mode 100644 packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts create mode 100644 packages/asset-swapper/test/swap_quote_consumer_utils_test.ts diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 8f619ff9a4..fa5973fd22 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -37,6 +37,7 @@ export { SwapQuoteGetOutputOpts, SwapQuoteExecutionOpts, SwapQuoteInfo, + GetExtensionContractTypeOpts, SwapQuoteExecutionOptsBase, SwapQuoteGetOutputOptsBase, ForwarderSwapQuoteExecutionOpts, diff --git a/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts deleted file mode 100644 index 8f4d70b8d1..0000000000 --- a/packages/asset-swapper/src/quote_consumers/smart_swap_quote_consumer.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { SupportedProvider } from '@0x/web3-wrapper'; - -import { - CalldataInfo, - SmartContractParams, - SmartContractParamsInfo, - SmartSwapQuoteExecutionOpts, - SmartSwapQuoteGetOutputOpts, - SwapQuote, - SwapQuoteConsumerOpts, -} from '../types'; -import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; - -import { SwapQuoteConsumer } from './swap_quote_consumer'; - -export class SmartSwapQuoteConsumer extends SwapQuoteConsumer { - - private readonly _contractWrappers: ContractWrappers; - - constructor(supportedProvider: SupportedProvider, options: Partial = {}) { - super(supportedProvider, options); - this._contractWrappers = new ContractWrappers(this.provider, { - networkId: this.networkId, - }); - } - - /** - * Given a SwapQuote, returns 'CalldataInfo' for a 0x exchange call. See type definition of CalldataInfo for more information. - * @param quote An object that conforms to SwapQuote. See type definition for more information. - * @param opts Options for getting SmartContractParams. See type definition for more information. - */ - public async getCalldataOrThrowAsync( - quote: SwapQuote, - opts: Partial = {}, - ): Promise { - const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); - return super.getCalldataOrThrowAsync(quote, { useExtensionContract }); - } - - /** - * Given a SwapQuote, returns 'SmartContractParamsInfo' for a 0x exchange call. See type definition of SmartContractParamsInfo for more information. - * @param quote An object that conforms to SwapQuote. See type definition for more information. - * @param opts Options for getting SmartContractParams. See type definition for more information. - */ - public async getSmartContractParamsOrThrowAsync( - quote: SwapQuote, - opts: Partial = {}, - ): Promise> { - const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); - return super.getSmartContractParamsOrThrowAsync(quote, { useExtensionContract }); - } - - /** - * Given a SwapQuote and desired rate (in takerAsset), attempt to execute the swap. - * @param quote An object that conforms to SwapQuote. See type definition for more information. - * @param opts Options for getting CalldataInfo. See type definition for more information. - */ - public async executeSwapQuoteOrThrowAsync( - quote: SwapQuote, - opts: Partial = {}, - ): Promise { - const useExtensionContract = await swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(quote, this._contractWrappers, this.provider, opts); - return super.executeSwapQuoteOrThrowAsync(quote, { useExtensionContract }); - } -} diff --git a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts index eddd84b155..1da8192ec6 100644 --- a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts @@ -1,3 +1,4 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; import { providerUtils } from '@0x/utils'; import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper'; import * as _ from 'lodash'; @@ -6,6 +7,7 @@ import { constants } from '../constants'; import { CalldataInfo, ExtensionContractType, + GetExtensionContractTypeOpts, SmartContractParams, SmartContractParamsInfo, SwapQuote, @@ -15,6 +17,7 @@ import { SwapQuoteGetOutputOpts, } from '../types'; import { assert } from '../utils/assert'; +import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { ExchangeSwapQuoteConsumer } from './exchange_swap_quote_consumer'; import { ForwarderSwapQuoteConsumer } from './forwarder_swap_quote_consumer'; @@ -25,6 +28,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase = {}) { const { networkId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); @@ -36,6 +40,9 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase = {}, + ): Promise { + return swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync( + quote, + this._contractWrappers, + this.provider, + opts, + ); + } + private async _getConsumerForSwapQuoteAsync( opts: Partial, ): Promise> { diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 51a7bf7371..f7d1f6f828 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -1,7 +1,6 @@ import { MarketOperation, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { MethodAbi } from 'ethereum-types'; -import { ForwarderSwapQuoteConsumer } from './quote_consumers/forwarder_swap_quote_consumer'; /** * makerAssetData: The assetData representing the desired makerAsset. @@ -184,8 +183,9 @@ export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOptsB export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote; -export interface SmartSwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts { +export interface GetExtensionContractTypeOpts { takerAddress?: string; + ethAmount?: BigNumber; } /** @@ -203,11 +203,6 @@ export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOu */ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts, ForwarderSwapQuoteExecutionOpts {} -/** - * Represents the options for executing a swap quote with SmartSwapQuoteConsumer - */ -export interface SmartSwapQuoteExecutionOpts extends SmartSwapQuoteGetOutputOpts, ForwarderSwapQuoteExecutionOpts {} - /** * takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). * makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). diff --git a/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts b/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts index 58648dffc5..291b7f2cbc 100644 --- a/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts +++ b/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts @@ -9,7 +9,7 @@ import * as _ from 'lodash'; import { constants } from '../constants'; import { ExtensionContractType, - SmartSwapQuoteGetOutputOpts, + GetExtensionContractTypeOpts, SwapQuote, SwapQuoteConsumerError, SwapQuoteExecutionOpts, @@ -83,7 +83,7 @@ export const swapQuoteConsumerUtils = { quote: SwapQuote, contractWrappers: ContractWrappers, provider: Provider, - opts: Partial, + opts: Partial, ): Promise { const wethAssetData = assetDataUtils.encodeERC20AssetData(contractWrappers.contractAddresses.etherToken); if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) { diff --git a/packages/asset-swapper/test/swap_quote_consumer_test.ts b/packages/asset-swapper/test/swap_quote_consumer_test.ts index 8706c5b1b4..2fb10140ed 100644 --- a/packages/asset-swapper/test/swap_quote_consumer_test.ts +++ b/packages/asset-swapper/test/swap_quote_consumer_test.ts @@ -116,18 +116,18 @@ describe('SwapQuoteConsumer', () => { // * 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 // */ - // 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 takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); // 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); // takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); // 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 takerBalance = await erc20TokenContract.balanceOf.callAsync(takerAddress); // expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI)); @@ -143,13 +143,13 @@ describe('SwapQuoteConsumer', () => { describe('getSmartContractParamsOrThrow', () => { describe('valid swap quote', async () => { // 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, { useExtensionContract: ExtensionContractType.Forwarder, }); 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, { useExtensionContract: ExtensionContractType.None, }); @@ -160,13 +160,13 @@ describe('SwapQuoteConsumer', () => { describe('getCalldataOrThrow', () => { 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, { useExtensionContract: ExtensionContractType.Forwarder, }); 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, { useExtensionContract: ExtensionContractType.None, }); diff --git a/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts b/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts new file mode 100644 index 0000000000..ed22d2dc35 --- /dev/null +++ b/packages/asset-swapper/test/swap_quote_consumer_utils_test.ts @@ -0,0 +1,166 @@ +import { ContractAddresses, ContractWrappers } from '@0x/contract-wrappers'; +import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { MarketOperation, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { SwapQuote, SwapQuoteConsumer } from '../src'; +import { ExtensionContractType } from '../src/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { migrateOnceAsync } from './utils/migrate'; +import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000); +const TESTRPC_NETWORK_ID = 50; +const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value => + value.multipliedBy(ONE_ETH_IN_WEI), +); +const LARGE_FILLABLE_AMOUNTS = [new BigNumber(20), new BigNumber(20), new BigNumber(20)].map(value => + value.multipliedBy(ONE_ETH_IN_WEI), +); + +describe('swapQuoteConsumerUtils', () => { + let contractWrappers: ContractWrappers; + let userAddresses: string[]; + let makerAddress: string; + let takerAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let wethAssetData: string; + let contractAddresses: ContractAddresses; + let swapQuoteConsumer: SwapQuoteConsumer; + + const networkId = TESTRPC_NETWORK_ID; + before(async () => { + contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + [takerAddress, makerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData, wethAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken), + ]; + + swapQuoteConsumer = new SwapQuoteConsumer(provider, { + networkId, + }); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('getConsumerTypeForSwapQuoteAsync', () => { + let forwarderOrders: SignedOrder[]; + let exchangeOrders: SignedOrder[]; + let largeForwarderOrders: SignedOrder[]; + let forwarderSwapQuote: SwapQuote; + let exchangeSwapQuote: SwapQuote; + let largeForwarderSwapQuote: SwapQuote; + + beforeEach(async () => { + exchangeOrders = await getSignedOrdersWithNoFeesAsync( + provider, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + FILLABLE_AMOUNTS, + ); + + forwarderOrders = await getSignedOrdersWithNoFeesAsync( + provider, + makerAssetData, + wethAssetData, + makerAddress, + takerAddress, + FILLABLE_AMOUNTS, + ); + + largeForwarderOrders = await getSignedOrdersWithNoFeesAsync( + provider, + makerAssetData, + wethAssetData, + makerAddress, + takerAddress, + LARGE_FILLABLE_AMOUNTS, + ); + + forwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees( + makerAssetData, + wethAssetData, + forwarderOrders, + MarketOperation.Sell, + ); + + largeForwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees( + makerAssetData, + wethAssetData, + largeForwarderOrders, + MarketOperation.Sell, + ); + + exchangeSwapQuote = getFullyFillableSwapQuoteWithNoFees( + makerAssetData, + takerAssetData, + exchangeOrders, + MarketOperation.Sell, + ); + }); + + it('should return exchange consumer if takerAsset is not wEth', async () => { + const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync( + exchangeSwapQuote, + { takerAddress }, + ); + expect(extensionContractType).to.equal(ExtensionContractType.None); + }); + it('should return forwarder consumer if takerAsset is wEth and have enough eth balance', async () => { + const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync( + forwarderSwapQuote, + { takerAddress }, + ); + expect(extensionContractType).to.equal(ExtensionContractType.Forwarder); + }); + 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); + await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); + const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync( + forwarderSwapQuote, + { takerAddress }, + ); + 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 () => { + const etherInWei = new BigNumber(50).multipliedBy(ONE_ETH_IN_WEI); + await contractWrappers.weth9.deposit.sendTransactionAsync({ value: etherInWei, from: takerAddress }); + const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync( + largeForwarderSwapQuote, + { takerAddress }, + ); + expect(extensionContractType).to.equal(ExtensionContractType.Forwarder); + }); + }); +});