diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 81f87838c8..2757460f37 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -145,6 +145,10 @@ { "note": "Fix exchange proxy overhead gas being scaled by gas price", "pr": 2723 + }, + { + "note": "Remove 0x-API swap/v0-specifc code from asset-swapper", + "pr": 2725 } ] }, diff --git a/packages/asset-swapper/src/quote_consumers/exchange_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/exchange_swap_quote_consumer.ts deleted file mode 100644 index 99168ed10c..0000000000 --- a/packages/asset-swapper/src/quote_consumers/exchange_swap_quote_consumer.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { ExchangeContract } from '@0x/contract-wrappers'; -import { providerUtils } from '@0x/utils'; -import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper'; -import * as _ from 'lodash'; - -import { constants } from '../constants'; -import { - CalldataInfo, - MarketOperation, - SwapQuote, - SwapQuoteConsumerBase, - SwapQuoteConsumerOpts, - SwapQuoteExecutionOpts, - SwapQuoteGetOutputOpts, -} from '../types'; -import { assert } from '../utils/assert'; -import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; - -export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase { - public readonly provider: ZeroExProvider; - public readonly chainId: number; - - private readonly _exchangeContract: ExchangeContract; - - constructor( - supportedProvider: SupportedProvider, - public readonly contractAddresses: ContractAddresses, - options: Partial = {}, - ) { - const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); - assert.isNumber('chainId', chainId); - const provider = providerUtils.standardizeOrThrow(supportedProvider); - this.provider = provider; - this.chainId = chainId; - this._exchangeContract = new ExchangeContract(contractAddresses.exchange, supportedProvider); - } - - public async getCalldataOrThrowAsync( - quote: SwapQuote, - _opts: Partial = {}, - ): Promise { - assert.isValidSwapQuote('quote', quote); - const { orders } = quote; - const signatures = _.map(orders, o => o.signature); - - let calldataHexString; - if (quote.type === MarketOperation.Buy) { - calldataHexString = this._exchangeContract - .marketBuyOrdersFillOrKill(orders, quote.makerAssetFillAmount, signatures) - .getABIEncodedTransactionData(); - } else { - calldataHexString = this._exchangeContract - .marketSellOrdersFillOrKill(orders, quote.takerAssetFillAmount, signatures) - .getABIEncodedTransactionData(); - } - - return { - calldataHexString, - ethAmount: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount, - toAddress: this._exchangeContract.address, - allowanceTarget: this.contractAddresses.erc20Proxy, - }; - } - - public async executeSwapQuoteOrThrowAsync( - quote: SwapQuote, - opts: Partial, - ): Promise { - assert.isValidSwapQuote('quote', quote); - - const { takerAddress, gasLimit, ethAmount } = opts; - - if (takerAddress !== undefined) { - assert.isETHAddressHex('takerAddress', takerAddress); - } - if (gasLimit !== undefined) { - assert.isNumber('gasLimit', gasLimit); - } - if (ethAmount !== undefined) { - assert.isBigNumber('ethAmount', ethAmount); - } - const { orders, gasPrice } = quote; - const signatures = orders.map(o => o.signature); - - const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts); - const value = ethAmount || quote.worstCaseQuoteInfo.protocolFeeInWeiAmount; - let txHash: string; - if (quote.type === MarketOperation.Buy) { - const { makerAssetFillAmount } = quote; - txHash = await this._exchangeContract - .marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, signatures) - .sendTransactionAsync({ - from: finalTakerAddress, - gas: gasLimit, - gasPrice, - value, - }); - } else { - const { takerAssetFillAmount } = quote; - txHash = await this._exchangeContract - .marketSellOrdersFillOrKill(orders, takerAssetFillAmount, signatures) - .sendTransactionAsync({ - from: finalTakerAddress, - gas: gasLimit, - gasPrice, - value, - }); - } - // TODO(dorothy-zbornak): Handle signature request denied - // (see contract-wrappers/decorators) - // and ExchangeRevertErrors.IncompleteFillError. - return txHash; - } -} diff --git a/packages/asset-swapper/src/quote_consumers/forwarder_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/forwarder_swap_quote_consumer.ts deleted file mode 100644 index ed1e7aae95..0000000000 --- a/packages/asset-swapper/src/quote_consumers/forwarder_swap_quote_consumer.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { ForwarderContract } from '@0x/contract-wrappers'; -import { assetDataUtils } from '@0x/order-utils'; -import { providerUtils } from '@0x/utils'; -import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper'; -import * as _ from 'lodash'; - -import { constants } from '../constants'; -import { - CalldataInfo, - MarketOperation, - SwapQuote, - SwapQuoteConsumerBase, - SwapQuoteConsumerOpts, - SwapQuoteExecutionOpts, - SwapQuoteGetOutputOpts, -} from '../types'; -import { affiliateFeeUtils } from '../utils/affiliate_fee_utils'; -import { assert } from '../utils/assert'; -import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; - -const { NULL_ADDRESS } = constants; - -export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase { - public readonly provider: ZeroExProvider; - public readonly chainId: number; - public buyQuoteSellAmountScalingFactor = 1.0001; // 100% + 1 bps - - private readonly _forwarder: ForwarderContract; - - constructor( - supportedProvider: SupportedProvider, - public readonly contractAddresses: ContractAddresses, - options: Partial = {}, - ) { - const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options); - assert.isNumber('chainId', chainId); - const provider = providerUtils.standardizeOrThrow(supportedProvider); - this.provider = provider; - this.chainId = chainId; - this._forwarder = new ForwarderContract(contractAddresses.forwarder, supportedProvider); - } - - /** - * Given a SwapQuote, returns 'CalldataInfo' for a forwarder extension 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 CalldataInfo. See type definition for more information. - */ - public async getCalldataOrThrowAsync( - quote: SwapQuote, - opts: Partial = {}, - ): Promise { - assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); - const { extensionContractOpts } = { ...constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS, ...opts }; - assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts); - const { feeRecipient, feePercentage } = extensionContractOpts; - const { orders, worstCaseQuoteInfo } = quote; - - const normalizedFeeRecipientAddress = feeRecipient.toLowerCase(); - const signatures = _.map(orders, o => o.signature); - const ethAmountWithFees = affiliateFeeUtils.getTotalEthAmountWithAffiliateFee( - { - ...worstCaseQuoteInfo, - // HACK(dorothy-zbornak): The forwarder contract has a rounding bug - // that causes buys of low-decimal tokens to not complete. - // Scaling the max sell amount by 1bps seems to be sufficient to - // overcome this. - ...(quote.type === MarketOperation.Buy - ? { - // tslint:disable-next-line: custom-no-magic-numbers - totalTakerAssetAmount: worstCaseQuoteInfo.totalTakerAssetAmount - .times(this.buyQuoteSellAmountScalingFactor) - .integerValue(), - } - : {}), - }, - feePercentage, - ); - const feeAmount = affiliateFeeUtils.getFeeAmount(worstCaseQuoteInfo, feePercentage); - - let calldataHexString; - if (quote.type === MarketOperation.Buy) { - calldataHexString = this._forwarder - .marketBuyOrdersWithEth( - orders, - quote.makerAssetFillAmount, - signatures, - [feeAmount], - [normalizedFeeRecipientAddress], - ) - .getABIEncodedTransactionData(); - } else { - calldataHexString = this._forwarder - .marketSellAmountWithEth( - orders, - quote.takerAssetFillAmount, - signatures, - [feeAmount], - [normalizedFeeRecipientAddress], - ) - .getABIEncodedTransactionData(); - } - - return { - calldataHexString, - toAddress: this._forwarder.address, - ethAmount: ethAmountWithFees, - allowanceTarget: NULL_ADDRESS, - }; - } - - /** - * Given a SwapQuote and desired rate (in Eth), 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 { - assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); - - const { ethAmount: providedEthAmount, takerAddress, gasLimit, extensionContractOpts } = { - ...constants.DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS, - ...opts, - }; - - assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts); - - const { feeRecipient, feePercentage } = extensionContractOpts; - - if (providedEthAmount !== undefined) { - assert.isBigNumber('ethAmount', providedEthAmount); - } - if (takerAddress !== undefined) { - assert.isETHAddressHex('takerAddress', takerAddress); - } - if (gasLimit !== undefined) { - assert.isNumber('gasLimit', gasLimit); - } - const { orders, gasPrice } = quote; // tslint:disable-line:no-unused-variable - const signatures = orders.map(o => o.signature); - - // get taker address - const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts); - // if no ethAmount is provided, default to the worst totalTakerAssetAmount - const ethAmountWithFees = - providedEthAmount || - affiliateFeeUtils.getTotalEthAmountWithAffiliateFee(quote.worstCaseQuoteInfo, feePercentage); - const feeAmount = affiliateFeeUtils.getFeeAmount( - { - ...quote.worstCaseQuoteInfo, - // HACK(dorothy-zbornak): The forwarder contract has a rounding bug - // that causes buys of low-decimal tokens to not complete. - // Scaling the max sell amount by 1bps seems to be sufficient to - // overcome this. - ...(quote.type === MarketOperation.Buy - ? { - // tslint:disable-next-line: custom-no-magic-numbers - totalTakerAssetAmount: quote.worstCaseQuoteInfo.totalTakerAssetAmount - .times(this.buyQuoteSellAmountScalingFactor) - .integerValue(), - } - : {}), - }, - feePercentage, - ); - let txHash: string; - if (quote.type === MarketOperation.Buy) { - const { makerAssetFillAmount } = quote; - txHash = await this._forwarder - .marketBuyOrdersWithEth(orders, makerAssetFillAmount, signatures, [feeAmount], [feeRecipient]) - .sendTransactionAsync({ - from: finalTakerAddress, - gas: gasLimit, - gasPrice, - value: ethAmountWithFees, - }); - } else { - txHash = await this._forwarder - .marketSellAmountWithEth(orders, quote.takerAssetFillAmount, signatures, [feeAmount], [feeRecipient]) - .sendTransactionAsync({ - from: finalTakerAddress, - gas: gasLimit, - gasPrice, - value: ethAmountWithFees, - }); - } - // TODO(dorothy-zbornak): Handle signature request denied - // (see contract-wrappers/decorators) - // and ForwarderRevertErrors.CompleteBuyFailed. - return txHash; - } - - private _getEtherTokenAssetDataOrThrow(): string { - return assetDataUtils.encodeERC20AssetData(this.contractAddresses.etherToken); - } -} 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 029663978c..8a5bea1691 100644 --- a/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/swap_quote_consumer.ts @@ -18,15 +18,11 @@ import { assert } from '../utils/assert'; import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { ExchangeProxySwapQuoteConsumer } from './exchange_proxy_swap_quote_consumer'; -import { ExchangeSwapQuoteConsumer } from './exchange_swap_quote_consumer'; -import { ForwarderSwapQuoteConsumer } from './forwarder_swap_quote_consumer'; export class SwapQuoteConsumer implements SwapQuoteConsumerBase { public readonly provider: ZeroExProvider; public readonly chainId: number; - private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer; - private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer; private readonly _contractAddresses: ContractAddresses; private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer; @@ -45,8 +41,6 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase { this.provider = provider; this.chainId = chainId; this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId); - this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, this._contractAddresses, options); - this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, this._contractAddresses, options); this._exchangeProxyConsumer = new ExchangeProxySwapQuoteConsumer( supportedProvider, this._contractAddresses, @@ -100,13 +94,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase { } private async _getConsumerForSwapQuoteAsync(opts: Partial): Promise { + // ( akroeger)leaving this switch to use different contracts in the future switch (opts.useExtensionContract) { - case ExtensionContractType.Forwarder: - return this._forwarderConsumer; case ExtensionContractType.ExchangeProxy: return this._exchangeProxyConsumer; default: - return this._exchangeConsumer; + return this._exchangeProxyConsumer; } } } 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 b08dee3c8b..b7fee5f0f6 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -62,7 +62,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { gasSchedule: {}, exchangeProxyOverhead: () => ZERO_AMOUNT, allowFallback: true, - shouldBatchBridgeOrders: true, shouldGenerateQuoteReport: false, }; 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 f88bb7fa05..4dd1e5f0eb 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -459,7 +459,6 @@ export class MarketOperationUtils { excludedSources: _opts.excludedSources, feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, - shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders, }, ); return optimizedOrders; @@ -497,7 +496,6 @@ export class MarketOperationUtils { orderDomain: this._orderDomain, contractAddresses: this.contractAddresses, bridgeSlippage: opts.bridgeSlippage || 0, - shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders, }; // Convert native orders and dex quotes into `Fill` objects. 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 d4c6cf110d..fcb0fdf41a 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -1,7 +1,7 @@ import { ContractAddresses } from '@0x/contract-addresses'; import { assetDataUtils, ERC20AssetData, generatePseudoRandomSalt, orderCalculationUtils } from '@0x/order-utils'; import { RFQTIndicativeQuote } from '@0x/quote-server'; -import { ERC20BridgeAssetData, SignedOrder } from '@0x/types'; +import { SignedOrder } from '@0x/types'; import { AbiEncoder, BigNumber } from '@0x/utils'; import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; @@ -40,30 +40,6 @@ import { // tslint:disable completed-docs no-unnecessary-type-assertion -interface DexForwaderBridgeData { - inputToken: string; - calls: Array<{ - target: string; - inputTokenAmount: BigNumber; - outputTokenAmount: BigNumber; - bridgeData: string; - }>; -} - -const dexForwarderBridgeDataEncoder = AbiEncoder.create([ - { name: 'inputToken', type: 'address' }, - { - name: 'calls', - type: 'tuple[]', - components: [ - { name: 'target', type: 'address' }, - { name: 'inputTokenAmount', type: 'uint256' }, - { name: 'outputTokenAmount', type: 'uint256' }, - { name: 'bridgeData', type: 'bytes' }, - ], - }, -]); - export function createDummyOrderForSampler( makerAssetData: string, takerAssetData: string, @@ -159,7 +135,6 @@ export interface CreateOrderFromPathOpts { orderDomain: OrderDomain; contractAddresses: ContractAddresses; bridgeSlippage: number; - shouldBatchBridgeOrders: boolean; } export function createOrdersFromTwoHopSample( @@ -340,47 +315,6 @@ export function createBridgeOrder( }; } -export function createBatchedBridgeOrder(fills: CollapsedFill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder { - const [makerToken, takerToken] = getMakerTakerTokens(opts); - let totalMakerAssetAmount = ZERO_AMOUNT; - let totalTakerAssetAmount = ZERO_AMOUNT; - const batchedBridgeData: DexForwaderBridgeData = { - inputToken: takerToken, - calls: [], - }; - for (const fill of fills) { - const bridgeOrder = createBridgeOrder(fill, makerToken, takerToken, opts); - totalMakerAssetAmount = totalMakerAssetAmount.plus(bridgeOrder.makerAssetAmount); - totalTakerAssetAmount = totalTakerAssetAmount.plus(bridgeOrder.takerAssetAmount); - const { bridgeAddress, bridgeData: orderBridgeData } = assetDataUtils.decodeAssetDataOrThrow( - bridgeOrder.makerAssetData, - ) as ERC20BridgeAssetData; - batchedBridgeData.calls.push({ - target: bridgeAddress, - bridgeData: orderBridgeData, - inputTokenAmount: bridgeOrder.takerAssetAmount, - outputTokenAmount: bridgeOrder.makerAssetAmount, - }); - } - const batchedBridgeAddress = opts.contractAddresses.dexForwarderBridge; - const batchedMakerAssetData = assetDataUtils.encodeERC20BridgeAssetData( - makerToken, - batchedBridgeAddress, - dexForwarderBridgeDataEncoder.encode(batchedBridgeData), - ); - return { - fills, - makerAssetData: batchedMakerAssetData, - takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken), - makerAddress: batchedBridgeAddress, - makerAssetAmount: totalMakerAssetAmount, - takerAssetAmount: totalTakerAssetAmount, - fillableMakerAssetAmount: totalMakerAssetAmount, - fillableTakerAssetAmount: totalTakerAssetAmount, - ...createCommonBridgeOrderFields(opts.orderDomain), - }; -} - export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] { const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken; const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path.ts b/packages/asset-swapper/src/utils/market_operation_utils/path.ts index e75e589081..8a633753f7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path.ts @@ -3,13 +3,7 @@ import { BigNumber } from '@0x/utils'; import { MarketOperation } from '../../types'; import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants'; -import { - createBatchedBridgeOrder, - createBridgeOrder, - createNativeOrder, - CreateOrderFromPathOpts, - getMakerTakerTokens, -} from './orders'; +import { createBridgeOrder, createNativeOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders'; import { getCompleteRate, getRate } from './rate_utils'; import { CollapsedFill, @@ -123,14 +117,9 @@ export class Path { } contiguousBridgeFills.push(collapsedFills[j]); } - // Always use DexForwarderBridge unless configured not to - if (!opts.shouldBatchBridgeOrders) { - this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts)); - i += 1; - } else { - this.orders.push(createBatchedBridgeOrder(contiguousBridgeFills, opts)); - i += contiguousBridgeFills.length; - } + + this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts)); + i += 1; } return this as CollapsedPath; } 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 0b2d5e331c..028d39d339 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -289,11 +289,6 @@ export interface GetMarketOrdersOpts { */ allowFallback: boolean; rfqt?: GetMarketOrdersRfqtOpts; - /** - * Whether to combine contiguous bridge orders into a single DexForwarderBridge - * order. Defaults to `true`. - */ - shouldBatchBridgeOrders: boolean; /** * Whether to generate a quote report */ diff --git a/packages/asset-swapper/test/exchange_swap_quote_consumer_test.ts b/packages/asset-swapper/test/exchange_swap_quote_consumer_test.ts deleted file mode 100644 index fd1ca162a7..0000000000 --- a/packages/asset-swapper/test/exchange_swap_quote_consumer_test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { ERC20TokenContract, ExchangeContract } from '@0x/contract-wrappers'; -import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils'; -import { migrateOnceAsync } from '@0x/migrations'; -import { assetDataUtils } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import 'mocha'; - -import { SwapQuote } from '../src'; -import { constants } from '../src/constants'; -import { ExchangeSwapQuoteConsumer } from '../src/quote_consumers/exchange_swap_quote_consumer'; -import { MarketOperation, SignedOrderWithFillableAmounts } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE); -const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000); -const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID; -const UNLIMITED_ALLOWANCE = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers - -const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array> = [ - { - takerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - }, - { - takerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - }, - { - takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - }, -]; - -const expectMakerAndTakerBalancesAsyncFactory = ( - erc20TokenContract: ERC20TokenContract, - makerAddress: string, - takerAddress: string, -) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => { - const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync(); - const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync(); - expect(makerBalance).to.bignumber.equal(expectedMakerBalance); - expect(takerBalance).to.bignumber.equal(expectedTakerBalance); -}; - -describe('ExchangeSwapQuoteConsumer', () => { - let userAddresses: string[]; - let erc20MakerTokenContract: ERC20TokenContract; - let erc20TakerTokenContract: ERC20TokenContract; - let coinbaseAddress: string; - let makerAddress: string; - let takerAddress: string; - let orderFactory: OrderFactory; - let feeRecipient: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAssetData: string; - let takerAssetData: string; - let contractAddresses: ContractAddresses; - let exchangeContract: ExchangeContract; - - const chainId = TESTRPC_CHAIN_ID; - - let orders: SignedOrderWithFillableAmounts[]; - let marketSellSwapQuote: SwapQuote; - let marketBuySwapQuote: SwapQuote; - let swapQuoteConsumer: ExchangeSwapQuoteConsumer; - let expectMakerAndTakerBalancesForMakerAssetAsync: ( - expectedMakerBalance: BigNumber, - expectedTakerBalance: BigNumber, - ) => Promise; - let expectMakerAndTakerBalancesForTakerAssetAsync: ( - expectedMakerBalance: BigNumber, - expectedTakerBalance: BigNumber, - ) => Promise; - - before(async () => { - contractAddresses = await migrateOnceAsync(provider); - await blockchainLifecycle.startAsync(); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - [coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - ]; - erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider); - erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider); - exchangeContract = new ExchangeContract(contractAddresses.exchange, provider); - // Configure order defaults - const defaultOrderParams = { - ...devConstants.STATIC_ORDER_PARAMS, - makerAddress, - takerAddress, - makerAssetData, - takerAssetData, - makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA, - takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - feeRecipientAddress: feeRecipient, - exchangeAddress: contractAddresses.exchange, - chainId, - }; - const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory( - erc20TakerTokenContract, - makerAddress, - takerAddress, - ); - expectMakerAndTakerBalancesForMakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory( - erc20MakerTokenContract, - makerAddress, - takerAddress, - ); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - orders = []; - for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) { - const order = await orderFactory.newSignedOrderAsync(partialOrder); - const prunedOrder = { - ...order, - ...partialOrder, - }; - orders.push(prunedOrder as SignedOrderWithFillableAmounts); - } - - marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync( - makerAssetData, - takerAssetData, - orders, - MarketOperation.Sell, - GAS_PRICE, - ); - - marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync( - makerAssetData, - takerAssetData, - orders, - MarketOperation.Buy, - GAS_PRICE, - ); - - swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, contractAddresses, { - chainId, - }); - - await erc20MakerTokenContract - .transfer(makerAddress, marketBuySwapQuote.worstCaseQuoteInfo.makerAssetAmount) - .sendTransactionAsync({ - from: coinbaseAddress, - }); - await erc20TakerTokenContract - .transfer(takerAddress, marketBuySwapQuote.worstCaseQuoteInfo.totalTakerAssetAmount) - .sendTransactionAsync({ - from: coinbaseAddress, - }); - await erc20MakerTokenContract - .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE) - .sendTransactionAsync({ from: makerAddress }); - await erc20TakerTokenContract - .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE) - .sendTransactionAsync({ from: takerAddress }); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#executeSwapQuoteOrThrowAsync', () => { - /* - * 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 a marketSell execution when provided a MarketSell type swapQuote', async () => { - await expectMakerAndTakerBalancesForMakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - await expectMakerAndTakerBalancesForTakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { - takerAddress, - gasLimit: 4000000, - }); - await expectMakerAndTakerBalancesForMakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - await expectMakerAndTakerBalancesForTakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - }); - it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => { - await expectMakerAndTakerBalancesForMakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - await expectMakerAndTakerBalancesForTakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { - takerAddress, - gasLimit: 4000000, - }); - await expectMakerAndTakerBalancesForMakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - await expectMakerAndTakerBalancesForTakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - }); - }); - - describe('#getCalldataOrThrow', () => { - describe('valid swap quote', async () => { - it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => { - await expectMakerAndTakerBalancesForMakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketSellSwapQuote, - {}, - ); - expect(toAddress).to.deep.equal(exchangeContract.address); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - gas: 4000000, - gasPrice: GAS_PRICE, - value: ethAmount, - }); - await expectMakerAndTakerBalancesForMakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => { - await expectMakerAndTakerBalancesForMakerAssetAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketBuySwapQuote, - {}, - ); - expect(toAddress).to.deep.equal(exchangeContract.address); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - gas: 4000000, - gasPrice: GAS_PRICE, - value: ethAmount, - }); - await expectMakerAndTakerBalancesForMakerAssetAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - }); - }); -}); diff --git a/packages/asset-swapper/test/forwarder_swap_quote_consumer_test.ts b/packages/asset-swapper/test/forwarder_swap_quote_consumer_test.ts deleted file mode 100644 index f1f1273fbb..0000000000 --- a/packages/asset-swapper/test/forwarder_swap_quote_consumer_test.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { ERC20TokenContract, ForwarderContract } from '@0x/contract-wrappers'; -import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils'; -import { migrateOnceAsync } from '@0x/migrations'; -import { assetDataUtils } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import 'mocha'; - -import { SwapQuote } from '../src'; -import { constants } from '../src/constants'; -import { ForwarderSwapQuoteConsumer } from '../src/quote_consumers/forwarder_swap_quote_consumer'; -import { MarketOperation, SignedOrderWithFillableAmounts } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE); -const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000); -const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID; - -const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers -const FEE_PERCENTAGE = 0.05; -const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array> = [ - { - takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI), - }, - { - takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI), - }, - { - takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI), - makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI), - fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI), - }, -]; - -const expectMakerAndTakerBalancesAsyncFactory = ( - erc20TokenContract: ERC20TokenContract, - makerAddress: string, - takerAddress: string, -) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => { - const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync(); - const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync(); - expect(makerBalance).to.bignumber.equal(expectedMakerBalance); - expect(takerBalance).to.bignumber.equal(expectedTakerBalance); -}; - -describe('ForwarderSwapQuoteConsumer', () => { - let userAddresses: string[]; - let coinbaseAddress: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAssetData: string; - let takerAssetData: string; - let orderFactory: OrderFactory; - let invalidOrderFactory: OrderFactory; - let wethAssetData: string; - let contractAddresses: ContractAddresses; - let erc20TokenContract: ERC20TokenContract; - let forwarderContract: ForwarderContract; - - let orders: SignedOrderWithFillableAmounts[]; - let invalidOrders: SignedOrderWithFillableAmounts[]; - let marketSellSwapQuote: SwapQuote; - let marketBuySwapQuote: SwapQuote; - let invalidMarketBuySwapQuote: SwapQuote; - let swapQuoteConsumer: ForwarderSwapQuoteConsumer; - let expectMakerAndTakerBalancesAsync: ( - expectedMakerBalance: BigNumber, - expectedTakerBalance: BigNumber, - ) => Promise; - const chainId = TESTRPC_CHAIN_ID; - - before(async () => { - contractAddresses = await migrateOnceAsync(provider); - await blockchainLifecycle.startAsync(); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - [coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - erc20TokenContract = new ERC20TokenContract(makerTokenAddress, provider); - forwarderContract = new ForwarderContract(contractAddresses.forwarder, provider); - [makerAssetData, takerAssetData, wethAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken), - ]; - // Configure order defaults - const defaultOrderParams = { - ...devConstants.STATIC_ORDER_PARAMS, - makerAddress, - takerAddress: constants.NULL_ADDRESS, - makerAssetData, - takerAssetData: wethAssetData, - makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA, - takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - feeRecipientAddress: feeRecipient, - exchangeAddress: contractAddresses.exchange, - chainId, - }; - const invalidDefaultOrderParams = { - ...defaultOrderParams, - ...{ - takerAssetData, - }, - }; - const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory( - erc20TokenContract, - makerAddress, - takerAddress, - ); - invalidOrderFactory = new OrderFactory(privateKey, invalidDefaultOrderParams); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - const UNLIMITED_ALLOWANCE = UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - - const totalFillableAmount = new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI); - - await erc20TokenContract.transfer(makerAddress, totalFillableAmount).sendTransactionAsync({ - from: coinbaseAddress, - }); - - await erc20TokenContract - .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE) - .sendTransactionAsync({ from: makerAddress }); - - await forwarderContract.approveMakerAssetProxy(makerAssetData).sendTransactionAsync({ from: makerAddress }); - - orders = []; - for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) { - const order = await orderFactory.newSignedOrderAsync(partialOrder); - const prunedOrder = { - ...order, - ...partialOrder, - }; - orders.push(prunedOrder as SignedOrderWithFillableAmounts); - } - - invalidOrders = []; - for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) { - const order = await invalidOrderFactory.newSignedOrderAsync(partialOrder); - const prunedOrder = { - ...order, - ...partialOrder, - }; - invalidOrders.push(prunedOrder as SignedOrderWithFillableAmounts); - } - - marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync( - makerAssetData, - wethAssetData, - orders, - MarketOperation.Sell, - GAS_PRICE, - ); - - marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync( - makerAssetData, - wethAssetData, - orders, - MarketOperation.Buy, - GAS_PRICE, - ); - - invalidMarketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync( - makerAssetData, - takerAssetData, - invalidOrders, - MarketOperation.Buy, - GAS_PRICE, - ); - - swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, contractAddresses, { - chainId, - }); - swapQuoteConsumer.buyQuoteSellAmountScalingFactor = 1; - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#executeSwapQuoteOrThrowAsync', () => { - describe('validation', () => { - it('should throw if swapQuote provided is not a valid forwarder SwapQuote (taker asset is wEth)', async () => { - expect( - swapQuoteConsumer.executeSwapQuoteOrThrowAsync(invalidMarketBuySwapQuote, { takerAddress }), - ).to.be.rejectedWith( - `Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`, - ); - }); - }); - - // TODO(david) test execution of swap quotes with fee orders - describe('valid swap quote', () => { - /* - * 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 a marketBuy execution when provided a MarketBuy type swapQuote', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { - takerAddress, - gasLimit: 4000000, - ethAmount: new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - - it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { - takerAddress, - gasLimit: 4000000, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - - it('should perform a marketBuy execution with affiliate fees', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { - takerAddress, - gasLimit: 4000000, - extensionContractOpts: { - feePercentage: 0.05, - feeRecipient, - }, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus( - marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount, - ); - expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal( - new BigNumber(FEE_PERCENTAGE).times(totalEthSpent), - ); - }); - - it('should perform a marketSell execution with affiliate fees', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { - takerAddress, - gasLimit: 4000000, - extensionContractOpts: { - feePercentage: 0.05, - feeRecipient, - }, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus( - marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount, - ); - expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal( - new BigNumber(FEE_PERCENTAGE).times(totalEthSpent), - ); - }); - }); - }); - - describe('#getCalldataOrThrow', () => { - describe('validation', () => { - it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => { - expect(swapQuoteConsumer.getCalldataOrThrowAsync(invalidMarketBuySwapQuote, {})).to.be.rejectedWith( - `Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`, - ); - }); - }); - - describe('valid swap quote', async () => { - it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketSellSwapQuote, - {}, - ); - expect(toAddress).to.deep.equal(forwarderContract.address); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - value: ethAmount, - gasPrice: GAS_PRICE, - gas: 4000000, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketBuySwapQuote, - {}, - ); - expect(toAddress).to.deep.equal(contractAddresses.forwarder); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - value: ethAmount, - gasPrice: GAS_PRICE, - gas: 4000000, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - }); - it('provide correct and optimized calldata options with affiliate fees for a marketSell SwapQuote', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketSellSwapQuote, - { - extensionContractOpts: { - feePercentage: 0.05, - feeRecipient, - }, - }, - ); - expect(toAddress).to.deep.equal(contractAddresses.forwarder); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - value: ethAmount, - gasPrice: GAS_PRICE, - gas: 4000000, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus( - marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount, - ); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal( - new BigNumber(FEE_PERCENTAGE).times(totalEthSpent), - ); - }); - it('provide correct and optimized calldata options with affiliate fees for a marketBuy SwapQuote', async () => { - await expectMakerAndTakerBalancesAsync( - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - constants.ZERO_AMOUNT, - ); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync( - marketBuySwapQuote, - { - extensionContractOpts: { - feePercentage: 0.05, - feeRecipient, - }, - }, - ); - expect(toAddress).to.deep.equal(contractAddresses.forwarder); - await web3Wrapper.sendTransactionAsync({ - from: takerAddress, - to: toAddress, - data: calldataHexString, - value: ethAmount, - gasPrice: GAS_PRICE, - gas: 4000000, - }); - await expectMakerAndTakerBalancesAsync( - constants.ZERO_AMOUNT, - new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI), - ); - const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus( - marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount, - ); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient); - expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal( - new BigNumber(FEE_PERCENTAGE).times(totalEthSpent), - ); - }); - }); - }); - // tslint:disable-next-line: max-file-line-count -}); diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 5c4c815837..85e0442f69 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -298,23 +298,6 @@ describe('MarketOperationUtils tests', () => { return rates; } - function getSortedOrderSources(side: MarketOperation, orders: OptimizedMarketOrder[]): ERC20BridgeSource[][] { - return ( - orders - // Sort fills by descending rate. - .map(o => { - return o.fills - .slice() - .sort((a, b) => - side === MarketOperation.Sell - ? b.output.div(b.input).comparedTo(a.output.div(a.input)) - : b.input.div(b.output).comparedTo(a.input.div(a.output)), - ) - .map(f => f.source); - }) - ); - } - const NUM_SAMPLES = 3; interface RatesBySource { @@ -507,7 +490,6 @@ describe('MarketOperationUtils tests', () => { maxFallbackSlippage: 100, excludedSources: DEFAULT_EXCLUDED, allowFallback: false, - shouldBatchBridgeOrders: false, }; beforeEach(() => { @@ -1273,7 +1255,6 @@ describe('MarketOperationUtils tests', () => { excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor), numSamples: 4, bridgeSlippage: 0, - shouldBatchBridgeOrders: false, }, ); const result = ordersAndReport.optimizedOrders; @@ -1291,37 +1272,6 @@ describe('MarketOperationUtils tests', () => { expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress); }); - it('batches contiguous bridge sources', async () => { - const rates: RatesBySource = {}; - rates[ERC20BridgeSource.Uniswap] = [1, 0.01, 0.01, 0.01]; - rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01]; - rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01]; - rates[ERC20BridgeSource.Curve] = [0.48, 0.01, 0.01, 0.01]; - replaceSamplerOps({ - getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), - }); - const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync( - createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), - FILL_AMOUNT, - { - ...DEFAULT_OPTS, - numSamples: 4, - excludedSources: [ - ERC20BridgeSource.Kyber, - ..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.Curve), - ], - shouldBatchBridgeOrders: true, - }, - ); - const improvedOrders = improvedOrdersResponse.optimizedOrders; - expect(improvedOrders).to.be.length(3); - const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders); - expect(orderFillSources).to.deep.eq([ - [ERC20BridgeSource.Uniswap], - [ERC20BridgeSource.Native], - [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve], - ]); - }); it('factors in exchange proxy gas overhead', async () => { // Uniswap has a slightly better rate than LiquidityProvider, // but LiquidityProvider is better accounting for the EP gas overhead. @@ -1380,7 +1330,6 @@ describe('MarketOperationUtils tests', () => { maxFallbackSlippage: 100, excludedSources: DEFAULT_EXCLUDED, allowFallback: false, - shouldBatchBridgeOrders: false, }; beforeEach(() => { @@ -1732,31 +1681,6 @@ describe('MarketOperationUtils tests', () => { expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); }); - it('batches contiguous bridge sources', async () => { - const rates: RatesBySource = { ...ZERO_RATES }; - rates[ERC20BridgeSource.Native] = [0.3, 0.01, 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({ - getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), - }); - const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync( - createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), - FILL_AMOUNT, - { - ...DEFAULT_OPTS, - numSamples: 4, - shouldBatchBridgeOrders: true, - }, - ); - const improvedOrders = improvedOrdersResponse.optimizedOrders; - expect(improvedOrders).to.be.length(2); - const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders); - expect(orderFillSources).to.deep.eq([ - [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap], - [ERC20BridgeSource.Native], - ]); - }); it('factors in exchange proxy gas overhead', async () => { // Uniswap has a slightly better rate than LiquidityProvider, // but LiquidityProvider is better accounting for the EP gas overhead.