aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils')
-rw-r--r--packages/order-utils/CHANGELOG.json22
-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/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
6 files changed, 308 insertions, 140 deletions
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json
index a2dcf13c4..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": [
{
@@ -18,10 +36,6 @@
},
{
"note": "Dependencies updated"
- },
- {
- "note": "Added rateUtils and sortingUtils",
- "pr": 953
}
],
"timestamp": 1534210131
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 189bf4180..18fc18bf6 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 };
}
constructor(
balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher,
@@ -101,6 +101,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,
@@ -109,20 +110,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;
}
@@ -278,6 +280,7 @@ export class OrderStateUtils {
traderFeeProxyAllowance,
filledTakerAssetAmount,
remainingFillableAssetAmount,
+ isOrderCancelled,
};
return sidedOrderRelevantState;
}
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 21c0a4802..109420a02 100644
--- a/packages/order-utils/test/market_utils_test.ts
+++ b/packages/order-utils/test/market_utils_test.ts
@@ -18,7 +18,6 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
[],
- [],
fillAmount,
);
expect(resultOrders).to.be.empty;
@@ -34,8 +33,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
@@ -43,9 +40,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);
@@ -57,9 +55,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);
@@ -71,9 +70,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));
@@ -83,7 +83,6 @@ describe('marketUtils', () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
- remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
@@ -94,7 +93,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]]);
@@ -120,8 +118,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));
@@ -137,15 +137,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);
@@ -168,9 +164,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));
@@ -185,14 +182,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);
@@ -210,14 +203,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);
@@ -243,9 +232,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);
@@ -263,14 +253,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);
+ });
+ });
+});