removed v0-specific code in asset-swapper
This commit is contained in:
parent
32218ce25e
commit
c8886febb9
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> {
|
||||
// ( 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
gasSchedule: {},
|
||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||
allowFallback: true,
|
||||
shouldBatchBridgeOrders: true,
|
||||
shouldGenerateQuoteReport: false,
|
||||
};
|
||||
|
||||
|
@ -370,7 +370,6 @@ export class MarketOperationUtils {
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
|
||||
allowFallback: _opts.allowFallback,
|
||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||
});
|
||||
|
||||
// Compute Quote Report and return the results.
|
||||
@ -408,7 +407,6 @@ export class MarketOperationUtils {
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
|
||||
allowFallback: _opts.allowFallback,
|
||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||
});
|
||||
let quoteReport: QuoteReport | undefined;
|
||||
if (_opts.shouldGenerateQuoteReport) {
|
||||
@ -508,7 +506,6 @@ export class MarketOperationUtils {
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
|
||||
},
|
||||
);
|
||||
return optimizedOrders;
|
||||
@ -555,7 +552,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.
|
||||
|
@ -150,7 +150,6 @@ export interface CreateOrderFromPathOpts {
|
||||
orderDomain: OrderDomain;
|
||||
contractAddresses: ContractAddresses;
|
||||
bridgeSlippage: number;
|
||||
shouldBatchBridgeOrders: boolean;
|
||||
}
|
||||
|
||||
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] {
|
||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
||||
|
@ -4,7 +4,6 @@ import { MarketOperation } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
createBatchedBridgeOrder,
|
||||
createBridgeOrder,
|
||||
createNativeOrder,
|
||||
CreateOrderFromPathOpts,
|
||||
@ -123,14 +122,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;
|
||||
}
|
||||
|
@ -287,11 +287,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
|
||||
*/
|
||||
|
@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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
|
||||
});
|
@ -468,7 +468,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: DEFAULT_EXCLUDED,
|
||||
allowFallback: false,
|
||||
shouldBatchBridgeOrders: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -883,7 +882,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor),
|
||||
numSamples: 4,
|
||||
bridgeSlippage: 0,
|
||||
shouldBatchBridgeOrders: false,
|
||||
},
|
||||
);
|
||||
const result = ordersAndReport.optimizedOrders;
|
||||
@ -901,37 +899,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.
|
||||
@ -990,7 +957,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: DEFAULT_EXCLUDED,
|
||||
allowFallback: false,
|
||||
shouldBatchBridgeOrders: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -1342,31 +1308,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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user