added options, features for asset-swapper

This commit is contained in:
David Sun
2019-07-17 11:56:27 -07:00
parent 9dbc9a8ad9
commit bf0d90d079
9 changed files with 106 additions and 22 deletions

View File

@@ -8,6 +8,7 @@ import {
OrdersAndFillableAmounts,
SwapQuoteRequestOpts,
SwapQuoterOpts,
SwapQuoteUtilsOpts,
} from './types';
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
@@ -21,6 +22,10 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
expiryBufferMs: 120000, // 2 minutes
};
const DEFAULT_SWAP_QUOTE_UTILS_OPTS: SwapQuoteUtilsOpts = {
networkId: MAINNET_NETWORK_ID,
};
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: ForwarderSwapQuoteGetOutputOpts = {
feePercentage: 0,
feeRecipient: NULL_ADDRESS,
@@ -30,6 +35,7 @@ const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: ForwarderSwapQuoteExecutionOpts
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
shouldForceOrderRefresh: false,
shouldDisableRequestingFeeOrders: false,
slippagePercentage: 0.2, // 20% slippage protection,
};
@@ -56,4 +62,5 @@ export const constants = {
EMPTY_ORDERS_AND_FILLABLE_AMOUNTS,
DEFAULT_PER_PAGE,
DEFAULT_LIQUIDITY_REQUEST_OPTS,
DEFAULT_SWAP_QUOTE_UTILS_OPTS,
};

View File

@@ -23,6 +23,7 @@ export {
export { SignedOrder } from '@0x/types';
export { BigNumber } from '@0x/utils';
export { SwapQuoteUtils } from './utils/swap_quote_utils';
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
export { SwapQuoter } from './swap_quoter';
export { InsufficientAssetLiquidityError } from './errors';
@@ -33,6 +34,7 @@ export { StandardRelayerAPIOrderProvider } from './order_providers/standard_rela
export {
SwapQuoterError,
SwapQuoterOpts,
SwapQuoteUtilsOpts,
SwapQuote,
SwapQuoteConsumerOpts,
CalldataInfo,

View File

@@ -1,4 +1,5 @@
import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers';
import { calldataOptimizationUtils } from '@0x/contract-wrappers/src/utils/calldata_optimization_utils';
import { MarketOperation } from '@0x/types';
import { AbiEncoder, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
@@ -75,6 +76,8 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
const { orders } = quote;
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
const signatures = _.map(orders, o => o.signature);
let params: ExchangeSmartContractParams;
@@ -84,7 +87,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
const { makerAssetFillAmount } = quote;
params = {
orders,
orders: optimizedOrders,
signatures,
makerAssetFillAmount,
type: MarketOperation.Buy,
@@ -95,7 +98,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
const { takerAssetFillAmount } = quote;
params = {
orders,
orders: optimizedOrders,
signatures,
takerAssetFillAmount,
type: MarketOperation.Sell,

View File

@@ -1,10 +1,12 @@
import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers';
import { calldataOptimizationUtils } from '@0x/contract-wrappers/src/utils/calldata_optimization_utils';
import { MarketOperation } from '@0x/types';
import { AbiEncoder, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import { MethodAbi } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from '../constants';
import {
CalldataInfo,
@@ -101,6 +103,12 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
const { orders, feeOrders, worstCaseQuoteInfo } = quoteWithAffiliateFee;
// lowercase input addresses
const normalizedFeeRecipientAddress = feeRecipient.toLowerCase();
// optimize orders
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(feeOrders);
const signatures = _.map(orders, o => o.signature);
const feeSignatures = _.map(feeOrders, o => o.signature);
@@ -113,25 +121,25 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
const { makerAssetFillAmount } = quoteWithAffiliateFee;
params = {
orders,
orders: optimizedOrders,
makerAssetFillAmount,
signatures,
feeOrders,
feeOrders: optimizedFeeOrders,
feeSignatures,
feePercentage,
feeRecipient,
feeRecipient: normalizedFeeRecipientAddress,
type: MarketOperation.Buy,
};
methodName = 'marketBuyOrdersWithEth';
} else {
params = {
orders,
orders: optimizedOrders,
signatures,
feeOrders,
feeOrders: optimizedFeeOrders,
feeSignatures,
feePercentage,
feeRecipient,
feeRecipient: normalizedFeeRecipientAddress,
type: MarketOperation.Sell,
};
methodName = 'marketSellOrdersWithEth';

View File

@@ -6,6 +6,7 @@ import * as _ from 'lodash';
import { constants } from '../constants';
import {
CalldataInfo,
ConsumerType,
SmartContractParams,
SmartContractParamsInfo,
SwapQuote,
@@ -74,13 +75,19 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts>,
): Promise<SwapQuoteConsumerBase<SmartContractParams>> {
return swapQuoteConsumerUtils.getConsumerForSwapQuoteAsync(
quote,
this._contractWrappers,
this.provider,
this._exchangeConsumer,
this._forwarderConsumer,
opts,
);
}
if (opts.useConsumerType === ConsumerType.Exchange) {
return this._exchangeConsumer;
} else if (opts.useConsumerType === ConsumerType.Forwarder) {
return this._forwarderConsumer;
} else {
return swapQuoteConsumerUtils.getConsumerForSwapQuoteAsync(
quote,
this._contractWrappers,
this.provider,
this._exchangeConsumer,
this._forwarderConsumer,
opts,
);
}
}
}

View File

@@ -391,7 +391,7 @@ export class SwapQuoter {
marketOperation: MarketOperation,
options: Partial<SwapQuoteRequestOpts>,
): Promise<SwapQuote> {
const { shouldForceOrderRefresh, slippagePercentage } = _.merge(
const { shouldForceOrderRefresh, slippagePercentage, shouldDisableRequestingFeeOrders } = _.merge(
{},
constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
options,
@@ -406,7 +406,7 @@ export class SwapQuoter {
// if the requested assetData is ZRX, don't get the fee info
const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([
this.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData, shouldForceOrderRefresh),
isMakerAssetZrxToken
shouldDisableRequestingFeeOrders || isMakerAssetZrxToken
? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS)
: this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, takerAssetData, shouldForceOrderRefresh),
shouldForceOrderRefresh,
@@ -429,6 +429,7 @@ export class SwapQuoter {
assetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
shouldDisableRequestingFeeOrders,
);
} else {
swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
@@ -437,6 +438,7 @@ export class SwapQuoter {
assetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
shouldDisableRequestingFeeOrders,
);
}

View File

@@ -94,6 +94,10 @@ export interface ExchangeMarketSellSmartContractParams extends SmartContractPara
type: MarketOperation.Sell;
}
export enum ConsumerType {
Forwarder, Exchange,
}
/**
* Represents all the parameters to interface with 0x exchange contracts' marketSell and marketBuy functions.
*/
@@ -157,6 +161,10 @@ export interface SwapQuoteConsumerOpts {
networkId: number;
}
export interface SwapQuoteUtilsOpts {
networkId: number;
}
/**
* Represents the options provided to a generic SwapQuoteConsumer
*/
@@ -198,6 +206,7 @@ export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
*/
export interface SwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts {
takerAddress?: string;
useConsumerType?: ConsumerType;
}
/**
@@ -262,6 +271,7 @@ export interface SwapQuoteInfo {
*/
export interface SwapQuoteRequestOpts {
shouldForceOrderRefresh: boolean;
shouldDisableRequestingFeeOrders: boolean;
slippagePercentage: number;
}

View File

@@ -22,6 +22,7 @@ export const swapQuoteCalculator = {
takerAssetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
shouldDisableFeeOrderCalculations: boolean,
): MarketSellSwapQuote {
return calculateSwapQuote(
ordersAndFillableAmounts,
@@ -29,7 +30,9 @@ export const swapQuoteCalculator = {
takerAssetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
shouldDisableFeeOrderCalculations,
MarketOperation.Sell,
) as MarketSellSwapQuote;
},
calculateMarketBuySwapQuote(
@@ -38,6 +41,7 @@ export const swapQuoteCalculator = {
makerAssetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
shouldDisableFeeOrderCalculations: boolean,
): MarketBuySwapQuote {
return calculateSwapQuote(
ordersAndFillableAmounts,
@@ -45,6 +49,7 @@ export const swapQuoteCalculator = {
makerAssetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
shouldDisableFeeOrderCalculations,
MarketOperation.Buy,
) as MarketBuySwapQuote;
},
@@ -56,6 +61,7 @@ function calculateSwapQuote(
assetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
shouldDisableFeeOrderCalculations: boolean,
marketOperation: MarketOperation,
): SwapQuote {
const orders = ordersAndFillableAmounts.orders;
@@ -128,7 +134,7 @@ function calculateSwapQuote(
// finding order that cover all fees, this will help with estimating ETH and minimizing gas usage
let resultFeeOrders = [] as SignedOrder[];
let feeOrdersRemainingFillableMakerAssetAmounts = [] as BigNumber[];
if (!isMakerAssetZrxToken) {
if (!shouldDisableFeeOrderCalculations && !isMakerAssetZrxToken) {
const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
resultOrders,
feeOrders,
@@ -165,6 +171,7 @@ function calculateSwapQuote(
trimmedFeeOrdersAndFillableAmounts,
assetFillAmount,
isMakerAssetZrxToken,
shouldDisableFeeOrderCalculations,
marketOperation,
);
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
@@ -173,6 +180,7 @@ function calculateSwapQuote(
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
assetFillAmount,
isMakerAssetZrxToken,
shouldDisableFeeOrderCalculations,
marketOperation,
);
@@ -205,6 +213,7 @@ function calculateQuoteInfo(
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
tokenAmount: BigNumber,
isMakerAssetZrxToken: boolean,
shouldDisableFeeOrderCalculations: boolean,
marketOperation: MarketOperation,
): SwapQuoteInfo {
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
@@ -212,7 +221,7 @@ function calculateQuoteInfo(
let takerTokenAmount = marketOperation === MarketOperation.Sell ? tokenAmount : constants.ZERO_AMOUNT;
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
if (isMakerAssetZrxToken) {
if (!shouldDisableFeeOrderCalculations && isMakerAssetZrxToken) {
if (marketOperation === MarketOperation.Buy) {
takerTokenAmount = findTakerTokenAmountNeededToBuyZrx(ordersAndFillableAmounts, makerTokenAmount);
} else {
@@ -221,7 +230,7 @@ function calculateQuoteInfo(
takerTokenAmount,
);
}
} else {
} else if (!shouldDisableFeeOrderCalculations) {
const findTokenAndZrxAmount =
marketOperation === MarketOperation.Buy
? findTakerTokenAndZrxAmountNeededToBuyAsset

View File

@@ -0,0 +1,36 @@
import { ContractWrappers, SupportedProvider, ZeroExProvider } from '@0x/contract-wrappers';
import { providerUtils } from '@0x/utils';
import * as _ from 'lodash';
import { constants } from '../constants';
import { SwapQuote, SwapQuoteUtilsOpts } from '../types';
import { assert } from '../utils/assert';
export class SwapQuoteUtils {
public readonly provider: ZeroExProvider;
public readonly networkId: number;
private readonly _contractWrappers: ContractWrappers;
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteUtilsOpts> = {}) {
const { networkId } = _.merge({}, constants.DEFAULT_SWAP_QUOTE_UTILS_OPTS, options);
assert.isNumber('networkId', networkId);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
this.provider = provider;
this.networkId = networkId;
this._contractWrappers = new ContractWrappers(this.provider, {
networkId,
});
}
public async isTakerAddressAllowanceEnoughForBestAndWorstQuoteInfoAsync(swapQuote: SwapQuote, takerAddress: string): Promise<[boolean, boolean]> {
const orderValidatorWrapper = this._contractWrappers.orderValidator;
const balanceAndAllowance = await orderValidatorWrapper.getBalanceAndAllowanceAsync(takerAddress, swapQuote.takerAssetData);
return [
balanceAndAllowance.allowance.isGreaterThanOrEqualTo(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount),
balanceAndAllowance.allowance.isGreaterThanOrEqualTo(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount),
];
}
}