removed v0-specific code in asset-swapper

This commit is contained in:
Alex Kroeger 2020-10-07 13:00:01 -07:00
parent 32218ce25e
commit c8886febb9
11 changed files with 5 additions and 1171 deletions

View File

@ -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<SwapQuoteConsumerOpts> = {},
) {
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<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> {
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<SwapQuoteExecutionOpts>,
): Promise<string> {
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;
}
}

View File

@ -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<SwapQuoteConsumerOpts> = {},
) {
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<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> {
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<SwapQuoteExecutionOpts>,
): Promise<string> {
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);
}
}

View File

@ -18,15 +18,11 @@ import { assert } from '../utils/assert';
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
import { ExchangeProxySwapQuoteConsumer } from './exchange_proxy_swap_quote_consumer'; 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 { export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
public readonly chainId: number; public readonly chainId: number;
private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
private readonly _contractAddresses: ContractAddresses; private readonly _contractAddresses: ContractAddresses;
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer; private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
@ -45,8 +41,6 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
this.provider = provider; this.provider = provider;
this.chainId = chainId; this.chainId = chainId;
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(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( this._exchangeProxyConsumer = new ExchangeProxySwapQuoteConsumer(
supportedProvider, supportedProvider,
this._contractAddresses, this._contractAddresses,
@ -100,13 +94,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
} }
private async _getConsumerForSwapQuoteAsync(opts: Partial<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> { private async _getConsumerForSwapQuoteAsync(opts: Partial<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> {
// ( akroeger)leaving this switch to use different contracts in the future
switch (opts.useExtensionContract) { switch (opts.useExtensionContract) {
case ExtensionContractType.Forwarder:
return this._forwarderConsumer;
case ExtensionContractType.ExchangeProxy: case ExtensionContractType.ExchangeProxy:
return this._exchangeProxyConsumer; return this._exchangeProxyConsumer;
default: default:
return this._exchangeConsumer; return this._exchangeProxyConsumer;
} }
} }
} }

View File

@ -62,7 +62,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
gasSchedule: {}, gasSchedule: {},
exchangeProxyOverhead: () => ZERO_AMOUNT, exchangeProxyOverhead: () => ZERO_AMOUNT,
allowFallback: true, allowFallback: true,
shouldBatchBridgeOrders: true,
shouldGenerateQuoteReport: false, shouldGenerateQuoteReport: false,
}; };

View File

@ -370,7 +370,6 @@ export class MarketOperationUtils {
feeSchedule: _opts.feeSchedule, feeSchedule: _opts.feeSchedule,
exchangeProxyOverhead: _opts.exchangeProxyOverhead, exchangeProxyOverhead: _opts.exchangeProxyOverhead,
allowFallback: _opts.allowFallback, allowFallback: _opts.allowFallback,
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
}); });
// Compute Quote Report and return the results. // Compute Quote Report and return the results.
@ -408,7 +407,6 @@ export class MarketOperationUtils {
feeSchedule: _opts.feeSchedule, feeSchedule: _opts.feeSchedule,
exchangeProxyOverhead: _opts.exchangeProxyOverhead, exchangeProxyOverhead: _opts.exchangeProxyOverhead,
allowFallback: _opts.allowFallback, allowFallback: _opts.allowFallback,
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
}); });
let quoteReport: QuoteReport | undefined; let quoteReport: QuoteReport | undefined;
if (_opts.shouldGenerateQuoteReport) { if (_opts.shouldGenerateQuoteReport) {
@ -508,7 +506,6 @@ export class MarketOperationUtils {
excludedSources: _opts.excludedSources, excludedSources: _opts.excludedSources,
feeSchedule: _opts.feeSchedule, feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback, allowFallback: _opts.allowFallback,
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
}, },
); );
return optimizedOrders; return optimizedOrders;
@ -555,7 +552,6 @@ export class MarketOperationUtils {
orderDomain: this._orderDomain, orderDomain: this._orderDomain,
contractAddresses: this.contractAddresses, contractAddresses: this.contractAddresses,
bridgeSlippage: opts.bridgeSlippage || 0, bridgeSlippage: opts.bridgeSlippage || 0,
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
}; };
// Convert native orders and dex quotes into `Fill` objects. // Convert native orders and dex quotes into `Fill` objects.

View File

@ -150,7 +150,6 @@ export interface CreateOrderFromPathOpts {
orderDomain: OrderDomain; orderDomain: OrderDomain;
contractAddresses: ContractAddresses; contractAddresses: ContractAddresses;
bridgeSlippage: number; bridgeSlippage: number;
shouldBatchBridgeOrders: boolean;
} }
export function createOrdersFromTwoHopSample( export function createOrdersFromTwoHopSample(
@ -331,47 +330,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] { export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken; const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken; const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;

View File

@ -4,7 +4,6 @@ import { MarketOperation } from '../../types';
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants'; import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
import { import {
createBatchedBridgeOrder,
createBridgeOrder, createBridgeOrder,
createNativeOrder, createNativeOrder,
CreateOrderFromPathOpts, CreateOrderFromPathOpts,
@ -123,14 +122,9 @@ export class Path {
} }
contiguousBridgeFills.push(collapsedFills[j]); contiguousBridgeFills.push(collapsedFills[j]);
} }
// Always use DexForwarderBridge unless configured not to
if (!opts.shouldBatchBridgeOrders) {
this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts)); this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts));
i += 1; i += 1;
} else {
this.orders.push(createBatchedBridgeOrder(contiguousBridgeFills, opts));
i += contiguousBridgeFills.length;
}
} }
return this as CollapsedPath; return this as CollapsedPath;
} }

View File

@ -287,11 +287,6 @@ export interface GetMarketOrdersOpts {
*/ */
allowFallback: boolean; allowFallback: boolean;
rfqt?: GetMarketOrdersRfqtOpts; rfqt?: GetMarketOrdersRfqtOpts;
/**
* Whether to combine contiguous bridge orders into a single DexForwarderBridge
* order. Defaults to `true`.
*/
shouldBatchBridgeOrders: boolean;
/** /**
* Whether to generate a quote report * Whether to generate a quote report
*/ */

View File

@ -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<Partial<SignedOrderWithFillableAmounts>> = [
{
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<void>;
let expectMakerAndTakerBalancesForTakerAssetAsync: (
expectedMakerBalance: BigNumber,
expectedTakerBalance: BigNumber,
) => Promise<void>;
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),
);
});
});
});
});

View File

@ -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<Partial<SignedOrderWithFillableAmounts>> = [
{
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<void>;
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
});

View File

@ -468,7 +468,6 @@ describe('MarketOperationUtils tests', () => {
maxFallbackSlippage: 100, maxFallbackSlippage: 100,
excludedSources: DEFAULT_EXCLUDED, excludedSources: DEFAULT_EXCLUDED,
allowFallback: false, allowFallback: false,
shouldBatchBridgeOrders: false,
}; };
beforeEach(() => { beforeEach(() => {
@ -883,7 +882,6 @@ describe('MarketOperationUtils tests', () => {
excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor), excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor),
numSamples: 4, numSamples: 4,
bridgeSlippage: 0, bridgeSlippage: 0,
shouldBatchBridgeOrders: false,
}, },
); );
const result = ordersAndReport.optimizedOrders; const result = ordersAndReport.optimizedOrders;
@ -901,37 +899,6 @@ describe('MarketOperationUtils tests', () => {
expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress); 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 () => { it('factors in exchange proxy gas overhead', async () => {
// Uniswap has a slightly better rate than LiquidityProvider, // Uniswap has a slightly better rate than LiquidityProvider,
// but LiquidityProvider is better accounting for the EP gas overhead. // but LiquidityProvider is better accounting for the EP gas overhead.
@ -990,7 +957,6 @@ describe('MarketOperationUtils tests', () => {
maxFallbackSlippage: 100, maxFallbackSlippage: 100,
excludedSources: DEFAULT_EXCLUDED, excludedSources: DEFAULT_EXCLUDED,
allowFallback: false, allowFallback: false,
shouldBatchBridgeOrders: false,
}; };
beforeEach(() => { beforeEach(() => {
@ -1342,31 +1308,6 @@ describe('MarketOperationUtils tests', () => {
expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); 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 () => { it('factors in exchange proxy gas overhead', async () => {
// Uniswap has a slightly better rate than LiquidityProvider, // Uniswap has a slightly better rate than LiquidityProvider,
// but LiquidityProvider is better accounting for the EP gas overhead. // but LiquidityProvider is better accounting for the EP gas overhead.