added testing to market utils for market sell
This commit is contained in:
		@@ -73,6 +73,8 @@ export {
 | 
			
		||||
    TransferType,
 | 
			
		||||
    FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
 | 
			
		||||
    FindOrdersThatCoverMakerAssetFillAmountOpts,
 | 
			
		||||
    FindOrdersThatCoverTakerAssetFillAmountOpts,
 | 
			
		||||
    FeeOrdersAndRemainingFeeAmount,
 | 
			
		||||
    OrdersAndRemainingFillAmount,
 | 
			
		||||
    OrdersAndRemainingTakerFillAmount,
 | 
			
		||||
    OrdersAndRemainingMakerFillAmount,
 | 
			
		||||
} from './types';
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,16 @@ import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { assert } from './assert';
 | 
			
		||||
import { constants } from './constants';
 | 
			
		||||
import {
 | 
			
		||||
    orderCalculationUtils,
 | 
			
		||||
} from './order_calculation_utils';
 | 
			
		||||
import {
 | 
			
		||||
    FeeOrdersAndRemainingFeeAmount,
 | 
			
		||||
    FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
 | 
			
		||||
    FindOrdersThatCoverMakerAssetFillAmountOpts,
 | 
			
		||||
    FindOrdersThatCoverTakerAssetFillAmountOpts,
 | 
			
		||||
    OrdersAndRemainingFillAmount,
 | 
			
		||||
    OrdersAndRemainingMakerFillAmount,
 | 
			
		||||
    OrdersAndRemainingTakerFillAmount,
 | 
			
		||||
} from './types';
 | 
			
		||||
 | 
			
		||||
export const marketUtils = {
 | 
			
		||||
@@ -18,10 +22,61 @@ export const marketUtils = {
 | 
			
		||||
        orders: T[],
 | 
			
		||||
        takerAssetFillAmount: BigNumber,
 | 
			
		||||
        opts?: FindOrdersThatCoverTakerAssetFillAmountOpts,
 | 
			
		||||
    ): OrdersAndRemainingFillAmount<T> {
 | 
			
		||||
    ): OrdersAndRemainingTakerFillAmount<T> {
 | 
			
		||||
        assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
 | 
			
		||||
        assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
 | 
			
		||||
        
 | 
			
		||||
        // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders
 | 
			
		||||
        const remainingFillableTakerAssetAmounts = _.get(
 | 
			
		||||
            opts,
 | 
			
		||||
            'remainingFillableTakerAssetAmounts',
 | 
			
		||||
            _.map(orders, order => order.takerAssetAmount),
 | 
			
		||||
        ) as BigNumber[];
 | 
			
		||||
        _.forEach(remainingFillableTakerAssetAmounts, (amount, index) =>
 | 
			
		||||
            assert.isValidBaseUnitAmount(`remainingFillableTakerAssetAmount[${index}]`, amount),
 | 
			
		||||
        );
 | 
			
		||||
        assert.assert(
 | 
			
		||||
            orders.length === remainingFillableTakerAssetAmounts.length,
 | 
			
		||||
            'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length',
 | 
			
		||||
        );
 | 
			
		||||
        // try to get slippageBufferAmount from opts, if it's not there, default to 0
 | 
			
		||||
        const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber;
 | 
			
		||||
        assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount);
 | 
			
		||||
        // calculate total amount of makerAsset needed to be filled
 | 
			
		||||
        const totalFillAmount = takerAssetFillAmount.plus(slippageBufferAmount);
 | 
			
		||||
        // iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
 | 
			
		||||
        const result = _.reduce(
 | 
			
		||||
            orders,
 | 
			
		||||
            ({ resultOrders, remainingFillAmount, ordersRemainingFillableTakerAssetAmounts }, order, index) => {
 | 
			
		||||
                if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
 | 
			
		||||
                    return {
 | 
			
		||||
                        resultOrders,
 | 
			
		||||
                        remainingFillAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
                        ordersRemainingFillableTakerAssetAmounts,
 | 
			
		||||
                    };
 | 
			
		||||
                } else {
 | 
			
		||||
                    const takerAssetAmountAvailable = remainingFillableTakerAssetAmounts[index];
 | 
			
		||||
                    const shouldIncludeOrder = takerAssetAmountAvailable.gt(constants.ZERO_AMOUNT);
 | 
			
		||||
                    // if there is no makerAssetAmountAvailable do not append order to resultOrders
 | 
			
		||||
                    // if we have exceeded the total amount we want to fill set remainingFillAmount to 0
 | 
			
		||||
                    return {
 | 
			
		||||
                        resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
 | 
			
		||||
                        ordersRemainingFillableTakerAssetAmounts: shouldIncludeOrder
 | 
			
		||||
                            ? _.concat(ordersRemainingFillableTakerAssetAmounts, takerAssetAmountAvailable)
 | 
			
		||||
                            : ordersRemainingFillableTakerAssetAmounts,
 | 
			
		||||
                        remainingFillAmount: BigNumber.max(
 | 
			
		||||
                            constants.ZERO_AMOUNT,
 | 
			
		||||
                            remainingFillAmount.minus(takerAssetAmountAvailable),
 | 
			
		||||
                        ),
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                resultOrders: [] as T[],
 | 
			
		||||
                remainingFillAmount: totalFillAmount,
 | 
			
		||||
                ordersRemainingFillableTakerAssetAmounts: [] as BigNumber[],
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        return result;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount
 | 
			
		||||
@@ -37,7 +92,7 @@ export const marketUtils = {
 | 
			
		||||
        orders: T[],
 | 
			
		||||
        makerAssetFillAmount: BigNumber,
 | 
			
		||||
        opts?: FindOrdersThatCoverMakerAssetFillAmountOpts,
 | 
			
		||||
    ): OrdersAndRemainingFillAmount<T> {
 | 
			
		||||
    ): OrdersAndRemainingMakerFillAmount<T> {
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -34,19 +34,18 @@ export interface CreateOrderOpts {
 | 
			
		||||
 */
 | 
			
		||||
export interface FindOrdersThatCoverMakerAssetFillAmountOpts {
 | 
			
		||||
    remainingFillableMakerAssetAmounts?: BigNumber[];
 | 
			
		||||
    remainingFillableTakerAssetAmounts?: BigNumber[];
 | 
			
		||||
    slippageBufferAmount?: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * remainingFillableTakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
 | 
			
		||||
 * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
 | 
			
		||||
 * You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values.
 | 
			
		||||
 * 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 FindOrdersThatCoverTakerAssetFillAmountOpts {
 | 
			
		||||
    remainingFillableMakerAssetAmounts?: BigNumber[];
 | 
			
		||||
    remainingFillableTakerAssetAmounts?: BigNumber[];
 | 
			
		||||
    slippageBufferAmount?: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -72,8 +71,14 @@ export interface FeeOrdersAndRemainingFeeAmount<T> {
 | 
			
		||||
    remainingFeeAmount: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OrdersAndRemainingFillAmount<T> {
 | 
			
		||||
export interface OrdersAndRemainingMakerFillAmount<T> {
 | 
			
		||||
    resultOrders: T[];
 | 
			
		||||
    ordersRemainingFillableMakerAssetAmounts: BigNumber[];
 | 
			
		||||
    remainingFillAmount: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OrdersAndRemainingTakerFillAmount<T> {
 | 
			
		||||
    resultOrders: T[];
 | 
			
		||||
    ordersRemainingFillableTakerAssetAmounts: BigNumber[];
 | 
			
		||||
    remainingFillAmount: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,135 @@ const expect = chai.expect;
 | 
			
		||||
 | 
			
		||||
// tslint:disable: no-unused-expression
 | 
			
		||||
describe('marketUtils', () => {
 | 
			
		||||
    describe('#findOrdersThatCoverTakerAssetFillAmount', () => {
 | 
			
		||||
        describe('no orders', () => {
 | 
			
		||||
            it('returns empty and unchanged remainingFillAmount', async () => {
 | 
			
		||||
                const fillAmount = new BigNumber(10);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    [],
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                );
 | 
			
		||||
                expect(resultOrders).to.be.empty;
 | 
			
		||||
                expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        describe('orders are completely fillable', () => {
 | 
			
		||||
            // generate three signed orders each with 10 units of makerAsset, 30 total
 | 
			
		||||
            const takerAssetAmount = new BigNumber(10);
 | 
			
		||||
            const inputOrders = testOrderFactory.generateTestSignedOrders(
 | 
			
		||||
                {
 | 
			
		||||
                    takerAssetAmount,
 | 
			
		||||
                },
 | 
			
		||||
                3,
 | 
			
		||||
            );
 | 
			
		||||
            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
 | 
			
		||||
                const fillAmount = new BigNumber(20);
 | 
			
		||||
                const slippageBufferAmount = new BigNumber(10);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                    {
 | 
			
		||||
                        slippageBufferAmount,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                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 15 units of makerAsset
 | 
			
		||||
                // include 10 units of slippageBufferAmount
 | 
			
		||||
                const fillAmount = new BigNumber(15);
 | 
			
		||||
                const slippageBufferAmount = new BigNumber(10);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                    {
 | 
			
		||||
                        slippageBufferAmount,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                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 30 units of makerAsset
 | 
			
		||||
                // include 5 units of slippageBufferAmount
 | 
			
		||||
                const fillAmount = new BigNumber(30);
 | 
			
		||||
                const slippageBufferAmount = new BigNumber(5);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                    {
 | 
			
		||||
                        slippageBufferAmount,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                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.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    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.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    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 takerAssetAmount = new BigNumber(10);
 | 
			
		||||
            const inputOrders = testOrderFactory.generateTestSignedOrders(
 | 
			
		||||
                {
 | 
			
		||||
                    takerAssetAmount,
 | 
			
		||||
                },
 | 
			
		||||
                3,
 | 
			
		||||
            );
 | 
			
		||||
            // generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios
 | 
			
		||||
            // 1. order is completely filled already
 | 
			
		||||
            // 2. order is partially fillable
 | 
			
		||||
            // 3. order is completely fillable
 | 
			
		||||
            const remainingFillableTakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), takerAssetAmount];
 | 
			
		||||
            it('returns last two orders and non-zero remainingFillAmount when trying to fill original takerAssetAmounts', async () => {
 | 
			
		||||
                // try to fill 30 units of takerAsset
 | 
			
		||||
                const fillAmount = new BigNumber(30);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                    {
 | 
			
		||||
                        remainingFillableTakerAssetAmounts,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
 | 
			
		||||
                expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15));
 | 
			
		||||
            });
 | 
			
		||||
            it('returns last two orders and zero remainingFillAmount when trying to fill exactly takerAssetAmounts remaining', async () => {
 | 
			
		||||
                // try to fill 15 units of takerAsset
 | 
			
		||||
                const fillAmount = new BigNumber(15);
 | 
			
		||||
                const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
 | 
			
		||||
                    inputOrders,
 | 
			
		||||
                    fillAmount,
 | 
			
		||||
                    {
 | 
			
		||||
                        remainingFillableTakerAssetAmounts,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
 | 
			
		||||
                expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(0));
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    describe('#findOrdersThatCoverMakerAssetFillAmount', () => {
 | 
			
		||||
        describe('no orders', () => {
 | 
			
		||||
            it('returns empty and unchanged remainingFillAmount', async () => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user