740 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			740 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
 | |
| import { SignedOrder } from '@0x/types';
 | |
| import { BigNumber } from '@0x/utils';
 | |
| import * as chai from 'chai';
 | |
| import * as _ from 'lodash';
 | |
| import 'mocha';
 | |
| 
 | |
| import { constants } from '../src/constants';
 | |
| import { OrdersAndFillableAmounts, SwapQuoterError } from '../src/types';
 | |
| import { swapQuoteCalculator } from '../src/utils/swap_quote_calculator';
 | |
| 
 | |
| import { chaiSetup } from './utils/chai_setup';
 | |
| import { testHelpers } from './utils/test_helpers';
 | |
| 
 | |
| chaiSetup.configure();
 | |
| const expect = chai.expect;
 | |
| 
 | |
| // tslint:disable:max-file-line-count
 | |
| // tslint:disable:custom-no-magic-numbers
 | |
| describe('swapQuoteCalculator', () => {
 | |
|     describe('#calculateMarketSellSwapQuote', () => {
 | |
|         let firstOrder: SignedOrder;
 | |
|         let firstRemainingFillAmount: BigNumber;
 | |
|         let secondOrder: SignedOrder;
 | |
|         let secondRemainingFillAmount: BigNumber;
 | |
|         let ordersAndFillableAmounts: OrdersAndFillableAmounts;
 | |
|         let smallFeeOrderAndFillableAmount: OrdersAndFillableAmounts;
 | |
|         let allFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts;
 | |
|         beforeEach(() => {
 | |
|             // generate two orders for our desired maker asset
 | |
|             // the first order has a rate of 4 makerAsset / WETH with a takerFee of 200 ZRX and has only 200 / 400 makerAsset units left to fill (half fillable)
 | |
|             // the second order has a rate of 2 makerAsset / WETH with a takerFee of 100 ZRX and has 200 / 200 makerAsset units left to fill (completely fillable)
 | |
|             // generate one order for fees
 | |
|             // the fee order has a rate of 1 ZRX / WETH with no taker fee and has 100 ZRX left to fill (completely fillable)
 | |
|             firstOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(400),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|                 takerFee: new BigNumber(200),
 | |
|             });
 | |
|             firstRemainingFillAmount = new BigNumber(200);
 | |
|             secondOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(200),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|                 takerFee: new BigNumber(100),
 | |
|             });
 | |
|             secondRemainingFillAmount = secondOrder.makerAssetAmount;
 | |
|             ordersAndFillableAmounts = {
 | |
|                 orders: [firstOrder, secondOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount],
 | |
|             };
 | |
|             const smallFeeOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(100),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|             });
 | |
|             smallFeeOrderAndFillableAmount = {
 | |
|                 orders: [smallFeeOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount],
 | |
|             };
 | |
|             const largeFeeOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(113),
 | |
|                 takerAssetAmount: new BigNumber(200),
 | |
|                 takerFee: new BigNumber(11),
 | |
|             });
 | |
|             allFeeOrdersAndFillableAmounts = {
 | |
|                 orders: [smallFeeOrder, largeFeeOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [
 | |
|                     smallFeeOrder.makerAssetAmount,
 | |
|                     largeFeeOrder.makerAssetAmount.minus(largeFeeOrder.takerFee),
 | |
|                 ],
 | |
|             };
 | |
|         });
 | |
|         describe('InsufficientLiquidityError', () => {
 | |
|             it('should throw if not enough taker asset liquidity (multiple orders)', () => {
 | |
|                 // we have 150 takerAsset units available to sell but attempt to calculate a quote for 200 takerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(200),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(150));
 | |
|             });
 | |
|             it('should throw if not enough taker asset liquidity (multiple orders with 20% slippage)', () => {
 | |
|                 // we have 150 takerAsset units available to sell but attempt to calculate a quote for 200 takerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(200),
 | |
|                         0.2,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(125));
 | |
|             });
 | |
|             it('should throw if not enough taker asset liquidity (multiple orders with 5% slippage)', () => {
 | |
|                 // we have 150 takerAsset units available to fill but attempt to calculate a quote for 200 takerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(200),
 | |
|                         0.05,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(142));
 | |
|             });
 | |
|             it('should throw if not enough taker asset liquidity (partially filled order)', () => {
 | |
|                 const firstOrderAndFillableAmount: OrdersAndFillableAmounts = {
 | |
|                     orders: [firstOrder],
 | |
|                     remainingFillableMakerAssetAmounts: [firstRemainingFillAmount],
 | |
|                 };
 | |
| 
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         firstOrderAndFillableAmount,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(51),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(50));
 | |
|             });
 | |
|             it('should throw if not enough taker asset liquidity (completely fillable order)', () => {
 | |
|                 const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(123),
 | |
|                     takerAssetAmount: new BigNumber(80),
 | |
|                     takerFee: new BigNumber(200),
 | |
|                 });
 | |
|                 const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = {
 | |
|                     orders: [completelyFillableOrder],
 | |
|                     remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount],
 | |
|                 };
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         completelyFillableOrdersAndFillableAmount,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(81),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(80));
 | |
|             });
 | |
|             it('should throw with 1 amount available if no slippage', () => {
 | |
|                 const smallOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(1),
 | |
|                     takerAssetAmount: new BigNumber(1),
 | |
|                     takerFee: new BigNumber(0),
 | |
|                 });
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(100),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(1));
 | |
|             });
 | |
|             it('should throw with 0 available to fill if amount rounds to 0', () => {
 | |
|                 const smallOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(1),
 | |
|                     takerAssetAmount: new BigNumber(1),
 | |
|                     takerFee: new BigNumber(0),
 | |
|                 });
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                         { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(100),
 | |
|                         0.2,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(0));
 | |
|             });
 | |
|         });
 | |
|         it('should not throw if order is fillable', () => {
 | |
|             expect(() =>
 | |
|                 swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                     ordersAndFillableAmounts,
 | |
|                     allFeeOrdersAndFillableAmounts,
 | |
|                     new BigNumber(125),
 | |
|                     0,
 | |
|                     false,
 | |
|                     false,
 | |
|                 ),
 | |
|             ).to.not.throw();
 | |
|         });
 | |
|         it('should throw if not enough ZRX liquidity', () => {
 | |
|             // we request 75 takerAsset units but the ZRX order is only enough to fill the first order, which only has 50 takerAsset units available
 | |
|             expect(() =>
 | |
|                 swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                     ordersAndFillableAmounts,
 | |
|                     smallFeeOrderAndFillableAmount,
 | |
|                     new BigNumber(75),
 | |
|                     0,
 | |
|                     false,
 | |
|                     false,
 | |
|                 ),
 | |
|             ).to.throw(SwapQuoterError.InsufficientZrxLiquidity);
 | |
|         });
 | |
|         it('calculates a correct swapQuote with no slippage', () => {
 | |
|             // we request 50 takerAsset units which can be filled using the first order
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetSellAmount = new BigNumber(50);
 | |
|             const slippagePercentage = 0;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 smallFeeOrderAndFillableAmount,
 | |
|                 assetSellAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 false,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]);
 | |
|             // test if rates are correct
 | |
|             // 50 takerAsset units to fill the first order + 100 takerAsset units for fees
 | |
|             const expectedMakerAssetAmountForTakerAsset = new BigNumber(200);
 | |
|             const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
 | |
|             const expectedTotalTakerAssetAmount = assetSellAmount.plus(expectedTakerAssetAmountForZrxFees);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // because we have no slippage protection, minRate is equal to maxRate
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote with slippage', () => {
 | |
|             // we request 50 takerAsset units which can be filled using the first order
 | |
|             // however with 50% slippage we are protecting the buy with 25 extra takerAssetUnits
 | |
|             // so we need enough orders to fill 75 takerAssetUnits
 | |
|             // 75 takerAssetUnits can only be filled using both orders
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetSellAmount = new BigNumber(50);
 | |
|             const slippagePercentage = 0.5;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 allFeeOrdersAndFillableAmounts,
 | |
|                 assetSellAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 false,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders);
 | |
|             // test if rates are correct
 | |
|             const expectedMakerAssetAmountForTakerAsset = new BigNumber(200);
 | |
|             const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
 | |
|             const expectedTotalTakerAssetAmount = assetSellAmount.plus(expectedTakerAssetAmountForZrxFees);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
| 
 | |
|             const expectedWorstMakerAssetAmountForTakerAsset = new BigNumber(100);
 | |
|             const expectedWorstTakerAssetAmountForZrxFees = new BigNumber(99);
 | |
|             const expectedWorstTotalTakerAssetAmount = assetSellAmount.plus(expectedWorstTakerAssetAmountForZrxFees);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote (with fee calculations disabled) with no slippage', () => {
 | |
|             // we request 50 takerAsset units which can be filled using the first order
 | |
|             const assetSellAmount = new BigNumber(50);
 | |
|             const slippagePercentage = 0;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 smallFeeOrderAndFillableAmount,
 | |
|                 assetSellAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 true,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([]);
 | |
|             // test if rates are correct
 | |
|             const expectedMakerAssetAmountForTakerAsset = new BigNumber(200);
 | |
|             const expectedTotalTakerAssetAmount = assetSellAmount;
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // because we have no slippage protection, minRate is equal to maxRate
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote (with fee calculatations disabled) with slippage', () => {
 | |
|             // we request 50 takerAsset units which can be filled using the first order
 | |
|             // however with 50% slippage we are protecting the buy with 25 extra takerAssetUnits
 | |
|             // so we need enough orders to fill 75 takerAssetUnits
 | |
|             // 50 takerAssetUnits can only be filled using both orders
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetSellAmount = new BigNumber(50);
 | |
|             const slippagePercentage = 0.5;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 allFeeOrdersAndFillableAmounts,
 | |
|                 assetSellAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 true,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([]);
 | |
|             // test if rates are correct
 | |
|             const expectedMakerAssetAmountForTakerAsset = new BigNumber(200);
 | |
|             const expectedTotalTakerAssetAmount = assetSellAmount;
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // 100 eth to fill the first order + 208 eth for fees
 | |
|             const expectedWorstMakerAssetAmountForTakerAsset = new BigNumber(100);
 | |
|             const expectedWorstTotalTakerAssetAmount = assetSellAmount;
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(assetSellAmount);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.makerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstMakerAssetAmountForTakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|     });
 | |
|     describe('#calculateMarketBuySwapQuote', () => {
 | |
|         let firstOrder: SignedOrder;
 | |
|         let firstRemainingFillAmount: BigNumber;
 | |
|         let secondOrder: SignedOrder;
 | |
|         let secondRemainingFillAmount: BigNumber;
 | |
|         let ordersAndFillableAmounts: OrdersAndFillableAmounts;
 | |
|         let smallFeeOrderAndFillableAmount: OrdersAndFillableAmounts;
 | |
|         let allFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts;
 | |
|         beforeEach(() => {
 | |
|             // generate two orders for our desired maker asset
 | |
|             // the first order has a rate of 4 makerAsset / WETH with a takerFee of 200 ZRX and has only 200 / 400 makerAsset units left to fill (half fillable)
 | |
|             // the second order has a rate of 2 makerAsset / WETH with a takerFee of 100 ZRX and has 200 / 200 makerAsset units left to fill (completely fillable)
 | |
|             // generate one order for fees
 | |
|             // the fee order has a rate of 1 ZRX / WETH with no taker fee and has 100 ZRX left to fill (completely fillable)
 | |
|             firstOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(400),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|                 takerFee: new BigNumber(200),
 | |
|             });
 | |
|             firstRemainingFillAmount = new BigNumber(200);
 | |
|             secondOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(200),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|                 takerFee: new BigNumber(100),
 | |
|             });
 | |
|             secondRemainingFillAmount = secondOrder.makerAssetAmount;
 | |
|             ordersAndFillableAmounts = {
 | |
|                 orders: [firstOrder, secondOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount],
 | |
|             };
 | |
|             const smallFeeOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(100),
 | |
|                 takerAssetAmount: new BigNumber(100),
 | |
|             });
 | |
|             smallFeeOrderAndFillableAmount = {
 | |
|                 orders: [smallFeeOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount],
 | |
|             };
 | |
|             const largeFeeOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                 makerAssetAmount: new BigNumber(113),
 | |
|                 takerAssetAmount: new BigNumber(200),
 | |
|                 takerFee: new BigNumber(11),
 | |
|             });
 | |
|             allFeeOrdersAndFillableAmounts = {
 | |
|                 orders: [smallFeeOrder, largeFeeOrder],
 | |
|                 remainingFillableMakerAssetAmounts: [
 | |
|                     smallFeeOrder.makerAssetAmount,
 | |
|                     largeFeeOrder.makerAssetAmount.minus(largeFeeOrder.takerFee),
 | |
|                 ],
 | |
|             };
 | |
|         });
 | |
|         describe('InsufficientLiquidityError', () => {
 | |
|             it('should throw if not enough maker asset liquidity (multiple orders)', () => {
 | |
|                 // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(500),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400));
 | |
|             });
 | |
|             it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => {
 | |
|                 // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(500),
 | |
|                         0.2,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(333));
 | |
|             });
 | |
|             it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => {
 | |
|                 // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         ordersAndFillableAmounts,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(600),
 | |
|                         0.05,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(380));
 | |
|             });
 | |
|             it('should throw if not enough maker asset liquidity (partially filled order)', () => {
 | |
|                 const firstOrderAndFillableAmount: OrdersAndFillableAmounts = {
 | |
|                     orders: [firstOrder],
 | |
|                     remainingFillableMakerAssetAmounts: [firstRemainingFillAmount],
 | |
|                 };
 | |
| 
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         firstOrderAndFillableAmount,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(201),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(200));
 | |
|             });
 | |
|             it('should throw if not enough maker asset liquidity (completely fillable order)', () => {
 | |
|                 const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(123),
 | |
|                     takerAssetAmount: new BigNumber(100),
 | |
|                     takerFee: new BigNumber(200),
 | |
|                 });
 | |
|                 const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = {
 | |
|                     orders: [completelyFillableOrder],
 | |
|                     remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount],
 | |
|                 };
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         completelyFillableOrdersAndFillableAmount,
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(124),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(123));
 | |
|             });
 | |
|             it('should throw with 1 amount available if no slippage', () => {
 | |
|                 const smallOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(1),
 | |
|                     takerAssetAmount: new BigNumber(1),
 | |
|                     takerFee: new BigNumber(0),
 | |
|                 });
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(600),
 | |
|                         0,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(1));
 | |
|             });
 | |
|             it('should throw with 0 available to fill if amount rounds to 0', () => {
 | |
|                 const smallOrder = orderFactory.createSignedOrderFromPartial({
 | |
|                     makerAssetAmount: new BigNumber(1),
 | |
|                     takerAssetAmount: new BigNumber(1),
 | |
|                     takerFee: new BigNumber(0),
 | |
|                 });
 | |
|                 const errorFunction = () => {
 | |
|                     swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                         { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
 | |
|                         smallFeeOrderAndFillableAmount,
 | |
|                         new BigNumber(600),
 | |
|                         0.2,
 | |
|                         false,
 | |
|                         false,
 | |
|                     );
 | |
|                 };
 | |
|                 testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(0));
 | |
|             });
 | |
|         });
 | |
|         it('should not throw if order is fillable', () => {
 | |
|             expect(() =>
 | |
|                 swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                     ordersAndFillableAmounts,
 | |
|                     allFeeOrdersAndFillableAmounts,
 | |
|                     new BigNumber(300),
 | |
|                     0,
 | |
|                     false,
 | |
|                     false,
 | |
|                 ),
 | |
|             ).to.not.throw();
 | |
|         });
 | |
|         it('should throw if not enough ZRX liquidity', () => {
 | |
|             // we request 300 makerAsset units but the ZRX order is only enough to fill the first order, which only has 200 makerAssetUnits available
 | |
|             expect(() =>
 | |
|                 swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                     ordersAndFillableAmounts,
 | |
|                     smallFeeOrderAndFillableAmount,
 | |
|                     new BigNumber(300),
 | |
|                     0,
 | |
|                     false,
 | |
|                     false,
 | |
|                 ),
 | |
|             ).to.throw(SwapQuoterError.InsufficientZrxLiquidity);
 | |
|         });
 | |
|         it('calculates a correct swapQuote with no slippage', () => {
 | |
|             // we request 200 makerAsset units which can be filled using the first order
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetBuyAmount = new BigNumber(200);
 | |
|             const slippagePercentage = 0;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 smallFeeOrderAndFillableAmount,
 | |
|                 assetBuyAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 false,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]);
 | |
|             // test if rates are correct
 | |
|             // 50 eth to fill the first order + 100 eth for fees
 | |
|             const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
 | |
|             const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
 | |
|             const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset.plus(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // because we have no slippage protection, minRate is equal to maxRate
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote with slippage', () => {
 | |
|             // we request 200 makerAsset units which can be filled using the first order
 | |
|             // however with 50% slippage we are protecting the buy with 100 extra makerAssetUnits
 | |
|             // so we need enough orders to fill 300 makerAssetUnits
 | |
|             // 300 makerAssetUnits can only be filled using both orders
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetBuyAmount = new BigNumber(200);
 | |
|             const slippagePercentage = 0.5;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 allFeeOrdersAndFillableAmounts,
 | |
|                 assetBuyAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 false,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders);
 | |
|             // test if rates are correct
 | |
|             // 50 eth to fill the first order + 100 eth for fees
 | |
|             const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
 | |
|             const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
 | |
|             const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset.plus(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // 100 eth to fill the first order + 208 eth for fees
 | |
|             const expectedWorstTakerAssetAmountForMakerAsset = new BigNumber(100);
 | |
|             const expectedWorstTakerAssetAmountForZrxFees = new BigNumber(208);
 | |
|             const expectedWorstTotalTakerAssetAmount = expectedWorstTakerAssetAmountForMakerAsset.plus(
 | |
|                 expectedWorstTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTakerAssetAmountForZrxFees,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote (with fee calculations disabled) with no slippage', () => {
 | |
|             // we request 200 makerAsset units which can be filled using the first order
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetBuyAmount = new BigNumber(200);
 | |
|             const slippagePercentage = 0;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 smallFeeOrderAndFillableAmount,
 | |
|                 assetBuyAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 true,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([]);
 | |
|             // test if rates are correct
 | |
|             // 50 eth to fill the first order + 100 eth for fees
 | |
|             const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
 | |
|             const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset;
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // because we have no slippage protection, minRate is equal to maxRate
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|         it('calculates a correct swapQuote (with fee calculations disabled) with slippage', () => {
 | |
|             // we request 200 makerAsset units which can be filled using the first order
 | |
|             // however with 50% slippage we are protecting the buy with 100 extra makerAssetUnits
 | |
|             // so we need enough orders to fill 300 makerAssetUnits
 | |
|             // 300 makerAssetUnits can only be filled using both orders
 | |
|             // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
 | |
|             const assetBuyAmount = new BigNumber(200);
 | |
|             const slippagePercentage = 0.5;
 | |
|             const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
 | |
|                 ordersAndFillableAmounts,
 | |
|                 allFeeOrdersAndFillableAmounts,
 | |
|                 assetBuyAmount,
 | |
|                 slippagePercentage,
 | |
|                 false,
 | |
|                 true,
 | |
|             );
 | |
|             // test if orders are correct
 | |
|             expect(swapQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
 | |
|             expect(swapQuote.feeOrders).to.deep.equal([]);
 | |
|             // test if rates are correct
 | |
|             // 50 eth to fill the first order + 100 eth for fees
 | |
|             const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
 | |
|             const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset;
 | |
|             expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
 | |
|             // 100 eth to fill the first order + 208 eth for fees
 | |
|             const expectedWorstTakerAssetAmountForMakerAsset = new BigNumber(100);
 | |
|             const expectedWorstTotalTakerAssetAmount = expectedWorstTakerAssetAmountForMakerAsset;
 | |
|             expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTakerAssetAmountForMakerAsset,
 | |
|             );
 | |
|             expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
 | |
|             expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
 | |
|                 expectedWorstTotalTakerAssetAmount,
 | |
|             );
 | |
|         });
 | |
|     });
 | |
| });
 |