aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils')
-rw-r--r--packages/order-utils/CHANGELOG.json18
-rw-r--r--packages/order-utils/package.json4
-rw-r--r--packages/order-utils/src/index.ts2
-rw-r--r--packages/order-utils/src/market_utils.ts129
-rw-r--r--packages/order-utils/src/order_state_utils.ts95
-rw-r--r--packages/order-utils/src/rate_utils.ts48
-rw-r--r--packages/order-utils/src/sorting_utils.ts54
-rw-r--r--packages/order-utils/src/types.ts28
-rw-r--r--packages/order-utils/test/market_utils_test.ts50
-rw-r--r--packages/order-utils/test/order_state_utils_test.ts124
-rw-r--r--packages/order-utils/test/rate_utils_test.ts55
-rw-r--r--packages/order-utils/test/sorting_utils_test.ts67
12 files changed, 536 insertions, 138 deletions
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json
index a81eb077d..86f0da65a 100644
--- a/packages/order-utils/CHANGELOG.json
+++ b/packages/order-utils/CHANGELOG.json
@@ -1,5 +1,23 @@
[
{
+ "version": "1.0.1-rc.4",
+ "changes": [
+ {
+ "note": "Remove rounding error being thrown when maker amount is very small",
+ "pr": 959
+ },
+ {
+ "note": "Added rateUtils and sortingUtils",
+ "pr": 953
+ },
+ {
+ "note":
+ "Update marketUtils api such that all optional parameters are bundled into one optional param and more defaults are provided",
+ "pr": 954
+ }
+ ]
+ },
+ {
"version": "1.0.1-rc.3",
"changes": [
{
diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json
index bcfb0611f..576f4dbc1 100644
--- a/packages/order-utils/package.json
+++ b/packages/order-utils/package.json
@@ -51,7 +51,7 @@
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^2.0.1",
- "copyfiles": "^1.2.0",
+ "copyfiles": "^2.0.0",
"dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^4.1.0",
@@ -60,7 +60,7 @@
"sinon": "^4.0.0",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
- "typescript": "2.9.2"
+ "typescript": "3.0.1"
},
"dependencies": {
"@0xproject/assert": "^1.0.5",
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index 638126af6..1f393b0c4 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -4,6 +4,8 @@ export { generatePseudoRandomSalt } from './salt';
export { assetDataUtils } from './asset_data_utils';
export { eip712Utils } from './eip712_utils';
export { marketUtils } from './market_utils';
+export { rateUtils } from './rate_utils';
+export { sortingUtils } from './sorting_utils';
export { OrderStateUtils } from './order_state_utils';
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts
index 681059ddf..a0a827546 100644
--- a/packages/order-utils/src/market_utils.ts
+++ b/packages/order-utils/src/market_utils.ts
@@ -1,46 +1,51 @@
import { schemas } from '@0xproject/json-schemas';
-import { SignedOrder } from '@0xproject/types';
+import { Order } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { assert } from './assert';
import { constants } from './constants';
+import { FindFeeOrdersThatCoverFeesForTargetOrdersOpts, FindOrdersThatCoverMakerAssetFillAmountOpts } from './types';
export const marketUtils = {
/**
- * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances,
- * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last.
+ * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount
+ * in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last order.
* Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH.
- * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset.
- * All orders should specify WETH as the takerAsset.
- * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter.
- * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
- * for these values.
- * @param makerAssetFillAmount The amount of makerAsset desired to be filled.
- * @param slippageBufferAmount An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills.
+ * @param orders An array of objects that extend the Order interface. All orders should specify the same makerAsset.
+ * All orders should specify WETH as the takerAsset.
+ * @param makerAssetFillAmount The amount of makerAsset desired to be filled.
+ * @param opts Optional arguments this function accepts.
* @return Resulting orders and remaining fill amount that could not be covered by the input.
*/
- findOrdersThatCoverMakerAssetFillAmount(
- signedOrders: SignedOrder[],
- remainingFillableMakerAssetAmounts: BigNumber[],
+ findOrdersThatCoverMakerAssetFillAmount<T extends Order>(
+ orders: T[],
makerAssetFillAmount: BigNumber,
- slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT,
- ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } {
- assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ opts?: FindOrdersThatCoverMakerAssetFillAmountOpts,
+ ): { resultOrders: T[]; remainingFillAmount: BigNumber } {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ 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.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount);
- assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount);
assert.assert(
- signedOrders.length === remainingFillableMakerAssetAmounts.length,
- 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length',
+ 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 signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount
+ // iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
- signedOrders,
+ orders,
({ resultOrders, remainingFillAmount }, order, index) => {
if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) {
return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT };
@@ -59,55 +64,61 @@ export const marketUtils = {
};
}
},
- { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount },
+ { resultOrders: [] as T[], remainingFillAmount: totalFillAmount },
);
return result;
},
/**
- * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account
- * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a
- * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of
+ * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX
+ * in order to fill the takerFees required by orders plus a slippageBufferAmount.
+ * Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of
* feeOrders that will cost the least ETH.
- * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as
- * the makerAsset and WETH as the takerAsset.
- * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter.
- * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
- * for these values.
- * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as
- * the makerAsset and WETH as the takerAsset.
- * @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter.
- * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
- * for these values.
- * @param slippageBufferAmount An additional amount of fee to be covered by the result in case of trade collisions or partial fills.
+ * @param orders An array of objects that extend the Order interface. All orders should specify ZRX as
+ * the makerAsset and WETH as the takerAsset.
+ * @param feeOrders An array of objects that extend the Order interface. All orders should specify ZRX as
+ * the makerAsset and WETH as the takerAsset.
+ * @param opts Optional arguments this function accepts.
* @return Resulting orders and remaining fee amount that could not be covered by the input.
*/
- findFeeOrdersThatCoverFeesForTargetOrders(
- signedOrders: SignedOrder[],
- remainingFillableMakerAssetAmounts: BigNumber[],
- signedFeeOrders: SignedOrder[],
- remainingFillableFeeAmounts: BigNumber[],
- slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT,
- ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } {
- assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ findFeeOrdersThatCoverFeesForTargetOrders<T extends Order>(
+ orders: T[],
+ feeOrders: T[],
+ opts?: FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
+ ): { resultOrders: T[]; remainingFeeAmount: BigNumber } {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema);
+ // 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.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
+ assert.assert(
+ orders.length === remainingFillableMakerAssetAmounts.length,
+ 'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length',
+ );
+ // try to get remainingFillableFeeAmounts from opts, if it's not there, use makerAssetAmount values from feeOrders
+ const remainingFillableFeeAmounts = _.get(
+ opts,
+ 'remainingFillableFeeAmounts',
+ _.map(feeOrders, order => order.makerAssetAmount),
+ ) as BigNumber[];
_.forEach(remainingFillableFeeAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount),
);
- assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount);
assert.assert(
- signedOrders.length === remainingFillableMakerAssetAmounts.length,
- 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length',
+ feeOrders.length === remainingFillableFeeAmounts.length,
+ 'Expected feeOrders.length to equal opts.remainingFillableFeeAmounts.length',
);
- assert.assert(
- signedOrders.length === remainingFillableMakerAssetAmounts.length,
- 'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length',
- );
- // calculate total amount of ZRX needed to fill signedOrders
+ // 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 ZRX needed to fill orders
const totalFeeAmount = _.reduce(
- signedOrders,
+ orders,
(accFees, order, index) => {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable
@@ -118,10 +129,12 @@ export const marketUtils = {
constants.ZERO_AMOUNT,
);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
- signedFeeOrders,
- remainingFillableFeeAmounts,
+ feeOrders,
totalFeeAmount,
- slippageBufferAmount,
+ {
+ remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts,
+ slippageBufferAmount,
+ },
);
return {
resultOrders,
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index cb08c5ae2..a0e24acf0 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -11,6 +11,7 @@ import { BigNumber } from '@0xproject/utils';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
import { orderHashUtils } from './order_hash';
+import { OrderValidationUtils } from './order_validation_utils';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
import { utils } from './utils';
@@ -22,9 +23,16 @@ interface SidedOrderRelevantState {
traderFeeProxyAllowance: BigNumber;
filledTakerAssetAmount: BigNumber;
remainingFillableAssetAmount: BigNumber;
+ isOrderCancelled: boolean;
}
-
-const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
+interface OrderValidResult {
+ isValid: true;
+}
+interface OrderInvalidResult {
+ isValid: false;
+ error: ExchangeContractErrs;
+}
+type OrderValidationResult = OrderValidResult | OrderInvalidResult;
export class OrderStateUtils {
private readonly _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
@@ -32,64 +40,56 @@ export class OrderStateUtils {
private static _validateIfOrderIsValid(
signedOrder: SignedOrder,
sidedOrderRelevantState: SidedOrderRelevantState,
- ): void {
+ ): OrderValidationResult {
const isMakerSide = sidedOrderRelevantState.isMakerSide;
+ if (sidedOrderRelevantState.isOrderCancelled) {
+ return { isValid: false, error: ExchangeContractErrs.OrderCancelled };
+ }
const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(
sidedOrderRelevantState.filledTakerAssetAmount,
);
if (availableTakerAssetAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ return { isValid: false, error: ExchangeContractErrs.OrderRemainingFillAmountZero };
}
if (sidedOrderRelevantState.traderBalance.eq(0)) {
- throw new Error(
- isMakerSide
- ? ExchangeContractErrs.InsufficientMakerBalance
- : ExchangeContractErrs.InsufficientTakerBalance,
- );
+ const error = isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerBalance
+ : ExchangeContractErrs.InsufficientTakerBalance;
+ return { isValid: false, error };
}
if (sidedOrderRelevantState.traderProxyAllowance.eq(0)) {
- throw new Error(
- isMakerSide
- ? ExchangeContractErrs.InsufficientMakerAllowance
- : ExchangeContractErrs.InsufficientTakerAllowance,
- );
+ const error = isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerAllowance
+ : ExchangeContractErrs.InsufficientTakerAllowance;
+ return { isValid: false, error };
}
if (!signedOrder.makerFee.eq(0)) {
if (sidedOrderRelevantState.traderFeeBalance.eq(0)) {
- throw new Error(
- isMakerSide
- ? ExchangeContractErrs.InsufficientMakerFeeBalance
- : ExchangeContractErrs.InsufficientTakerFeeBalance,
- );
+ const error = isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeBalance
+ : ExchangeContractErrs.InsufficientTakerFeeBalance;
+ return { isValid: false, error };
}
if (sidedOrderRelevantState.traderFeeProxyAllowance.eq(0)) {
- throw new Error(
- isMakerSide
- ? ExchangeContractErrs.InsufficientMakerFeeAllowance
- : ExchangeContractErrs.InsufficientTakerFeeAllowance,
- );
+ const error = isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeAllowance
+ : ExchangeContractErrs.InsufficientTakerFeeAllowance;
+ return { isValid: false, error };
}
}
-
- let minFillableTakerAssetAmountWithinNoRoundingErrorRange;
- if (isMakerSide) {
- minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
- .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
- .dividedBy(signedOrder.makerAssetAmount);
- } else {
- minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.makerAssetAmount
- .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
- .dividedBy(signedOrder.takerAssetAmount);
- }
-
- if (
- sidedOrderRelevantState.remainingFillableAssetAmount.lessThan(
- minFillableTakerAssetAmountWithinNoRoundingErrorRange,
- )
- ) {
- throw new Error(ExchangeContractErrs.OrderFillRoundingError);
+ const remainingTakerAssetAmount = signedOrder.takerAssetAmount.minus(
+ sidedOrderRelevantState.filledTakerAssetAmount,
+ );
+ const isRoundingError = OrderValidationUtils.isRoundingError(
+ remainingTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ if (isRoundingError) {
+ return { isValid: false, error: ExchangeContractErrs.OrderFillRoundingError };
}
+ return { isValid: true };
}
/**
* Instantiate OrderStateUtils
@@ -117,6 +117,7 @@ export class OrderStateUtils {
public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
const sidedOrderRelevantState = {
isMakerSide: true,
traderBalance: orderRelevantState.makerBalance,
@@ -125,20 +126,21 @@ export class OrderStateUtils {
traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
remainingFillableAssetAmount: orderRelevantState.remainingFillableMakerAssetAmount,
+ isOrderCancelled,
};
- try {
- OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState);
+ const orderValidationResult = OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState);
+ if (orderValidationResult.isValid) {
const orderState: OrderStateValid = {
isValid: true,
orderHash,
orderRelevantState,
};
return orderState;
- } catch (err) {
+ } else {
const orderState: OrderStateInvalid = {
isValid: false,
orderHash,
- error: err.message,
+ error: orderValidationResult.error,
};
return orderState;
}
@@ -279,6 +281,7 @@ export class OrderStateUtils {
traderFeeProxyAllowance,
filledTakerAssetAmount,
remainingFillableAssetAmount,
+ isOrderCancelled,
};
return sidedOrderRelevantState;
}
diff --git a/packages/order-utils/src/rate_utils.ts b/packages/order-utils/src/rate_utils.ts
new file mode 100644
index 000000000..c9ca72c59
--- /dev/null
+++ b/packages/order-utils/src/rate_utils.ts
@@ -0,0 +1,48 @@
+import { schemas } from '@0xproject/json-schemas';
+import { Order } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+import { assert } from './assert';
+import { constants } from './constants';
+
+export const rateUtils = {
+ /**
+ * Takes an order and calculates the fee adjusted rate (takerAsset/makerAsset) by calculating how much takerAsset
+ * is required to cover the fees (feeRate * takerFee), adding the takerAssetAmount and dividing by makerAssetAmount
+ * @param order An object that conforms to the order interface
+ * @param feeRate The market rate of ZRX denominated in takerAssetAmount
+ * (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX)
+ * Defaults to 0
+ * @return The rate (takerAsset/makerAsset) of the order adjusted for fees
+ */
+ getFeeAdjustedRateOfOrder(order: Order, feeRate: BigNumber = constants.ZERO_AMOUNT): BigNumber {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ assert.isBigNumber('feeRate', feeRate);
+ assert.assert(
+ feeRate.gte(constants.ZERO_AMOUNT),
+ `Expected feeRate: ${feeRate} to be greater than or equal to 0`,
+ );
+ const takerAssetAmountNeededToPayForFees = order.takerFee.mul(feeRate);
+ const totalTakerAssetAmount = takerAssetAmountNeededToPayForFees.plus(order.takerAssetAmount);
+ const rate = totalTakerAssetAmount.div(order.makerAssetAmount);
+ return rate;
+ },
+ /**
+ * Takes a fee order (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH) and calculates
+ * the fee adjusted rate (WETH/ZRX) by dividing the takerAssetAmount by the makerAmount minus the takerFee
+ * @param feeOrder An object that conforms to the order interface
+ * @return The rate (WETH/ZRX) of the fee order adjusted for fees
+ */
+ getFeeAdjustedRateOfFeeOrder(feeOrder: Order): BigNumber {
+ assert.doesConformToSchema('feeOrder', feeOrder, schemas.orderSchema);
+ const zrxAmountAfterFees = feeOrder.makerAssetAmount.sub(feeOrder.takerFee);
+ assert.assert(
+ zrxAmountAfterFees.greaterThan(constants.ZERO_AMOUNT),
+ `Expected takerFee: ${JSON.stringify(feeOrder.takerFee)} to be less than makerAssetAmount: ${JSON.stringify(
+ feeOrder.makerAssetAmount,
+ )}`,
+ );
+ const rate = feeOrder.takerAssetAmount.div(zrxAmountAfterFees);
+ return rate;
+ },
+};
diff --git a/packages/order-utils/src/sorting_utils.ts b/packages/order-utils/src/sorting_utils.ts
new file mode 100644
index 000000000..8811bcaf8
--- /dev/null
+++ b/packages/order-utils/src/sorting_utils.ts
@@ -0,0 +1,54 @@
+import { schemas } from '@0xproject/json-schemas';
+import { Order } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { assert } from './assert';
+import { constants } from './constants';
+import { rateUtils } from './rate_utils';
+
+export const sortingUtils = {
+ /**
+ * Takes an array of orders and sorts them by takerAsset/makerAsset rate in ascending order (best rate first).
+ * Adjusts the rate of each order according to the feeRate and takerFee for that order.
+ * @param orders An array of objects that extend the Order interface. All orders should specify ZRX as
+ * the makerAsset and WETH as the takerAsset.
+ * @param feeRate The market rate of ZRX denominated in takerAssetAmount
+ * (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX)
+ * Defaults to 0
+ * @return The input orders sorted by rate in ascending order
+ */
+ sortOrdersByFeeAdjustedRate<T extends Order>(orders: T[], feeRate: BigNumber = constants.ZERO_AMOUNT): T[] {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ assert.isBigNumber('feeRate', feeRate);
+ const rateCalculator = (order: Order) => rateUtils.getFeeAdjustedRateOfOrder(order, feeRate);
+ const sortedOrders = sortOrders(orders, rateCalculator);
+ return sortedOrders;
+ },
+ /**
+ * Takes an array of fee orders (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH)
+ * and sorts them by rate in ascending order (best rate first). Adjusts the rate according to the takerFee.
+ * @param feeOrders An array of objects that extend the Order interface. All orders should specify ZRX as
+ * the makerAsset and WETH as the takerAsset.
+ * @return The input orders sorted by rate in ascending order
+ */
+ sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] {
+ assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema);
+ const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils);
+ const sortedOrders = sortOrders(feeOrders, rateCalculator);
+ return sortedOrders;
+ },
+};
+
+type RateCalculator = (order: Order) => BigNumber;
+
+// takes an array of orders, copies them, and sorts the copy based on the rate definition provided by rateCalculator
+function sortOrders<T extends Order>(orders: T[], rateCalculator: RateCalculator): T[] {
+ const copiedOrders = _.cloneDeep(orders);
+ copiedOrders.sort((firstOrder, secondOrder) => {
+ const firstOrderRate = rateCalculator(firstOrder);
+ const secondOrderRate = rateCalculator(secondOrder);
+ return firstOrderRate.comparedTo(secondOrderRate);
+ });
+ return copiedOrders;
+}
diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts
index 1fbd8cf7b..2e9c79d80 100644
--- a/packages/order-utils/src/types.ts
+++ b/packages/order-utils/src/types.ts
@@ -41,3 +41,31 @@ export interface CreateOrderOpts {
salt?: BigNumber;
expirationTimeSeconds?: BigNumber;
}
+
+/**
+ * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
+ * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values.
+ * Defaults to `makerAssetAmount` values from the orders param.
+ * slippageBufferAmount: An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills.
+ * Defaults to 0
+ */
+export interface FindOrdersThatCoverMakerAssetFillAmountOpts {
+ remainingFillableMakerAssetAmounts?: BigNumber[];
+ slippageBufferAmount?: BigNumber;
+}
+
+/**
+ * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
+ * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values.
+ * Defaults to `makerAssetAmount` values from the orders param.
+ * remainingFillableFeeAmounts: An array of BigNumbers corresponding to the feeOrders parameter.
+ * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups for these values.
+ * Defaults to `makerAssetAmount` values from the feeOrders param.
+ * slippageBufferAmount: An additional amount of fee to be covered by the result in case of trade collisions or partial fills.
+ * Defaults to 0
+ */
+export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts {
+ remainingFillableMakerAssetAmounts?: BigNumber[];
+ remainingFillableFeeAmounts?: BigNumber[];
+ slippageBufferAmount?: BigNumber;
+}
diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts
index 5d94cc55c..cce97e0e4 100644
--- a/packages/order-utils/test/market_utils_test.ts
+++ b/packages/order-utils/test/market_utils_test.ts
@@ -19,7 +19,6 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
[],
- [],
fillAmount,
);
expect(resultOrders).to.be.empty;
@@ -35,8 +34,6 @@ describe('marketUtils', () => {
},
3,
);
- // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
- const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
// try to fill 20 units of makerAsset
// include 10 units of slippageBufferAmount
@@ -44,9 +41,10 @@ describe('marketUtils', () => {
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
- slippageBufferAmount,
+ {
+ slippageBufferAmount,
+ },
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -58,9 +56,10 @@ describe('marketUtils', () => {
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
- slippageBufferAmount,
+ {
+ slippageBufferAmount,
+ },
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -72,9 +71,10 @@ describe('marketUtils', () => {
const slippageBufferAmount = new BigNumber(5);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
- slippageBufferAmount,
+ {
+ slippageBufferAmount,
+ },
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5));
@@ -84,7 +84,6 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
@@ -95,7 +94,6 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(15);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
@@ -121,8 +119,10 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(30);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
+ {
+ remainingFillableMakerAssetAmounts,
+ },
);
expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15));
@@ -138,15 +138,11 @@ describe('marketUtils', () => {
},
3,
);
- // generate remainingFillableFeeAmounts that equal the zrxAmount
- const remainingFillableFeeAmounts = [zrxAmount, zrxAmount, zrxAmount];
describe('no target orders', () => {
it('returns empty and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
[],
- [],
inputFeeOrders,
- remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -169,9 +165,10 @@ describe('marketUtils', () => {
it('returns empty and non-zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
- remainingFillableMakerAssetAmounts,
- [],
[],
+ {
+ remainingFillableMakerAssetAmounts,
+ },
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
@@ -186,14 +183,10 @@ describe('marketUtils', () => {
},
3,
);
- // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
- const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns empty and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
- remainingFillableMakerAssetAmounts,
inputFeeOrders,
- remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -211,14 +204,10 @@ describe('marketUtils', () => {
},
3,
);
- // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
- const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input fee orders and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
- remainingFillableMakerAssetAmounts,
inputFeeOrders,
- remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -244,9 +233,10 @@ describe('marketUtils', () => {
it('returns first two input fee orders and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
- remainingFillableMakerAssetAmounts,
inputFeeOrders,
- remainingFillableFeeAmounts,
+ {
+ remainingFillableMakerAssetAmounts,
+ },
);
expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
@@ -264,14 +254,10 @@ describe('marketUtils', () => {
},
3,
);
- // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
- const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input fee orders and non-zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
- remainingFillableMakerAssetAmounts,
inputFeeOrders,
- remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
diff --git a/packages/order-utils/test/order_state_utils_test.ts b/packages/order-utils/test/order_state_utils_test.ts
new file mode 100644
index 000000000..91ef23b69
--- /dev/null
+++ b/packages/order-utils/test/order_state_utils_test.ts
@@ -0,0 +1,124 @@
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { AbstractBalanceAndProxyAllowanceFetcher } from '../src/abstract/abstract_balance_and_proxy_allowance_fetcher';
+import { AbstractOrderFilledCancelledFetcher } from '../src/abstract/abstract_order_filled_cancelled_fetcher';
+import { OrderStateUtils } from '../src/order_state_utils';
+
+import { chaiSetup } from './utils/chai_setup';
+import { testOrderFactory } from './utils/test_order_factory';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('OrderStateUtils', () => {
+ describe('#getOpenOrderStateAsync', () => {
+ const buildMockBalanceFetcher = (takerBalance: BigNumber): AbstractBalanceAndProxyAllowanceFetcher => {
+ const balanceFetcher = {
+ async getBalanceAsync(_assetData: string, _userAddress: string): Promise<BigNumber> {
+ return takerBalance;
+ },
+ async getProxyAllowanceAsync(_assetData: string, _userAddress: string): Promise<BigNumber> {
+ return takerBalance;
+ },
+ };
+ return balanceFetcher;
+ };
+ const buildMockOrderFilledFetcher = (
+ filledAmount: BigNumber = new BigNumber(0),
+ cancelled: boolean = false,
+ ): AbstractOrderFilledCancelledFetcher => {
+ const orderFetcher = {
+ async getFilledTakerAmountAsync(_orderHash: string): Promise<BigNumber> {
+ return filledAmount;
+ },
+ async isOrderCancelledAsync(_orderHash: string): Promise<boolean> {
+ return cancelled;
+ },
+ getZRXAssetData(): string {
+ return '';
+ },
+ };
+ return orderFetcher;
+ };
+ it('should have valid order state if order can be fully filled with small maker amount', async () => {
+ const makerAssetAmount = new BigNumber(10);
+ const takerAssetAmount = new BigNumber(10000000000000000);
+ const takerBalance = takerAssetAmount;
+ const orderFilledAmount = new BigNumber(0);
+ const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance);
+ const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount);
+ const [signedOrder] = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ takerAssetAmount,
+ },
+ 1,
+ );
+
+ const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher);
+ const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder);
+ expect(orderState.isValid).to.eq(true);
+ });
+ it('should be invalid when an order is partially filled where only a rounding error remains', async () => {
+ const makerAssetAmount = new BigNumber(1001);
+ const takerAssetAmount = new BigNumber(3);
+ const takerBalance = takerAssetAmount;
+ const orderFilledAmount = new BigNumber(2);
+ const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance);
+ const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount);
+ const [signedOrder] = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ takerAssetAmount,
+ },
+ 1,
+ );
+
+ const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher);
+ const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder);
+ expect(orderState.isValid).to.eq(false);
+ });
+ it('should be invalid when an order is cancelled', async () => {
+ const makerAssetAmount = new BigNumber(1000);
+ const takerAssetAmount = new BigNumber(2);
+ const takerBalance = takerAssetAmount;
+ const orderFilledAmount = new BigNumber(0);
+ const isCancelled = true;
+ const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance);
+ const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount, isCancelled);
+ const [signedOrder] = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ takerAssetAmount,
+ },
+ 1,
+ );
+
+ const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher);
+ const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder);
+ expect(orderState.isValid).to.eq(false);
+ });
+ it('should be invalid when an order is fully filled', async () => {
+ const makerAssetAmount = new BigNumber(1000);
+ const takerAssetAmount = new BigNumber(2);
+ const takerBalance = takerAssetAmount;
+ const orderFilledAmount = takerAssetAmount;
+ const isCancelled = false;
+ const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance);
+ const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount, isCancelled);
+ const [signedOrder] = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ takerAssetAmount,
+ },
+ 1,
+ );
+
+ const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher);
+ const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder);
+ expect(orderState.isValid).to.eq(false);
+ });
+ });
+});
diff --git a/packages/order-utils/test/rate_utils_test.ts b/packages/order-utils/test/rate_utils_test.ts
new file mode 100644
index 000000000..2e299c209
--- /dev/null
+++ b/packages/order-utils/test/rate_utils_test.ts
@@ -0,0 +1,55 @@
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { rateUtils } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { testOrderFactory } from './utils/test_order_factory';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('rateUtils', () => {
+ const testOrder = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(100),
+ takerFee: new BigNumber(20),
+ });
+ describe('#getFeeAdjustedRateOfOrder', () => {
+ it('throws when feeRate is less than zero', async () => {
+ const feeRate = new BigNumber(-1);
+ expect(() => rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate)).to.throw(
+ 'Expected feeRate: -1 to be greater than or equal to 0',
+ );
+ });
+ it('correctly calculates fee adjusted rate when feeRate is provided', async () => {
+ const feeRate = new BigNumber(2); // ZRX costs 2 units of takerAsset per 1 unit of ZRX
+ const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate);
+ // the order actually takes 100 + (2 * 20) takerAsset units to fill 100 units of makerAsset
+ expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.4));
+ });
+ it('correctly calculates fee adjusted rate when no feeRate is provided', async () => {
+ const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder);
+ // because no feeRate was provided we just assume 0 fees
+ // the order actually takes 100 takerAsset units to fill 100 units of makerAsset
+ expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1));
+ });
+ });
+ describe('#getFeeAdjustedRateOfFeeOrder', () => {
+ it('throws when takerFee exceeds makerAssetAmount', async () => {
+ const badOrder = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerFee: new BigNumber(101),
+ });
+ expect(() => rateUtils.getFeeAdjustedRateOfFeeOrder(badOrder)).to.throw(
+ 'Expected takerFee: "101" to be less than makerAssetAmount: "100"',
+ );
+ });
+ it('correctly calculates fee adjusted rate', async () => {
+ const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfFeeOrder(testOrder);
+ // the order actually takes 100 takerAsset units to fill (100 - 20) units of makerAsset
+ expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.25));
+ });
+ });
+});
diff --git a/packages/order-utils/test/sorting_utils_test.ts b/packages/order-utils/test/sorting_utils_test.ts
new file mode 100644
index 000000000..016432505
--- /dev/null
+++ b/packages/order-utils/test/sorting_utils_test.ts
@@ -0,0 +1,67 @@
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { sortingUtils } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { testOrderFactory } from './utils/test_order_factory';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('sortingUtils', () => {
+ describe('#sortOrdersByFeeAdjustedRate', () => {
+ const feeRate = new BigNumber(1); // ZRX costs 1 unit of takerAsset per 1 unit of ZRX
+ // rate: 2 takerAsset / makerAsset
+ const testOrder1 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ });
+ // rate: 1 takerAsset / makerAsset
+ const testOrder2 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(100),
+ });
+ // rate: 2.5 takerAsset / makerAsset
+ const testOrder3 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ takerFee: new BigNumber(50),
+ });
+ it('correctly sorts by fee adjusted rate when feeRate is Provided', async () => {
+ const orders = [testOrder1, testOrder2, testOrder3];
+ const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders, feeRate);
+ expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]);
+ });
+ it('correctly sorts by fee adjusted rate when no feeRate is Provided', async () => {
+ const orders = [testOrder1, testOrder2, testOrder3];
+ const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders);
+ expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]);
+ });
+ });
+ describe('#sortFeeOrdersByFeeAdjustedRate', () => {
+ // rate: 200 takerAsset / makerAsset
+ const testOrder1 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ takerFee: new BigNumber(99),
+ });
+ // rate: 1 takerAsset / makerAsset
+ const testOrder2 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(100),
+ });
+ // rate: 4 takerAsset / makerAsset
+ const testOrder3 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ takerFee: new BigNumber(50),
+ });
+ it('correctly sorts by fee adjusted rate', async () => {
+ const orders = [testOrder1, testOrder2, testOrder3];
+ const sortedOrders = sortingUtils.sortFeeOrdersByFeeAdjustedRate(orders);
+ expect(sortedOrders).to.deep.equal([testOrder2, testOrder3, testOrder1]);
+ });
+ });
+});