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, ); }); }); });