diff options
-rw-r--r-- | packages/order-utils/src/market_utils.ts | 93 | ||||
-rw-r--r-- | packages/order-utils/src/types.ts | 28 | ||||
-rw-r--r-- | packages/order-utils/test/market_utils_test.ts | 67 |
3 files changed, 125 insertions, 63 deletions
diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index 681059ddf..58e9ab7ba 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -5,37 +5,42 @@ 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 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 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[], makerAssetFillAmount: BigNumber, - slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + opts?: FindOrdersThatCoverMakerAssetFillAmountOpts, ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); + // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from signedOrders + const remainingFillableMakerAssetAmounts = _.get( + opts, + 'remainingFillableMakerAssetAmounts', + _.map(signedOrders, 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', + 'Expected signedOrders.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 @@ -64,47 +69,53 @@ export const marketUtils = { 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 signedOrders 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 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 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 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, + opts?: FindFeeOrdersThatCoverFeesForTargetOrdersOpts, ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } { assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from signedOrders + const remainingFillableMakerAssetAmounts = _.get( + opts, + 'remainingFillableMakerAssetAmounts', + _.map(signedOrders, order => order.makerAssetAmount), + ) as BigNumber[]; _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), ); - assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); - _.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', + 'Expected signedOrders.length to equal opts.remainingFillableMakerAssetAmounts.length', + ); + // try to get remainingFillableFeeAmounts from opts, if it's not there, use makerAssetAmount values from signedFeeOrders + const remainingFillableFeeAmounts = _.get( + opts, + 'remainingFillableFeeAmounts', + _.map(signedFeeOrders, order => order.makerAssetAmount), + ) as BigNumber[]; + _.forEach(remainingFillableFeeAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount), ); assert.assert( signedOrders.length === remainingFillableMakerAssetAmounts.length, - 'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length', + 'Expected signedFeeOrders.length to equal opts.remainingFillableFeeAmounts.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 ZRX needed to fill signedOrders const totalFeeAmount = _.reduce( signedOrders, @@ -119,9 +130,11 @@ export const marketUtils = { ); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( signedFeeOrders, - remainingFillableFeeAmounts, totalFeeAmount, - slippageBufferAmount, + { + remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts, + slippageBufferAmount, + }, ); return { resultOrders, diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index 1fbd8cf7b..8a9a4cafe 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 `signedOrders` parameter. + * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * Defaults to `makerAssetAmount` values from the signedOrders 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 `signedOrders` parameter. + * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * Defaults to `makerAssetAmount` values from the signedOrders 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. + * Defaults to `makerAssetAmount` values from the signedFeeOrders 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..2f6bb7bd1 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -11,14 +11,13 @@ chaiSetup.configure(); const expect = chai.expect; // tslint:disable: no-unused-expression -describe('marketUtils', () => { +describe.only('marketUtils', () => { describe('#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; @@ -43,9 +42,11 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + remainingFillableMakerAssetAmounts, + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -57,9 +58,11 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + remainingFillableMakerAssetAmounts, + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -71,9 +74,11 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(5); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + remainingFillableMakerAssetAmounts, + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5)); @@ -83,8 +88,10 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, + { + remainingFillableMakerAssetAmounts, + }, ); expect(resultOrders).to.be.deep.equal([inputOrders[0]]); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -94,8 +101,10 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(15); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, + { + remainingFillableMakerAssetAmounts, + }, ); expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -120,8 +129,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)); @@ -143,9 +154,10 @@ describe('marketUtils', () => { it('returns empty and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( [], - [], inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableFeeAmounts, + }, ); expect(resultOrders).to.be.empty; expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -168,9 +180,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)); @@ -190,9 +203,11 @@ describe('marketUtils', () => { it('returns empty and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts, + }, ); expect(resultOrders).to.be.empty; expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -215,9 +230,11 @@ describe('marketUtils', () => { it('returns input fee orders and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts, + }, ); expect(resultOrders).to.be.deep.equal(inputFeeOrders); expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -243,9 +260,11 @@ describe('marketUtils', () => { it('returns first two input fee orders and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts, + }, ); expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]); expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -268,9 +287,11 @@ describe('marketUtils', () => { it('returns input fee orders and non-zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts, + }, ); expect(resultOrders).to.be.deep.equal(inputFeeOrders); expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30)); |