refactored types and market sell operation

This commit is contained in:
David Sun 2019-07-03 14:29:28 -07:00
parent d0ea74e180
commit b4ac6d3439
9 changed files with 386 additions and 511 deletions

View File

@ -7,13 +7,10 @@ import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { import {
CalldataInfo, CalldataInfo,
ExchangeMarketBuySmartContractParams, ExchangeSmartContractParams,
ExchangeMarketSellSmartContractParams,
MarketBuySwapQuote,
MarketSellSwapQuote,
SmartContractParamsInfo, SmartContractParamsInfo,
SwapQuote, SwapQuote,
SwapQuoteConsumer, SwapQuoteConsumerBase,
SwapQuoteConsumerError, SwapQuoteConsumerError,
SwapQuoteConsumerOpts, SwapQuoteConsumerOpts,
SwapQuoteExecutionOpts, SwapQuoteExecutionOpts,
@ -23,8 +20,7 @@ import { assert } from '../utils/assert';
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
import { utils } from '../utils/utils'; import { utils } from '../utils/utils';
export class ExchangeSwapQuoteConsumer export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<ExchangeSmartContractParams> {
implements SwapQuoteConsumer<ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams> {
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
public readonly networkId: number; public readonly networkId: number;
@ -48,19 +44,18 @@ export class ExchangeSwapQuoteConsumer
): Promise<CalldataInfo> { ): Promise<CalldataInfo> {
assert.isValidSwapQuote('quote', quote); assert.isValidSwapQuote('quote', quote);
const consumableQuote = (quote as any) as (MarketBuySwapQuote | MarketSellSwapQuote); const { to, methodAbi, ethAmount, params } = await this.getSmartContractParamsOrThrowAsync(quote, opts);
const smartContractParamsInfo = await this.getSmartContractParamsOrThrowAsync(consumableQuote, opts);
const { to, methodAbi, ethAmount } = smartContractParamsInfo;
const abiEncoder = new AbiEncoder.Method(methodAbi); const abiEncoder = new AbiEncoder.Method(methodAbi);
const { orders, signatures } = params;
let args: any[]; let args: any[];
if (utils.isSwapQuoteMarketBuy(consumableQuote)) { if (params.type === 'marketBuy') {
const marketBuyParams = (smartContractParamsInfo.params as any) as ExchangeMarketBuySmartContractParams; const { makerAssetFillAmount } = params;
args = [marketBuyParams.orders, marketBuyParams.makerAssetFillAmount, marketBuyParams.signatures]; args = [orders, makerAssetFillAmount, signatures];
} else { } else {
const marketSellParams = (smartContractParamsInfo.params as any) as ExchangeMarketSellSmartContractParams; const { takerAssetFillAmount } = params;
args = [marketSellParams.orders, marketSellParams.takerAssetFillAmount, marketSellParams.signatures]; args = [orders, takerAssetFillAmount, signatures];
} }
const calldataHexString = abiEncoder.encode(args); const calldataHexString = abiEncoder.encode(args);
return { return {
@ -73,36 +68,36 @@ export class ExchangeSwapQuoteConsumer
public async getSmartContractParamsOrThrowAsync( public async getSmartContractParamsOrThrowAsync(
quote: SwapQuote, quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts>, _opts: Partial<SwapQuoteGetOutputOpts>,
): Promise<SmartContractParamsInfo<ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams>> { ): Promise<SmartContractParamsInfo<ExchangeSmartContractParams>> {
assert.isValidSwapQuote('quote', quote); assert.isValidSwapQuote('quote', quote);
const consumableQuote = (quote as any) as (MarketBuySwapQuote | MarketSellSwapQuote); const { orders } = quote;
const { orders } = consumableQuote;
const signatures = _.map(orders, o => o.signature); const signatures = _.map(orders, o => o.signature);
let params: ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams; let params: ExchangeSmartContractParams;
let methodName: string; let methodName: string;
if (utils.isSwapQuoteMarketBuy(consumableQuote)) { if (quote.type === 'marketBuy') {
const { makerAssetFillAmount } = consumableQuote; const { makerAssetFillAmount } = quote;
params = { params = {
orders, orders,
signatures, signatures,
makerAssetFillAmount, makerAssetFillAmount,
type: 'marketBuy',
}; };
methodName = 'marketBuyOrders'; methodName = 'marketBuyOrders';
} else { } else {
const { takerAssetFillAmount } = consumableQuote; const { takerAssetFillAmount } = quote;
params = { params = {
orders, orders,
signatures, signatures,
takerAssetFillAmount, takerAssetFillAmount,
type: 'marketSell',
}; };
methodName = 'marketSellOrders'; methodName = 'marketSellOrders';
@ -138,16 +133,14 @@ export class ExchangeSwapQuoteConsumer
assert.isBigNumber('gasPrice', gasPrice); assert.isBigNumber('gasPrice', gasPrice);
} }
const consumableQuote = (quote as any) as (MarketBuySwapQuote | MarketSellSwapQuote); const { orders } = quote;
const { orders } = consumableQuote;
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts); const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
try { try {
let txHash: string; let txHash: string;
if (utils.isSwapQuoteMarketBuy(consumableQuote)) { if (quote.type === 'marketBuy') {
const { makerAssetFillAmount } = consumableQuote; const { makerAssetFillAmount } = quote;
txHash = await this._contractWrappers.exchange.marketBuyOrdersNoThrowAsync( txHash = await this._contractWrappers.exchange.marketBuyOrdersNoThrowAsync(
orders, orders,
makerAssetFillAmount, makerAssetFillAmount,
@ -159,7 +152,7 @@ export class ExchangeSwapQuoteConsumer
}, },
); );
} else { } else {
const { takerAssetFillAmount } = consumableQuote; const { takerAssetFillAmount } = quote;
txHash = await this._contractWrappers.exchange.marketSellOrdersNoThrowAsync( txHash = await this._contractWrappers.exchange.marketSellOrdersNoThrowAsync(
orders, orders,
takerAssetFillAmount, takerAssetFillAmount,

View File

@ -7,15 +7,12 @@ import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { import {
CalldataInfo, CalldataInfo,
ForwarderMarketBuySmartContractParams, ForwarderSmartContractParams,
ForwarderMarketSellSmartContractParams,
ForwarderSwapQuoteExecutionOpts, ForwarderSwapQuoteExecutionOpts,
ForwarderSwapQuoteGetOutputOpts, ForwarderSwapQuoteGetOutputOpts,
MarketBuySwapQuote,
MarketSellSwapQuote,
SmartContractParamsInfo, SmartContractParamsInfo,
SwapQuote, SwapQuote,
SwapQuoteConsumer, SwapQuoteConsumerBase,
SwapQuoteConsumerError, SwapQuoteConsumerError,
SwapQuoteConsumerOpts, SwapQuoteConsumerOpts,
} from '../types'; } from '../types';
@ -25,8 +22,7 @@ import { assetDataUtils } from '../utils/asset_data_utils';
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils'; import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
import { utils } from '../utils/utils'; import { utils } from '../utils/utils';
export class ForwarderSwapQuoteConsumer export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<ForwarderSmartContractParams> {
implements SwapQuoteConsumer<ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams> {
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
public readonly networkId: number; public readonly networkId: number;
@ -55,34 +51,18 @@ export class ForwarderSwapQuoteConsumer
): Promise<CalldataInfo> { ): Promise<CalldataInfo> {
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
const consumableQuote = (quote as any) as (MarketBuySwapQuote | MarketSellSwapQuote); const { to, methodAbi, ethAmount, params } = await this.getSmartContractParamsOrThrowAsync(quote, opts);
const smartContractParamsInfo = await this.getSmartContractParamsOrThrowAsync(consumableQuote, opts);
const { to, methodAbi, ethAmount } = smartContractParamsInfo;
const abiEncoder = new AbiEncoder.Method(methodAbi); const abiEncoder = new AbiEncoder.Method(methodAbi);
const { orders, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient } = params;
let args: any[]; let args: any[];
if (utils.isSwapQuoteMarketBuy(consumableQuote)) { if (params.type === 'marketBuy') {
const marketBuyParams = (smartContractParamsInfo.params as any) as ForwarderMarketBuySmartContractParams; const { makerAssetFillAmount } = params;
args = [ args = [orders, makerAssetFillAmount, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient];
marketBuyParams.orders,
marketBuyParams.makerAssetFillAmount,
marketBuyParams.signatures,
marketBuyParams.feeOrders,
marketBuyParams.feeSignatures,
marketBuyParams.feePercentage,
marketBuyParams.feeRecipient,
];
} else { } else {
const marketSellParams = (smartContractParamsInfo.params as any) as ForwarderMarketSellSmartContractParams; args = [orders, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient];
args = [
marketSellParams.orders,
marketSellParams.signatures,
marketSellParams.feeOrders,
marketSellParams.feeSignatures,
marketSellParams.feePercentage,
marketSellParams.feeRecipient,
];
} }
const calldataHexString = abiEncoder.encode(args); const calldataHexString = abiEncoder.encode(args);
return { return {
@ -101,9 +81,7 @@ export class ForwarderSwapQuoteConsumer
public async getSmartContractParamsOrThrowAsync( public async getSmartContractParamsOrThrowAsync(
quote: SwapQuote, quote: SwapQuote,
opts: Partial<ForwarderSwapQuoteGetOutputOpts>, opts: Partial<ForwarderSwapQuoteGetOutputOpts>,
): Promise< ): Promise<SmartContractParamsInfo<ForwarderSmartContractParams>> {
SmartContractParamsInfo<ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams>
> {
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge( const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge(
@ -118,27 +96,20 @@ export class ForwarderSwapQuoteConsumer
assert.isBigNumber('ethAmount', ethAmount); assert.isBigNumber('ethAmount', ethAmount);
} }
const swapQuoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee( const quoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, unFormattedFeePercentage);
quote,
unFormattedFeePercentage,
);
const consumableQuoteWithAffiliateFee = (swapQuoteWithAffiliateFee as any) as ( const { orders, feeOrders, worstCaseQuoteInfo } = quoteWithAffiliateFee;
| MarketBuySwapQuote
| MarketSellSwapQuote);
const { orders, feeOrders, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee;
const signatures = _.map(orders, o => o.signature); const signatures = _.map(orders, o => o.signature);
const feeSignatures = _.map(feeOrders, o => o.signature); const feeSignatures = _.map(feeOrders, o => o.signature);
const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage); const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage);
let params: ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams; let params: ForwarderSmartContractParams;
let methodName: string; let methodName: string;
if (utils.isSwapQuoteMarketBuy(consumableQuoteWithAffiliateFee)) { if (quoteWithAffiliateFee.type === 'marketBuy') {
const { makerAssetFillAmount } = consumableQuoteWithAffiliateFee; const { makerAssetFillAmount } = quoteWithAffiliateFee;
params = { params = {
orders, orders,
@ -148,20 +119,19 @@ export class ForwarderSwapQuoteConsumer
feeSignatures, feeSignatures,
feePercentage, feePercentage,
feeRecipient, feeRecipient,
type: 'marketBuy',
}; };
methodName = 'marketBuyOrdersWithEth'; methodName = 'marketBuyOrdersWithEth';
} else { } else {
const { takerAssetFillAmount } = consumableQuoteWithAffiliateFee;
params = { params = {
orders, orders,
takerAssetFillAmount,
signatures, signatures,
feeOrders, feeOrders,
feeSignatures, feeSignatures,
feePercentage, feePercentage,
feeRecipient, feeRecipient,
type: 'marketSell',
}; };
methodName = 'marketSellOrdersWithEth'; methodName = 'marketSellOrdersWithEth';
} }
@ -210,20 +180,16 @@ export class ForwarderSwapQuoteConsumer
assert.isBigNumber('gasPrice', gasPrice); assert.isBigNumber('gasPrice', gasPrice);
} }
const swapQuoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, feePercentage); const quoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, feePercentage);
const consumableQuoteWithAffiliateFee = (swapQuoteWithAffiliateFee as any) as ( const { orders, feeOrders, worstCaseQuoteInfo } = quoteWithAffiliateFee;
| MarketBuySwapQuote
| MarketSellSwapQuote);
const { orders, feeOrders, worstCaseQuoteInfo } = consumableQuoteWithAffiliateFee;
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts); const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
try { try {
let txHash: string; let txHash: string;
if (utils.isSwapQuoteMarketBuy(consumableQuoteWithAffiliateFee)) { if (quoteWithAffiliateFee.type === 'marketBuy') {
const { makerAssetFillAmount } = consumableQuoteWithAffiliateFee; const { makerAssetFillAmount } = quoteWithAffiliateFee;
txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
orders, orders,
makerAssetFillAmount, makerAssetFillAmount,

View File

@ -66,46 +66,48 @@ export interface SmartContractParamsInfo<T> {
methodAbi: MethodAbi; methodAbi: MethodAbi;
} }
export interface SmartContractParamsBase {
orders: SignedOrder[];
signatures: string[];
}
export type MarketOperation = 'marketBuy' | 'marketSell';
/** /**
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage. * orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
* makerAssetFillAmount: The amount of makerAsset to swap for. * makerAssetFillAmount: The amount of makerAsset to swap for.
* signatures: An array of signatures that attest that the maker of the orders in fact made the orders. * signatures: An array of signatures that attest that the maker of the orders in fact made the orders.
*/ */
export interface ExchangeMarketBuySmartContractParams { export interface ExchangeMarketBuySmartContractParams extends SmartContractParamsBase {
orders: SignedOrder[];
makerAssetFillAmount: BigNumber; makerAssetFillAmount: BigNumber;
signatures: string[]; type: 'marketBuy';
} }
export interface ExchangeMarketSellSmartContractParams { export interface ExchangeMarketSellSmartContractParams extends SmartContractParamsBase {
orders: SignedOrder[];
takerAssetFillAmount: BigNumber; takerAssetFillAmount: BigNumber;
signatures: string[]; type: 'marketSell';
} }
/** export type ExchangeSmartContractParams = ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams;
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
* makerAssetFillAmount: The amount of makerAsset to swap for. export interface ForwarderSmartContractParamsBase {
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above.
* signatures: An array of signatures that attest that the maker of the orders in fact made the orders.
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above.
* feeSignatures: An array of signatures that attest that the maker of the fee orders in fact made the orders.
* feePercentage: percentage (up to 5%) of the taker asset paid to feeRecipient
* feeRecipient: address of the receiver of the feePercentage of taker asset
*/
export interface ForwarderMarketBuySmartContractParams extends ExchangeMarketBuySmartContractParams {
feeOrders: SignedOrder[]; feeOrders: SignedOrder[];
feeSignatures: string[]; feeSignatures: string[];
feePercentage: BigNumber; feePercentage: BigNumber;
feeRecipient: string; feeRecipient: string;
} }
export interface ForwarderMarketSellSmartContractParams extends ExchangeMarketSellSmartContractParams { export interface ForwarderMarketBuySmartContractParams
feeOrders: SignedOrder[]; extends ExchangeMarketBuySmartContractParams,
feeSignatures: string[]; ForwarderSmartContractParamsBase {}
feePercentage: BigNumber;
feeRecipient: string; export interface ForwarderMarketSellSmartContractParams
} extends Omit<ExchangeMarketSellSmartContractParams, 'takerAssetFillAmount'>,
ForwarderSmartContractParamsBase {}
export type ForwarderSmartContractParams =
| ForwarderMarketBuySmartContractParams
| ForwarderMarketSellSmartContractParams;
/** /**
* Interface that varying SwapQuoteConsumers adhere to (exchange consumer, router consumer, forwarder consumer, coordinator consumer) * Interface that varying SwapQuoteConsumers adhere to (exchange consumer, router consumer, forwarder consumer, coordinator consumer)
@ -113,7 +115,7 @@ export interface ForwarderMarketSellSmartContractParams extends ExchangeMarketSe
* getSmartContractParamsOrThrow: Get SmartContractParamsInfo to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided. * getSmartContractParamsOrThrow: Get SmartContractParamsInfo to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
* executeSwapQuoteOrThrowAsync: Executes a web3 transaction to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided. * executeSwapQuoteOrThrowAsync: Executes a web3 transaction to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
*/ */
export interface SwapQuoteConsumer<T> { export interface SwapQuoteConsumerBase<T> {
getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteGetOutputOpts>): Promise<CalldataInfo>; getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteGetOutputOpts>): Promise<CalldataInfo>;
getSmartContractParamsOrThrowAsync( getSmartContractParamsOrThrowAsync(
quote: SwapQuote, quote: SwapQuote,
@ -161,6 +163,8 @@ export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOpts
*/ */
export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOpts {} export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOpts {}
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
/** /**
* takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). * takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). * makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
@ -170,7 +174,7 @@ export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOu
* bestCaseQuoteInfo: Info about the best case price for the asset. * bestCaseQuoteInfo: Info about the best case price for the asset.
* worstCaseQuoteInfo: Info about the worst case price for the asset. * worstCaseQuoteInfo: Info about the worst case price for the asset.
*/ */
export interface SwapQuote { export interface SwapQuoteBase {
takerAssetData: string; takerAssetData: string;
makerAssetData: string; makerAssetData: string;
orders: SignedOrder[]; orders: SignedOrder[];
@ -179,22 +183,25 @@ export interface SwapQuote {
worstCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo;
} }
export interface MarketSellSwapQuote extends SwapQuote { export interface MarketSellSwapQuote extends SwapQuoteBase {
takerAssetFillAmount: BigNumber; takerAssetFillAmount: BigNumber;
bestCaseQuoteInfo: SwapQuoteInfo; type: 'marketSell';
worstCaseQuoteInfo: SwapQuoteInfo;
} }
export interface MarketBuySwapQuote extends SwapQuote { export interface MarketBuySwapQuote extends SwapQuoteBase {
makerAssetFillAmount: BigNumber; makerAssetFillAmount: BigNumber;
bestCaseQuoteInfo: SwapQuoteInfo; type: 'marketBuy';
worstCaseQuoteInfo: SwapQuoteInfo;
} }
export interface SwapQuoteWithAffiliateFee extends SwapQuote { export interface SwapQuoteWithAffiliateFeeBase {
feePercentage: number; feePercentage: number;
} }
export interface MarketSellSwapQuoteWithAffiliateFee extends SwapQuoteWithAffiliateFeeBase, MarketSellSwapQuote {}
export interface MarketBuySwapQuoteWithAffiliateFee extends SwapQuoteWithAffiliateFeeBase, MarketBuySwapQuote {}
export type SwapQuoteWithAffiliateFee = MarketBuySwapQuoteWithAffiliateFee | MarketSellSwapQuoteWithAffiliateFee;
/** /**
* assetEthAmount: The amount of eth required to pay for the requested asset. * assetEthAmount: The amount of eth required to pay for the requested asset.
* feeEthAmount: The amount of eth required to pay any fee concerned with completing the swap. * feeEthAmount: The amount of eth required to pay any fee concerned with completing the swap.

View File

@ -3,8 +3,7 @@ import { schemas } from '@0x/json-schemas';
import { SignedOrder } from '@0x/types'; import { SignedOrder } from '@0x/types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { OrderProvider, OrderProviderRequest, SwapQuote, SwapQuoteConsumerError, SwapQuoteInfo } from '../types'; import { OrderProvider, OrderProviderRequest, SwapQuote, SwapQuoteInfo } from '../types';
import { utils } from '../utils/utils';
export const assert = { export const assert = {
...sharedAssert, ...sharedAssert,
@ -15,12 +14,10 @@ export const assert = {
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, swapQuote.feeOrders, schemas.signedOrdersSchema); sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, swapQuote.feeOrders, schemas.signedOrdersSchema);
assert.isValidSwapQuoteInfo(`${variableName}.bestCaseQuoteInfo`, swapQuote.bestCaseQuoteInfo); assert.isValidSwapQuoteInfo(`${variableName}.bestCaseQuoteInfo`, swapQuote.bestCaseQuoteInfo);
assert.isValidSwapQuoteInfo(`${variableName}.worstCaseQuoteInfo`, swapQuote.worstCaseQuoteInfo); assert.isValidSwapQuoteInfo(`${variableName}.worstCaseQuoteInfo`, swapQuote.worstCaseQuoteInfo);
if (utils.isSwapQuoteMarketBuy(swapQuote)) { if (swapQuote.type === 'marketBuy') {
sharedAssert.isBigNumber(`${variableName}.makerAssetFillAmount`, swapQuote.makerAssetFillAmount); sharedAssert.isBigNumber(`${variableName}.makerAssetFillAmount`, swapQuote.makerAssetFillAmount);
} else if (utils.isSwapQuoteMarketSell(swapQuote)) {
sharedAssert.isBigNumber(`${variableName}.takerAssetFillAmount`, swapQuote.takerAssetFillAmount);
} else { } else {
throw new Error(SwapQuoteConsumerError.InvalidMarketSellOrMarketBuySwapQuote); sharedAssert.isBigNumber(`${variableName}.takerAssetFillAmount`, swapQuote.takerAssetFillAmount);
} }
}, },
isValidForwarderSwapQuote(variableName: string, swapQuote: SwapQuote, wethAssetData: string): void { isValidForwarderSwapQuote(variableName: string, swapQuote: SwapQuote, wethAssetData: string): void {

View File

@ -6,8 +6,10 @@ import { constants } from '../constants';
import { InsufficientAssetLiquidityError } from '../errors'; import { InsufficientAssetLiquidityError } from '../errors';
import { import {
MarketBuySwapQuote, MarketBuySwapQuote,
MarketOperation,
MarketSellSwapQuote, MarketSellSwapQuote,
OrdersAndFillableAmounts, OrdersAndFillableAmounts,
SwapQuote,
SwapQuoteInfo, SwapQuoteInfo,
SwapQuoterError, SwapQuoterError,
} from '../types'; } from '../types';
@ -21,6 +23,41 @@ export const swapQuoteCalculator = {
slippagePercentage: number, slippagePercentage: number,
isMakerAssetZrxToken: boolean, isMakerAssetZrxToken: boolean,
): MarketSellSwapQuote { ): MarketSellSwapQuote {
return calculateSwapQuote(
ordersAndFillableAmounts,
feeOrdersAndFillableAmounts,
takerAssetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
'marketSell',
) as MarketSellSwapQuote;
},
calculateMarketBuySwapQuote(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
makerAssetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
): MarketBuySwapQuote {
return calculateSwapQuote(
ordersAndFillableAmounts,
feeOrdersAndFillableAmounts,
makerAssetFillAmount,
slippagePercentage,
isMakerAssetZrxToken,
'marketBuy',
) as MarketBuySwapQuote;
},
};
function calculateSwapQuote(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
assetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
marketOperation: MarketOperation,
): SwapQuote {
const orders = ordersAndFillableAmounts.orders; const orders = ordersAndFillableAmounts.orders;
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts; const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const remainingFillableTakerAssetAmounts = remainingFillableMakerAssetAmounts.map( const remainingFillableTakerAssetAmounts = remainingFillableMakerAssetAmounts.map(
@ -30,26 +67,47 @@ export const swapQuoteCalculator = {
); );
const feeOrders = feeOrdersAndFillableAmounts.orders; const feeOrders = feeOrdersAndFillableAmounts.orders;
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts; const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const slippageBufferAmount = takerAssetFillAmount.multipliedBy(slippagePercentage).integerValue();
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
let resultOrders: SignedOrder[];
let remainingFillAmount: BigNumber;
let ordersRemainingFillableMakerAssetAmounts: BigNumber[];
if (marketOperation === 'marketBuy') {
// find the orders that cover the desired assetBuyAmount (with slippage) // find the orders that cover the desired assetBuyAmount (with slippage)
const { ({
resultOrders,
remainingFillAmount,
ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetFillAmount, {
remainingFillableMakerAssetAmounts,
slippageBufferAmount,
}));
} else {
let ordersRemainingFillableTakerAssetAmounts: BigNumber[];
// find the orders that cover the desired assetBuyAmount (with slippage)
({
resultOrders, resultOrders,
remainingFillAmount, remainingFillAmount,
ordersRemainingFillableTakerAssetAmounts, ordersRemainingFillableTakerAssetAmounts,
} = marketUtils.findOrdersThatCoverTakerAssetFillAmount(orders, takerAssetFillAmount, { } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(orders, assetFillAmount, {
remainingFillableTakerAssetAmounts, remainingFillableTakerAssetAmounts,
slippageBufferAmount, slippageBufferAmount,
}); }));
const ordersRemainingFillableMakerAssetAmounts = _.map(
ordersRemainingFillableMakerAssetAmounts = _.map(
ordersRemainingFillableTakerAssetAmounts, ordersRemainingFillableTakerAssetAmounts,
(takerAssetAmount: BigNumber, index: number) => { (takerAssetAmount: BigNumber, index: number) => {
return orderCalculationUtils.getMakerFillAmount(resultOrders[index], takerAssetAmount); return orderCalculationUtils.getMakerFillAmount(resultOrders[index], takerAssetAmount);
}, },
); );
}
// if we do not have enough orders to cover the desired assetBuyAmount, throw // if we do not have enough orders to cover the desired assetBuyAmount, throw
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
// We needed the amount they requested to buy, plus the amount for slippage // We needed the amount they requested to buy, plus the amount for slippage
const totalAmountRequested = takerAssetFillAmount.plus(slippageBufferAmount); const totalAmountRequested = assetFillAmount.plus(slippageBufferAmount);
const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount); const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount);
// multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by // multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by
// in order to get the total amount needed considering slippage // in order to get the total amount needed considering slippage
@ -101,151 +159,81 @@ export const swapQuoteCalculator = {
orders: resultFeeOrders, orders: resultFeeOrders,
remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts, remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts,
}; };
const bestCaseQuoteInfo = calculateMarketSellQuoteInfo(
const bestCaseQuoteInfo = calculateQuoteInfo(
trimmedOrdersAndFillableAmounts, trimmedOrdersAndFillableAmounts,
trimmedFeeOrdersAndFillableAmounts, trimmedFeeOrdersAndFillableAmounts,
takerAssetFillAmount, assetFillAmount,
isMakerAssetZrxToken, isMakerAssetZrxToken,
marketOperation,
); );
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = calculateMarketSellQuoteInfo( const worstCaseQuoteInfo = calculateQuoteInfo(
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts), reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
takerAssetFillAmount, assetFillAmount,
isMakerAssetZrxToken, isMakerAssetZrxToken,
marketOperation,
); );
return { const quoteBase = {
takerAssetData, takerAssetData,
makerAssetData, makerAssetData,
takerAssetFillAmount,
orders: resultOrders, orders: resultOrders,
feeOrders: resultFeeOrders, feeOrders: resultFeeOrders,
bestCaseQuoteInfo, bestCaseQuoteInfo,
worstCaseQuoteInfo, worstCaseQuoteInfo,
}; };
},
calculateMarketBuySwapQuote(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
makerAssetFillAmount: BigNumber,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
): MarketBuySwapQuote {
const orders = ordersAndFillableAmounts.orders;
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const feeOrders = feeOrdersAndFillableAmounts.orders;
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const slippageBufferAmount = makerAssetFillAmount.multipliedBy(slippagePercentage).integerValue();
// find the orders that cover the desired assetBuyAmount (with slippage)
const {
resultOrders,
remainingFillAmount,
ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, makerAssetFillAmount, {
remainingFillableMakerAssetAmounts,
slippageBufferAmount,
});
// if we do not have enough orders to cover the desired assetBuyAmount, throw
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
// We needed the amount they requested to buy, plus the amount for slippage
const totalAmountRequested = makerAssetFillAmount.plus(slippageBufferAmount);
const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount);
// multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by
// in order to get the total amount needed considering slippage
// i.e. if slippagePercent was 0.2 (20%), multiplierNeededWithSlippage would be 1.2
const multiplierNeededWithSlippage = new BigNumber(1).plus(slippagePercentage);
// Given amountAvailableToFillConsideringSlippage * multiplierNeededWithSlippage = amountAbleToFill
// We divide amountUnableToFill by multiplierNeededWithSlippage to determine amountAvailableToFillConsideringSlippage
const amountAvailableToFillConsideringSlippage = amountAbleToFill
.div(multiplierNeededWithSlippage)
.integerValue(BigNumber.ROUND_FLOOR);
throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage);
}
// if we are not buying ZRX:
// given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage)
// TODO(bmillman): optimization
// update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to
// 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) {
const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
resultOrders,
feeOrders,
{
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
remainingFillableFeeAmounts,
},
);
// if we do not have enough feeOrders to cover the fees, throw
if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
throw new Error(SwapQuoterError.InsufficientZrxLiquidity);
}
resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders;
feeOrdersRemainingFillableMakerAssetAmounts =
feeOrdersAndRemainingFeeAmount.feeOrdersRemainingFillableMakerAssetAmounts;
}
// assetData information for the result
const takerAssetData = orders[0].takerAssetData;
const makerAssetData = orders[0].makerAssetData;
// compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount
const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
orders: resultOrders,
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
};
const trimmedFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
orders: resultFeeOrders,
remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts,
};
const bestCaseQuoteInfo = calculateMarketBuyQuoteInfo(
trimmedOrdersAndFillableAmounts,
trimmedFeeOrdersAndFillableAmounts,
makerAssetFillAmount,
isMakerAssetZrxToken,
);
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = calculateMarketBuyQuoteInfo(
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
makerAssetFillAmount,
isMakerAssetZrxToken,
);
if (marketOperation === 'marketBuy') {
return { return {
takerAssetData, ...quoteBase,
makerAssetData, type: 'marketBuy',
makerAssetFillAmount, makerAssetFillAmount: assetFillAmount,
orders: resultOrders,
feeOrders: resultFeeOrders,
bestCaseQuoteInfo,
worstCaseQuoteInfo,
}; };
}, } else {
return {
...quoteBase,
type: 'marketSell',
takerAssetFillAmount: assetFillAmount,
}; };
}
}
function calculateMarketBuyQuoteInfo( function calculateQuoteInfo(
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
makerTokenAmount: BigNumber, tokenAmount: BigNumber,
isMakerAssetZrxToken: boolean, isMakerAssetZrxToken: boolean,
marketOperation: MarketOperation,
): SwapQuoteInfo { ): SwapQuoteInfo {
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
let takerTokenAmount = constants.ZERO_AMOUNT; let makerTokenAmount = marketOperation === 'marketBuy' ? tokenAmount : constants.ZERO_AMOUNT;
let takerTokenAmount = marketOperation === 'marketSell' ? tokenAmount : constants.ZERO_AMOUNT;
let zrxTakerTokenAmount = constants.ZERO_AMOUNT; let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
if (isMakerAssetZrxToken) { if (isMakerAssetZrxToken) {
if (marketOperation === 'marketBuy') {
takerTokenAmount = findTakerTokenAmountNeededToBuyZrx(ordersAndFillableAmounts, makerTokenAmount); takerTokenAmount = findTakerTokenAmountNeededToBuyZrx(ordersAndFillableAmounts, makerTokenAmount);
} else { } else {
// find eth and zrx amounts needed to buy makerTokenAmount = findZrxTokenAmountFromSellingTakerTokenAmount(
const takerTokenAndZrxAmountToBuyAsset = findTakerTokenAndZrxAmountNeededToBuyAsset(
ordersAndFillableAmounts, ordersAndFillableAmounts,
makerTokenAmount, takerTokenAmount,
); );
takerTokenAmount = takerTokenAndZrxAmountToBuyAsset[0]; }
const zrxAmountToBuyAsset = takerTokenAndZrxAmountToBuyAsset[1]; } else {
const findTokenAndZrxAmount =
marketOperation === 'marketBuy'
? findTakerTokenAndZrxAmountNeededToBuyAsset
: findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset;
// find eth and zrx amounts needed to buy
const tokenAndZrxAmountToBuyAsset = findTokenAndZrxAmount(ordersAndFillableAmounts, makerTokenAmount);
if (marketOperation === 'marketBuy') {
takerTokenAmount = tokenAndZrxAmountToBuyAsset[0];
} else {
makerTokenAmount = tokenAndZrxAmountToBuyAsset[0];
}
const zrxAmountToBuyAsset = tokenAndZrxAmountToBuyAsset[1];
// find eth amount needed to buy zrx // find eth amount needed to buy zrx
zrxTakerTokenAmount = findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); zrxTakerTokenAmount = findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
} }
@ -261,42 +249,6 @@ function calculateMarketBuyQuoteInfo(
totalTakerTokenAmount, totalTakerTokenAmount,
}; };
} }
function calculateMarketSellQuoteInfo(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
takerTokenAmount: BigNumber,
isMakerAssetZrxToken: boolean,
): SwapQuoteInfo {
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
let makerTokenAmount = constants.ZERO_AMOUNT;
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
if (isMakerAssetZrxToken) {
makerTokenAmount = findZrxTokenAmountFromSellingTakerTokenAmount(ordersAndFillableAmounts, takerTokenAmount);
} else {
// find eth and zrx amounts needed to buy
const takerTokenAndZrxAmountToBuyAsset = findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset(
ordersAndFillableAmounts,
takerTokenAmount,
);
makerTokenAmount = takerTokenAndZrxAmountToBuyAsset[0];
const zrxAmountToSellAsset = takerTokenAndZrxAmountToBuyAsset[1];
// find eth amount needed to buy zrx
zrxTakerTokenAmount = findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToSellAsset);
}
const feeTakerTokenAmount = zrxTakerTokenAmount;
// eth amount needed in total is the sum of the amount needed for the asset and the amount needed for fees
const totalTakerTokenAmount = takerTokenAmount.plus(feeTakerTokenAmount);
return {
makerTokenAmount,
takerTokenAmount,
feeTakerTokenAmount,
totalTakerTokenAmount,
};
}
// given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties // given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties
function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts { function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts {
const ordersCopy = _.clone(ordersAndFillableAmounts.orders); const ordersCopy = _.clone(ordersAndFillableAmounts.orders);

View File

@ -4,7 +4,6 @@ import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { MarketBuySwapQuote, MarketSellSwapQuote, SwapQuote } from '../types';
// tslint:disable:no-unnecessary-type-assertion // tslint:disable:no-unnecessary-type-assertion
export const utils = { export const utils = {
@ -26,10 +25,4 @@ export const utils = {
}, },
) as MethodAbi | undefined; ) as MethodAbi | undefined;
}, },
isSwapQuoteMarketBuy(quote: SwapQuote): quote is MarketBuySwapQuote {
return (quote as MarketSellSwapQuote).takerAssetFillAmount !== undefined;
},
isSwapQuoteMarketSell(quote: SwapQuote): quote is MarketSellSwapQuote {
return (quote as MarketBuySwapQuote).makerAssetFillAmount !== undefined;
},
}; };

View File

@ -3,7 +3,7 @@ import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { MarketBuySwapQuote, MarketSellSwapQuote } from '../../src'; import { MarketOperation, SwapQuote } from '../../src/types';
const ZERO_BIG_NUMBER = new BigNumber(0); const ZERO_BIG_NUMBER = new BigNumber(0);
@ -25,11 +25,12 @@ export const getSignedOrdersWithNoFees = (
); );
}; };
export const getFullyFillableMarketBuySwapQuoteWithNoFees = ( export const getFullyFillableSwapQuoteWithNoFees = (
makerAssetData: string, makerAssetData: string,
takerAssetData: string, takerAssetData: string,
orders: SignedOrder[], orders: SignedOrder[],
): MarketBuySwapQuote => { operation: MarketOperation,
): SwapQuote => {
const makerAssetFillAmount = _.reduce( const makerAssetFillAmount = _.reduce(
orders, orders,
(a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount), (a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount),
@ -47,46 +48,26 @@ export const getFullyFillableMarketBuySwapQuoteWithNoFees = (
totalTakerTokenAmount, totalTakerTokenAmount,
}; };
return { const quoteBase = {
makerAssetData, makerAssetData,
takerAssetData, takerAssetData,
orders, orders,
feeOrders: [], feeOrders: [],
bestCaseQuoteInfo: quoteInfo,
worstCaseQuoteInfo: quoteInfo,
};
if (operation === 'marketBuy') {
return {
...quoteBase,
type: 'marketBuy',
makerAssetFillAmount, makerAssetFillAmount,
bestCaseQuoteInfo: quoteInfo,
worstCaseQuoteInfo: quoteInfo,
}; };
}; } else {
export const getFullyFillableMarketSellSwapQuoteWithNoFees = (
makerAssetData: string,
takerAssetData: string,
orders: SignedOrder[],
): MarketSellSwapQuote => {
const makerAssetFillAmount = _.reduce(
orders,
(a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount),
ZERO_BIG_NUMBER,
);
const totalTakerTokenAmount = _.reduce(
orders,
(a: BigNumber, c: SignedOrder) => a.plus(c.takerAssetAmount),
ZERO_BIG_NUMBER,
);
const quoteInfo = {
makerTokenAmount: makerAssetFillAmount,
takerTokenAmount: totalTakerTokenAmount,
feeTakerTokenAmount: ZERO_BIG_NUMBER,
totalTakerTokenAmount,
};
return { return {
makerAssetData, ...quoteBase,
takerAssetData, type: 'marketSell',
orders,
feeOrders: [],
takerAssetFillAmount: totalTakerTokenAmount, takerAssetFillAmount: totalTakerTokenAmount,
bestCaseQuoteInfo: quoteInfo,
worstCaseQuoteInfo: quoteInfo,
}; };
}
}; };

View File

@ -10,6 +10,7 @@ import {
FindFeeOrdersThatCoverFeesForTargetOrdersOpts, FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
FindOrdersThatCoverMakerAssetFillAmountOpts, FindOrdersThatCoverMakerAssetFillAmountOpts,
FindOrdersThatCoverTakerAssetFillAmountOpts, FindOrdersThatCoverTakerAssetFillAmountOpts,
MarketOperation,
OrdersAndRemainingMakerFillAmount, OrdersAndRemainingMakerFillAmount,
OrdersAndRemainingTakerFillAmount, OrdersAndRemainingTakerFillAmount,
} from './types'; } from './types';
@ -20,60 +21,12 @@ export const marketUtils = {
takerAssetFillAmount: BigNumber, takerAssetFillAmount: BigNumber,
opts?: FindOrdersThatCoverTakerAssetFillAmountOpts, opts?: FindOrdersThatCoverTakerAssetFillAmountOpts,
): OrdersAndRemainingTakerFillAmount<T> { ): OrdersAndRemainingTakerFillAmount<T> {
assert.doesConformToSchema('orders', orders, schemas.ordersSchema); return findOrdersThatCoverAssetFillAmount(
assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
// try to get remainingFillableTakerAssetAmounts from opts, if it's not there, use takerAssetAmount values from orders
const remainingFillableTakerAssetAmounts = _.get(
opts,
'remainingFillableTakerAssetAmounts',
_.map(orders, order => order.takerAssetAmount),
) as BigNumber[];
_.forEach(remainingFillableTakerAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableTakerAssetAmount[${index}]`, amount),
);
assert.assert(
orders.length === remainingFillableTakerAssetAmounts.length,
'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length',
);
// try to get slippageBufferAmount from opts, if it's not there, default to 0
const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber;
assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount);
// calculate total amount of makerAsset needed to be filled
const totalFillAmount = takerAssetFillAmount.plus(slippageBufferAmount);
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
orders, orders,
({ resultOrders, remainingFillAmount, ordersRemainingFillableTakerAssetAmounts }, order, index) => { takerAssetFillAmount,
if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) { 'marketSell',
return { opts,
resultOrders, ) as OrdersAndRemainingTakerFillAmount<T>;
remainingFillAmount: constants.ZERO_AMOUNT,
ordersRemainingFillableTakerAssetAmounts,
};
} else {
const takerAssetAmountAvailable = remainingFillableTakerAssetAmounts[index];
const shouldIncludeOrder = takerAssetAmountAvailable.gt(constants.ZERO_AMOUNT);
// if there is no makerAssetAmountAvailable do not append order to resultOrders
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return {
resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
ordersRemainingFillableTakerAssetAmounts: shouldIncludeOrder
? _.concat(ordersRemainingFillableTakerAssetAmounts, takerAssetAmountAvailable)
: ordersRemainingFillableTakerAssetAmounts,
remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingFillAmount.minus(takerAssetAmountAvailable),
),
};
}
},
{
resultOrders: [] as T[],
remainingFillAmount: totalFillAmount,
ordersRemainingFillableTakerAssetAmounts: [] as BigNumber[],
},
);
return result;
}, },
/** /**
* Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount
@ -90,60 +43,12 @@ export const marketUtils = {
makerAssetFillAmount: BigNumber, makerAssetFillAmount: BigNumber,
opts?: FindOrdersThatCoverMakerAssetFillAmountOpts, opts?: FindOrdersThatCoverMakerAssetFillAmountOpts,
): OrdersAndRemainingMakerFillAmount<T> { ): OrdersAndRemainingMakerFillAmount<T> {
assert.doesConformToSchema('orders', orders, schemas.ordersSchema); return findOrdersThatCoverAssetFillAmount(
assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount);
// try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders
const remainingFillableMakerAssetAmounts = _.get(
opts,
'remainingFillableMakerAssetAmounts',
_.map(orders, order => order.makerAssetAmount),
) as BigNumber[];
_.forEach(remainingFillableMakerAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount),
);
assert.assert(
orders.length === remainingFillableMakerAssetAmounts.length,
'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length',
);
// try to get slippageBufferAmount from opts, if it's not there, default to 0
const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber;
assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount);
// calculate total amount of makerAsset needed to be filled
const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount);
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
orders, orders,
({ resultOrders, remainingFillAmount, ordersRemainingFillableMakerAssetAmounts }, order, index) => { makerAssetFillAmount,
if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) { 'marketBuy',
return { opts,
resultOrders, ) as OrdersAndRemainingMakerFillAmount<T>;
remainingFillAmount: constants.ZERO_AMOUNT,
ordersRemainingFillableMakerAssetAmounts,
};
} else {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
const shouldIncludeOrder = makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT);
// if there is no makerAssetAmountAvailable do not append order to resultOrders
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return {
resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
ordersRemainingFillableMakerAssetAmounts: shouldIncludeOrder
? _.concat(ordersRemainingFillableMakerAssetAmounts, makerAssetAmountAvailable)
: ordersRemainingFillableMakerAssetAmounts,
remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingFillAmount.minus(makerAssetAmountAvailable),
),
};
}
},
{
resultOrders: [] as T[],
remainingFillAmount: totalFillAmount,
ordersRemainingFillableMakerAssetAmounts: [] as BigNumber[],
},
);
return result;
}, },
/** /**
* Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX
@ -222,3 +127,82 @@ export const marketUtils = {
// https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx // https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx
}, },
}; };
function findOrdersThatCoverAssetFillAmount<T extends Order>(
orders: T[],
assetFillAmount: BigNumber,
operation: MarketOperation,
opts?: FindOrdersThatCoverTakerAssetFillAmountOpts,
): OrdersAndRemainingTakerFillAmount<T> | OrdersAndRemainingMakerFillAmount<T> {
const variablePrefix = operation === 'marketBuy' ? 'maker' : 'taker';
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
assert.isValidBaseUnitAmount(`${variablePrefix}AssetFillAmount}`, assetFillAmount);
// try to get remainingFillableTakerAssetAmounts from opts, if it's not there, use takerAssetAmount values from orders
const remainingFillableAssetAmounts = _.get(
opts,
`remainingFillable${variablePrefix}AssetAmounts`,
_.map(orders, order => (operation === 'marketBuy' ? order.makerAssetAmount : order.takerAssetAmount)),
) as BigNumber[];
_.forEach(remainingFillableAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillable${variablePrefix}AssetAmount[${index}]`, amount),
);
assert.assert(
orders.length === remainingFillableAssetAmounts.length,
`Expected orders.length to equal opts.remainingFillable${variablePrefix}AssetAmounts.length`,
);
// try to get slippageBufferAmount from opts, if it's not there, default to 0
const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber;
assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount);
// calculate total amount of asset needed to be filled
const totalFillAmount = assetFillAmount.plus(slippageBufferAmount);
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
orders,
({ resultOrders, remainingFillAmount, ordersRemainingFillableAssetAmounts }, order, index) => {
if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
return {
resultOrders,
remainingFillAmount: constants.ZERO_AMOUNT,
ordersRemainingFillableAssetAmounts,
};
} else {
const assetAmountAvailable = remainingFillableAssetAmounts[index];
const shouldIncludeOrder = assetAmountAvailable.gt(constants.ZERO_AMOUNT);
// if there is no assetAmountAvailable do not append order to resultOrders
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return {
resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
ordersRemainingFillableAssetAmounts: shouldIncludeOrder
? _.concat(ordersRemainingFillableAssetAmounts, assetAmountAvailable)
: ordersRemainingFillableAssetAmounts,
remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingFillAmount.minus(assetAmountAvailable),
),
};
}
},
{
resultOrders: [] as T[],
remainingFillAmount: totalFillAmount,
ordersRemainingFillableAssetAmounts: [] as BigNumber[],
},
);
const {
ordersRemainingFillableAssetAmounts: resultOrdersRemainingFillableAssetAmounts,
...ordersAndRemainingFillAmount
} = result;
if (operation === 'marketBuy') {
return {
...ordersAndRemainingFillAmount,
ordersRemainingFillableMakerAssetAmounts: resultOrdersRemainingFillableAssetAmounts,
};
} else {
return {
...ordersAndRemainingFillAmount,
ordersRemainingFillableMakerAssetAmounts: resultOrdersRemainingFillableAssetAmounts,
};
}
}

View File

@ -49,6 +49,8 @@ export interface FindOrdersThatCoverTakerAssetFillAmountOpts {
slippageBufferAmount?: BigNumber; slippageBufferAmount?: BigNumber;
} }
export type MarketOperation = 'marketSell' | 'marketBuy';
/** /**
* remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter. * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
* You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values. * You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values.