aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/order-utils/src/constants.ts1
-rw-r--r--packages/order-utils/src/index.ts1
-rw-r--r--packages/order-utils/src/market_utils.ts23
-rw-r--r--packages/order-utils/test/market_utils_test.ts146
-rw-r--r--packages/order-utils/test/utils/test_order_factory.ts63
5 files changed, 222 insertions, 12 deletions
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts
index b18546a6c..5137ff499 100644
--- a/packages/order-utils/src/constants.ts
+++ b/packages/order-utils/src/constants.ts
@@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ NULL_BYTES: '0x',
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
TESTRPC_NETWORK_ID: 50,
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index 129eb0a3d..858f500c6 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -32,3 +32,4 @@ export { assetDataUtils } from './asset_data_utils';
export { EIP712Utils } from './eip712_utils';
export { OrderValidationUtils } from './order_validation_utils';
export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
+export { marketUtils } from './market_utils';
diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts
index 4ddcc6ec8..710eddf8a 100644
--- a/packages/order-utils/src/market_utils.ts
+++ b/packages/order-utils/src/market_utils.ts
@@ -39,10 +39,17 @@ export const marketUtils = {
return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT };
} else {
const orderState = orderStates[index];
- const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState);
+ const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount;
+ // 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: _.concat(resultOrders, order),
- remainingFillAmount: remainingFillAmount.minus(makerAssetAmountAvailable),
+ resultOrders: makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT)
+ ? _.concat(resultOrders, order)
+ : resultOrders,
+ remainingFillAmount: BigNumber.max(
+ constants.ZERO_AMOUNT,
+ remainingFillAmount.minus(makerAssetAmountAvailable),
+ ),
};
}
},
@@ -83,7 +90,7 @@ export const marketUtils = {
signedOrders,
(accFees, order, index) => {
const orderState = orderStates[index];
- const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState);
+ const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount;
const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable
.div(order.makerAssetAmount)
.mul(order.takerFee);
@@ -99,11 +106,3 @@ export const marketUtils = {
);
},
};
-
-const getMakerAssetAmountAvailable = (orderState: OrderRelevantState) => {
- return BigNumber.min(
- orderState.makerBalance,
- orderState.remainingFillableMakerAssetAmount,
- orderState.makerProxyAllowance,
- );
-};
diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts
new file mode 100644
index 000000000..93779d035
--- /dev/null
+++ b/packages/order-utils/test/market_utils_test.ts
@@ -0,0 +1,146 @@
+import { OrderRelevantState, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import 'mocha';
+
+import { constants, marketUtils, orderFactory } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { testOrderFactory } from './utils/test_order_factory';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+// tslint:disable: no-unused-expression
+describe('marketUtils', () => {
+ describe.only('#findOrdersThatCoverMakerAssetFillAmount', () => {
+ describe('no orders', () => {
+ it('returns empty and unchanged remainingFillAmount', async () => {
+ const fillAmount = new BigNumber(10);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ [],
+ [],
+ fillAmount,
+ );
+ expect(resultOrders).to.be.empty;
+ expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
+ });
+ });
+ describe('orders are all completely fillable', () => {
+ // generate three signed orders each with 10 units of makerAsset, 30 total
+ const testOrderCount = 3;
+ const makerAssetAmount = new BigNumber(10);
+ const inputOrders = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ },
+ testOrderCount,
+ );
+ // generate order states that cover the required fill amount
+ const inputOrderStates = testOrderFactory.generateTestOrderRelevantStates(
+ {
+ remainingFillableMakerAssetAmount: makerAssetAmount,
+ },
+ testOrderCount,
+ );
+ it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
+ // try to fill 30 units of makerAsset
+ const fillAmount = new BigNumber(30);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal(inputOrders);
+ expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
+ // try to fill 25 units of makerAsset
+ const fillAmount = new BigNumber(25);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal(inputOrders);
+ expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
+ // try to fill 35 units of makerAsset
+ const fillAmount = new BigNumber(35);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal(inputOrders);
+ expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5));
+ });
+ it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
+ // try to fill 10 units of makerAsset
+ const fillAmount = new BigNumber(10);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
+ expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
+ // try to fill 15 units of makerAsset
+ const fillAmount = new BigNumber(15);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
+ expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ });
+ describe('orders are partially fillable', () => {
+ // generate three signed orders each with 10 units of makerAsset, 30 total
+ const testOrderCount = 3;
+ const makerAssetAmount = new BigNumber(10);
+ const inputOrders = testOrderFactory.generateTestSignedOrders(
+ {
+ makerAssetAmount,
+ },
+ testOrderCount,
+ );
+ // generate order states that cover different scenarios
+ // 1. order is completely filled already
+ // 2. order is partially fillable
+ // 3. order is completely fillable
+ const partialOrderStates: Array<Partial<OrderRelevantState>> = [
+ {
+ remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT,
+ },
+ {
+ remainingFillableMakerAssetAmount: new BigNumber(5),
+ },
+ {
+ remainingFillableMakerAssetAmount: makerAssetAmount,
+ },
+ ];
+ const inputOrderStates: OrderRelevantState[] = _.map(
+ partialOrderStates,
+ testOrderFactory.generateTestOrderRelevantState,
+ );
+ it('returns last 2 orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => {
+ // try to fill 30 units of makerAsset
+ const fillAmount = new BigNumber(30);
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ inputOrders,
+ inputOrderStates,
+ fillAmount,
+ );
+ expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
+ expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15));
+ });
+ });
+ });
+ describe('#findFeeOrdersThatCoverFeesForTargetOrders', () => {});
+});
diff --git a/packages/order-utils/test/utils/test_order_factory.ts b/packages/order-utils/test/utils/test_order_factory.ts
new file mode 100644
index 000000000..2c5d8cf61
--- /dev/null
+++ b/packages/order-utils/test/utils/test_order_factory.ts
@@ -0,0 +1,63 @@
+import { Order, OrderRelevantState, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { constants, orderFactory } from '../../src';
+
+const BASE_TEST_ORDER: Order = orderFactory.createOrder(
+ constants.NULL_ADDRESS,
+ constants.NULL_ADDRESS,
+ constants.NULL_ADDRESS,
+ constants.ZERO_AMOUNT,
+ constants.ZERO_AMOUNT,
+ constants.ZERO_AMOUNT,
+ constants.NULL_BYTES,
+ constants.ZERO_AMOUNT,
+ constants.NULL_BYTES,
+ constants.NULL_ADDRESS,
+ constants.NULL_ADDRESS,
+);
+const BASE_TEST_SIGNED_ORDER: SignedOrder = {
+ ...BASE_TEST_ORDER,
+ signature: constants.NULL_BYTES,
+};
+const BASE_TEST_ORDER_RELEVANT_STATE: OrderRelevantState = {
+ makerBalance: constants.ZERO_AMOUNT,
+ makerProxyAllowance: constants.ZERO_AMOUNT,
+ makerFeeBalance: constants.ZERO_AMOUNT,
+ makerFeeProxyAllowance: constants.ZERO_AMOUNT,
+ filledTakerAssetAmount: constants.ZERO_AMOUNT,
+ remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT,
+ remainingFillableTakerAssetAmount: constants.ZERO_AMOUNT,
+};
+
+export const testOrderFactory = {
+ generateTestSignedOrder(partialOrder: Partial<SignedOrder>): SignedOrder {
+ return transformObject(BASE_TEST_SIGNED_ORDER, partialOrder);
+ },
+ generateTestSignedOrders(partialOrder: Partial<SignedOrder>, numOrders: number): SignedOrder[] {
+ const baseTestOrders = generateArrayOfInput(BASE_TEST_SIGNED_ORDER, numOrders);
+ return transformObjects(baseTestOrders, partialOrder);
+ },
+ generateTestOrderRelevantState(partialOrderRelevantState: Partial<OrderRelevantState>): OrderRelevantState {
+ return transformObject(BASE_TEST_ORDER_RELEVANT_STATE, partialOrderRelevantState);
+ },
+ generateTestOrderRelevantStates(
+ partialOrderRelevantState: Partial<OrderRelevantState>,
+ numOrderStates: number,
+ ): OrderRelevantState[] {
+ const baseTestOrderStates = generateArrayOfInput(BASE_TEST_ORDER_RELEVANT_STATE, numOrderStates);
+ return transformObjects(baseTestOrderStates, partialOrderRelevantState);
+ },
+};
+
+function generateArrayOfInput<T>(input: T, rangeLength: number): T[] {
+ return _.map(_.range(rangeLength), () => input);
+}
+function transformObject<T>(input: T, transformation: Partial<T>): T {
+ const copy = _.cloneDeep(input);
+ return _.assign(copy, transformation);
+}
+function transformObjects<T>(inputs: T[], transformation: Partial<T>): T[] {
+ return _.map(inputs, input => transformObject(input, transformation));
+}