From 57b4396193b731d97f73f4e3648a8dbdc1f589ac Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 13:23:48 -0700 Subject: Add buy_quote_calculator_test --- .../asset-buyer/src/utils/buy_quote_calculator.ts | 147 ++++++++++++++++----- .../asset-buyer/test/buy_quote_calculator_test.ts | 130 ++++++++++++++++++ 2 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 packages/asset-buyer/test/buy_quote_calculator_test.ts (limited to 'packages') diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index b706ea143..53f2228e9 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -1,4 +1,4 @@ -import { marketUtils } from '@0xproject/order-utils'; +import { marketUtils, rateUtils } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -21,6 +21,7 @@ export const buyQuoteCalculator = { const feeOrders = feeOrdersAndFillableAmounts.orders; const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts; const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round(); + // find the orders that cover the desired assetBuyAmount (with slippage) const { resultOrders, remainingFillAmount, @@ -29,9 +30,11 @@ export const buyQuoteCalculator = { remainingFillableMakerAssetAmounts, slippageBufferAmount, }); + // if we do not have enough orders to cover the desired assetBuyAmount, throw if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { throw new Error(AssetBuyerError.InsufficientAssetLiquidity); } + // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) // TODO(bmillman): optimization // update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to // finding order that cover all fees, this will help with estimating ETH and minimizing gas usage @@ -40,49 +43,131 @@ export const buyQuoteCalculator = { remainingFeeAmount, feeOrdersRemainingFillableMakerAssetAmounts, } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(resultOrders, feeOrders, { - remainingFillableMakerAssetAmounts, + remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, remainingFillableFeeAmounts, }); + // if we do not have enough feeOrders to cover the fees, throw if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) { throw new Error(AssetBuyerError.InsufficientZrxLiquidity); } + // assetData information for the result const assetData = orders[0].makerAssetData; - - // calculate minRate and maxRate by calculating min and max eth usage and then dividing into - // assetBuyAmount to get assetData / WETH, needs to take into account feePercentage as well - // minEthAmount = (sum(takerAssetAmount[i]) until sum(makerAssetAmount[i]) >= assetBuyAmount ) * (1 + feePercentage) - // maxEthAmount = (sum(takerAssetAmount[i]) until i == orders.length) * (1 + feePercentage) - const allOrders = _.concat(resultOrders, resultFeeOrders); - const allRemainingAmounts = _.concat( - ordersRemainingFillableMakerAssetAmounts, - feeOrdersRemainingFillableMakerAssetAmounts, + // compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount + const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = { + orders: resultOrders, + remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, + }; + const trimmedFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts = { + orders: resultFeeOrders, + remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts, + }; + const minRate = calculateRate( + trimmedOrdersAndFillableAmounts, + trimmedFeeOrdersAndFillableAmounts, + assetBuyAmount, + feePercentage, + ); + // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate + const maxRate = calculateRate( + reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts), + reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), + assetBuyAmount, + feePercentage, ); - let minEthAmount = constants.ZERO_AMOUNT; - let maxEthAmount = constants.ZERO_AMOUNT; - let cumulativeMakerAmount = constants.ZERO_AMOUNT; - _.forEach(allOrders, (order, index) => { - const remainingFillableMakerAssetAmount = allRemainingAmounts[index]; - const claimableTakerAssetAmount = orderUtils.calculateRemainingTakerAssetAmount( - order, - remainingFillableMakerAssetAmount, - ); - // taker asset is always assumed to be WETH - maxEthAmount = maxEthAmount.plus(claimableTakerAssetAmount); - if (cumulativeMakerAmount.lessThan(assetBuyAmount)) { - minEthAmount = minEthAmount.plus(claimableTakerAssetAmount); - } - cumulativeMakerAmount = cumulativeMakerAmount.plus(remainingFillableMakerAssetAmount); - }); - const feeAdjustedMinRate = minEthAmount.mul(feePercentage + 1).div(assetBuyAmount); - const feeAdjustedMaxRate = minEthAmount.mul(feePercentage + 1).div(assetBuyAmount); return { assetData, orders: resultOrders, feeOrders: resultFeeOrders, - minRate: feeAdjustedMinRate, - maxRate: feeAdjustedMaxRate, + minRate, + maxRate, assetBuyAmount, feePercentage, }; }, }; + +function calculateRate( + ordersAndFillableAmounts: OrdersAndFillableAmounts, + feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, + assetBuyAmount: BigNumber, + feePercentage: number, +): BigNumber { + // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right (best rate to worst rate) + const [minEthAmountToBuyAsset, minZrxAmountToBuyAsset] = findEthAndZrxAmountNeededToBuyAsset( + ordersAndFillableAmounts, + assetBuyAmount, + ); + // find the total eth needed to buy fees + const minEthAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, minZrxAmountToBuyAsset); + const finalMinEthAmount = minEthAmountToBuyAsset.plus(minEthAmountToBuyFees).mul(feePercentage + 1); + // divide into the assetBuyAmount in order to find rate of makerAsset / WETH + const result = assetBuyAmount.div(finalMinEthAmount); + return result; +} + +// given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties +function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts { + const ordersCopy = _.clone(ordersAndFillableAmounts.orders); + const remainingFillableMakerAssetAmountsCopy = _.clone(ordersAndFillableAmounts.remainingFillableMakerAssetAmounts); + return { + orders: ordersCopy.reverse(), + remainingFillableMakerAssetAmounts: remainingFillableMakerAssetAmountsCopy.reverse(), + }; +} + +function findEthAmountNeededToBuyFees( + feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, + feeAmount: BigNumber, +): BigNumber { + const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts; + const result = _.reduce( + orders, + (acc, order, index) => { + const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; + const amountToFill = BigNumber.min(acc.remainingFeeAmount, remainingFillableMakerAssetAmount); + const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfFeeOrder(order); + const ethAmountForThisOrder = feeAdjustedRate.mul(amountToFill); + return { + ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), + remainingFeeAmount: BigNumber.max(constants.ZERO_AMOUNT, acc.remainingFeeAmount.minus(amountToFill)), + }; + }, + { + ethAmount: constants.ZERO_AMOUNT, + remainingFeeAmount: feeAmount, + }, + ); + return result.ethAmount; +} + +function findEthAndZrxAmountNeededToBuyAsset( + ordersAndFillableAmounts: OrdersAndFillableAmounts, + assetBuyAmount: BigNumber, +): [BigNumber, BigNumber] { + const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts; + const result = _.reduce( + orders, + (acc, order, index) => { + const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; + const amountToFill = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); + const ethAmountForThisOrder = amountToFill + .mul(order.takerAssetAmount) + .dividedToIntegerBy(order.makerAssetAmount); + const zrxAmountForThisOrder = amountToFill.mul(order.takerFee).dividedToIntegerBy(order.makerAssetAmount); + return { + ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), + zrxAmount: acc.ethAmount.plus(zrxAmountForThisOrder), + remainingAssetBuyAmount: BigNumber.max( + constants.ZERO_AMOUNT, + acc.remainingAssetBuyAmount.minus(amountToFill), + ), + }; + }, + { + ethAmount: constants.ZERO_AMOUNT, + zrxAmount: constants.ZERO_AMOUNT, + remainingAssetBuyAmount: assetBuyAmount, + }, + ); + return [result.ethAmount, result.zrxAmount]; +} diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts new file mode 100644 index 000000000..2cad1ab05 --- /dev/null +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -0,0 +1,130 @@ +import { orderFactory } from '@0xproject/order-utils/lib/src/order_factory'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { AssetBuyerError, OrdersAndFillableAmounts } from '../src/types'; +import { buyQuoteCalculator } from '../src/utils/buy_quote_calculator'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; +const NULL_BYTES = '0x'; + +// tslint:disable:custom-no-magic-numbers +describe('buyQuoteCalculator', () => { + describe('#calculate', () => { + let ordersAndFillableAmounts: OrdersAndFillableAmounts; + let feeOrdersAndFillableAmounts: 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) + const firstOrder = orderFactory.createOrder( + NULL_ADDRESS, + new BigNumber(400), + NULL_BYTES, + new BigNumber(100), + NULL_BYTES, + NULL_ADDRESS, + { + takerFee: new BigNumber(200), + }, + ); + const firstRemainingFillAmount = new BigNumber(200); + const secondOrder = orderFactory.createOrder( + NULL_ADDRESS, + new BigNumber(200), + NULL_BYTES, + new BigNumber(100), + NULL_BYTES, + NULL_ADDRESS, + { + takerFee: new BigNumber(100), + }, + ); + const secondRemainingFillAmount = secondOrder.makerAssetAmount; + const signedOrders = _.map([firstOrder, secondOrder], order => { + return { + ...order, + signature: NULL_BYTES, + }; + }); + ordersAndFillableAmounts = { + orders: signedOrders, + remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount], + }; + const feeOrder = orderFactory.createOrder( + NULL_ADDRESS, + new BigNumber(100), + NULL_BYTES, + new BigNumber(100), + NULL_BYTES, + NULL_ADDRESS, + ); + const signedFeeOrder = { + ...feeOrder, + signature: NULL_BYTES, + }; + feeOrdersAndFillableAmounts = { + orders: [signedFeeOrder], + remainingFillableMakerAssetAmounts: [signedFeeOrder.makerAssetAmount], + }; + }); + it('should throw if not enough maker asset liquidity', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + expect(() => + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + feeOrdersAndFillableAmounts, + new BigNumber(500), + 0, + 0, + ), + ).to.throw(AssetBuyerError.InsufficientAssetLiquidity); + }); + 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(() => + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + feeOrdersAndFillableAmounts, + new BigNumber(300), + 0, + 0, + ), + ).to.throw(AssetBuyerError.InsufficientZrxLiquidity); + }); + it('calculates a correct buyQuote', () => { + // 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 feePercentage = 0.02; + const slippagePercentage = 0; + const buyQuote = buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + feeOrdersAndFillableAmounts, + assetBuyAmount, + feePercentage, + slippagePercentage, + ); + // test if orders are correct + expect(buyQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]); + expect(buyQuote.feeOrders).to.deep.equal([feeOrdersAndFillableAmounts.orders[0]]); + // test if rates are correct + const expectedMinEthToFill = new BigNumber(150); + const expectedMinRate = assetBuyAmount.div(expectedMinEthToFill.mul(feePercentage + 1)); + expect(buyQuote.minRate).to.bignumber.equal(expectedMinRate); + // because we have no slippage protection, minRate is equal to maxRate + expect(buyQuote.maxRate).to.bignumber.equal(expectedMinRate); + // test if feePercentage gets passed through + expect(buyQuote.feePercentage).to.equal(feePercentage); + }); + }); +}); -- cgit From 260db053fe45ae8d5973207b43d762d905b28cd8 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 14:50:59 -0700 Subject: Add additional test for slippage --- .../asset-buyer/src/utils/buy_quote_calculator.ts | 12 ++-- .../asset-buyer/test/buy_quote_calculator_test.ts | 76 ++++++++++++++++++---- 2 files changed, 70 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 53f2228e9..5a531e000 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -92,16 +92,16 @@ function calculateRate( assetBuyAmount: BigNumber, feePercentage: number, ): BigNumber { - // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right (best rate to worst rate) - const [minEthAmountToBuyAsset, minZrxAmountToBuyAsset] = findEthAndZrxAmountNeededToBuyAsset( + // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right + const [ethAmountToBuyAsset, zrxAmountToBuyAsset] = findEthAndZrxAmountNeededToBuyAsset( ordersAndFillableAmounts, assetBuyAmount, ); // find the total eth needed to buy fees - const minEthAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, minZrxAmountToBuyAsset); - const finalMinEthAmount = minEthAmountToBuyAsset.plus(minEthAmountToBuyFees).mul(feePercentage + 1); + const ethAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); + const ethAmount = ethAmountToBuyAsset.plus(ethAmountToBuyFees).mul(feePercentage + 1); // divide into the assetBuyAmount in order to find rate of makerAsset / WETH - const result = assetBuyAmount.div(finalMinEthAmount); + const result = assetBuyAmount.div(ethAmount); return result; } @@ -156,7 +156,7 @@ function findEthAndZrxAmountNeededToBuyAsset( const zrxAmountForThisOrder = amountToFill.mul(order.takerFee).dividedToIntegerBy(order.makerAssetAmount); return { ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), - zrxAmount: acc.ethAmount.plus(zrxAmountForThisOrder), + zrxAmount: acc.zrxAmount.plus(zrxAmountForThisOrder), remainingAssetBuyAmount: BigNumber.max( constants.ZERO_AMOUNT, acc.remainingAssetBuyAmount.minus(amountToFill), diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 2cad1ab05..37a429531 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -19,7 +19,8 @@ const NULL_BYTES = '0x'; describe('buyQuoteCalculator', () => { describe('#calculate', () => { let ordersAndFillableAmounts: OrdersAndFillableAmounts; - let feeOrdersAndFillableAmounts: 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) @@ -60,7 +61,7 @@ describe('buyQuoteCalculator', () => { orders: signedOrders, remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount], }; - const feeOrder = orderFactory.createOrder( + const smallFeeOrder = orderFactory.createOrder( NULL_ADDRESS, new BigNumber(100), NULL_BYTES, @@ -68,13 +69,32 @@ describe('buyQuoteCalculator', () => { NULL_BYTES, NULL_ADDRESS, ); - const signedFeeOrder = { - ...feeOrder, + const signedSmallFeeOrder = { + ...smallFeeOrder, signature: NULL_BYTES, }; - feeOrdersAndFillableAmounts = { - orders: [signedFeeOrder], - remainingFillableMakerAssetAmounts: [signedFeeOrder.makerAssetAmount], + smallFeeOrderAndFillableAmount = { + orders: [signedSmallFeeOrder], + remainingFillableMakerAssetAmounts: [signedSmallFeeOrder.makerAssetAmount], + }; + const largeFeeOrder = orderFactory.createOrder( + NULL_ADDRESS, + new BigNumber(100), + NULL_BYTES, + new BigNumber(200), + NULL_BYTES, + NULL_ADDRESS, + ); + const signedLargeFeeOrder = { + ...largeFeeOrder, + signature: NULL_BYTES, + }; + allFeeOrdersAndFillableAmounts = { + orders: [signedSmallFeeOrder, signedLargeFeeOrder], + remainingFillableMakerAssetAmounts: [ + signedSmallFeeOrder.makerAssetAmount, + largeFeeOrder.makerAssetAmount, + ], }; }); it('should throw if not enough maker asset liquidity', () => { @@ -82,7 +102,7 @@ describe('buyQuoteCalculator', () => { expect(() => buyQuoteCalculator.calculate( ordersAndFillableAmounts, - feeOrdersAndFillableAmounts, + smallFeeOrderAndFillableAmount, new BigNumber(500), 0, 0, @@ -94,14 +114,14 @@ describe('buyQuoteCalculator', () => { expect(() => buyQuoteCalculator.calculate( ordersAndFillableAmounts, - feeOrdersAndFillableAmounts, + smallFeeOrderAndFillableAmount, new BigNumber(300), 0, 0, ), ).to.throw(AssetBuyerError.InsufficientZrxLiquidity); }); - it('calculates a correct buyQuote', () => { + it('calculates a correct buyQuote 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); @@ -109,15 +129,16 @@ describe('buyQuoteCalculator', () => { const slippagePercentage = 0; const buyQuote = buyQuoteCalculator.calculate( ordersAndFillableAmounts, - feeOrdersAndFillableAmounts, + smallFeeOrderAndFillableAmount, assetBuyAmount, feePercentage, slippagePercentage, ); // test if orders are correct expect(buyQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]); - expect(buyQuote.feeOrders).to.deep.equal([feeOrdersAndFillableAmounts.orders[0]]); + expect(buyQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]); // test if rates are correct + // 50 eth to fill the first order + 100 eth for fees const expectedMinEthToFill = new BigNumber(150); const expectedMinRate = assetBuyAmount.div(expectedMinEthToFill.mul(feePercentage + 1)); expect(buyQuote.minRate).to.bignumber.equal(expectedMinRate); @@ -126,5 +147,36 @@ describe('buyQuoteCalculator', () => { // test if feePercentage gets passed through expect(buyQuote.feePercentage).to.equal(feePercentage); }); + it('calculates a correct buyQuote with 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 feePercentage = 0.02; + const slippagePercentage = 0.5; + const buyQuote = buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + allFeeOrdersAndFillableAmounts, + assetBuyAmount, + feePercentage, + slippagePercentage, + ); + // test if orders are correct + expect(buyQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders); + expect(buyQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders); + // test if rates are correct + // 50 eth to fill the first order + 100 eth for fees + const expectedMinEthToFill = new BigNumber(150); + const expectedMinRate = assetBuyAmount.div(expectedMinEthToFill.mul(feePercentage + 1)); + expect(buyQuote.minRate).to.bignumber.equal(expectedMinRate); + // 100 eth to fill the first order + 200 eth for fees + const expectedMaxEthToFill = new BigNumber(300); + const expectedMaxRate = assetBuyAmount.div(expectedMaxEthToFill.mul(feePercentage + 1)); + expect(buyQuote.maxRate).to.bignumber.equal(expectedMaxRate); + // test if feePercentage gets passed through + expect(buyQuote.feePercentage).to.equal(feePercentage); + }); }); }); -- cgit From 93736c15675973f053e7b9b072c1d029b9dd385d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 14:52:44 -0700 Subject: Fix linter --- packages/asset-buyer/src/utils/buy_quote_calculator.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 5a531e000..9ccaa7933 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -5,8 +5,6 @@ import * as _ from 'lodash'; import { constants } from '../constants'; import { AssetBuyerError, BuyQuote, OrdersAndFillableAmounts } from '../types'; -import { orderUtils } from './order_utils'; - // Calculates a buy quote for orders that have WETH as the takerAsset export const buyQuoteCalculator = { calculate( -- cgit From 10f54893ef483d40de4d72f752ae921c62c9ac62 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 14:59:06 -0700 Subject: Update CHANGELOG --- packages/asset-buyer/CHANGELOG.json | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'packages') diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 8c2c7a8d2..df67059cb 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Expand AssetBuyer to work with multiple assets at once", "pr": 1086 + }, + { + "note": "Fix minRate and maxRate calculation", + "pr": 1113 } ] }, -- cgit From 250a9a480940ae4fca48109aae97ee0323d57a52 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 22:54:32 -0700 Subject: Add comments about buy quote calculation --- packages/asset-buyer/src/utils/buy_quote_calculator.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages') diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 9ccaa7933..78666356c 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -148,9 +148,11 @@ function findEthAndZrxAmountNeededToBuyAsset( (acc, order, index) => { const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; const amountToFill = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); + // find the amount of eth required to fill amountToFill (amountToFill / makerAssetAmount) * takerAssetAmount const ethAmountForThisOrder = amountToFill .mul(order.takerAssetAmount) .dividedToIntegerBy(order.makerAssetAmount); + // find the amount of zrx required to fill fees for amountToFill (amountToFill / makerAssetAmount) * takerFee const zrxAmountForThisOrder = amountToFill.mul(order.takerFee).dividedToIntegerBy(order.makerAssetAmount); return { ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), -- cgit From 059162a90a3d26e5fdfefd8553bb1f721a3116fc Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 23:14:55 -0700 Subject: Add additional order factory methods and refactor test to use them --- .../asset-buyer/test/buy_quote_calculator_test.ts | 83 ++++++---------------- packages/order-utils/src/order_factory.ts | 39 +++++++++- 2 files changed, 61 insertions(+), 61 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 37a429531..3bd0f8d4e 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -27,74 +27,37 @@ describe('buyQuoteCalculator', () => { // 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) - const firstOrder = orderFactory.createOrder( - NULL_ADDRESS, - new BigNumber(400), - NULL_BYTES, - new BigNumber(100), - NULL_BYTES, - NULL_ADDRESS, - { - takerFee: new BigNumber(200), - }, - ); + const firstOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(400), + takerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(200), + }); const firstRemainingFillAmount = new BigNumber(200); - const secondOrder = orderFactory.createOrder( - NULL_ADDRESS, - new BigNumber(200), - NULL_BYTES, - new BigNumber(100), - NULL_BYTES, - NULL_ADDRESS, - { - takerFee: new BigNumber(100), - }, - ); - const secondRemainingFillAmount = secondOrder.makerAssetAmount; - const signedOrders = _.map([firstOrder, secondOrder], order => { - return { - ...order, - signature: NULL_BYTES, - }; + const secondOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(200), + takerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(100), }); + const secondRemainingFillAmount = secondOrder.makerAssetAmount; ordersAndFillableAmounts = { - orders: signedOrders, + orders: [firstOrder, secondOrder], remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount], }; - const smallFeeOrder = orderFactory.createOrder( - NULL_ADDRESS, - new BigNumber(100), - NULL_BYTES, - new BigNumber(100), - NULL_BYTES, - NULL_ADDRESS, - ); - const signedSmallFeeOrder = { - ...smallFeeOrder, - signature: NULL_BYTES, - }; + const smallFeeOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(100), + }); smallFeeOrderAndFillableAmount = { - orders: [signedSmallFeeOrder], - remainingFillableMakerAssetAmounts: [signedSmallFeeOrder.makerAssetAmount], - }; - const largeFeeOrder = orderFactory.createOrder( - NULL_ADDRESS, - new BigNumber(100), - NULL_BYTES, - new BigNumber(200), - NULL_BYTES, - NULL_ADDRESS, - ); - const signedLargeFeeOrder = { - ...largeFeeOrder, - signature: NULL_BYTES, + orders: [smallFeeOrder], + remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount], }; + const largeFeeOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(200), + }); allFeeOrdersAndFillableAmounts = { - orders: [signedSmallFeeOrder, signedLargeFeeOrder], - remainingFillableMakerAssetAmounts: [ - signedSmallFeeOrder.makerAssetAmount, - largeFeeOrder.makerAssetAmount, - ], + orders: [smallFeeOrder, largeFeeOrder], + remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount, largeFeeOrder.makerAssetAmount], }; }); it('should throw if not enough maker asset liquidity', () => { diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 46a69ae4d..b1292903a 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -8,8 +8,21 @@ import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; import { signatureUtils } from './signature_utils'; import { CreateOrderOpts } from './types'; - export const orderFactory = { + createOrderFromPartial(partialOrder: Partial): Order { + const defaultOrder = generateEmptyOrder(); + return { + ...defaultOrder, + ...partialOrder, + }; + }, + createSignedOrderFromPartial(partialSignedOrder: Partial): SignedOrder { + const defaultOrder = generateEmptySignedOrder(); + return { + ...defaultOrder, + ...partialSignedOrder, + }; + }, createOrder( makerAddress: string, makerAssetAmount: BigNumber, @@ -69,6 +82,30 @@ export const orderFactory = { }, }; +function generateEmptySignedOrder(): SignedOrder { + return { + ...generateEmptyOrder(), + signature: constants.NULL_BYTES, + }; +} +function generateEmptyOrder(): Order { + return { + senderAddress: constants.NULL_ADDRESS, + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + makerAssetAmount: constants.ZERO_AMOUNT, + takerAssetAmount: constants.ZERO_AMOUNT, + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + salt: generatePseudoRandomSalt(), + exchangeAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + expirationTimeSeconds: constants.INFINITE_TIMESTAMP_SEC, + }; +} + function generateDefaultCreateOrderOpts(): { takerAddress: string; senderAddress: string; -- cgit From 24e0fbd7b9debcfd193f9174f0254d7d5ae22c12 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 23:22:38 -0700 Subject: Add fee order with a takerFee --- packages/asset-buyer/test/buy_quote_calculator_test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 3bd0f8d4e..0108dfcc6 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -52,12 +52,16 @@ describe('buyQuoteCalculator', () => { remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount], }; const largeFeeOrder = orderFactory.createSignedOrderFromPartial({ - makerAssetAmount: new BigNumber(100), + makerAssetAmount: new BigNumber(110), takerAssetAmount: new BigNumber(200), + takerFee: new BigNumber(10), }); allFeeOrdersAndFillableAmounts = { orders: [smallFeeOrder, largeFeeOrder], - remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount, largeFeeOrder.makerAssetAmount], + remainingFillableMakerAssetAmounts: [ + smallFeeOrder.makerAssetAmount, + largeFeeOrder.makerAssetAmount.minus(largeFeeOrder.takerFee), + ], }; }); it('should throw if not enough maker asset liquidity', () => { -- cgit From a7a007435cfefc8146246c0db66efdee05c809b8 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 23:23:53 -0700 Subject: Remove unused constants --- packages/asset-buyer/test/buy_quote_calculator_test.ts | 3 --- 1 file changed, 3 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 0108dfcc6..667dec051 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -12,9 +12,6 @@ import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); const expect = chai.expect; -const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; -const NULL_BYTES = '0x'; - // tslint:disable:custom-no-magic-numbers describe('buyQuoteCalculator', () => { describe('#calculate', () => { -- cgit From 4394036e341e9755a57a29680f5b14f3c92fef9c Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 3 Oct 2018 23:46:07 -0700 Subject: Add missing default options --- packages/asset-buyer/src/asset_buyer.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 0bb757f52..a56bc01d5 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -123,7 +123,7 @@ export class AssetBuyer { public async getBuyQuoteAsync( assetData: string, assetBuyAmount: BigNumber, - options: Partial, + options: Partial = {}, ): Promise { const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = { ...constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, @@ -164,7 +164,7 @@ export class AssetBuyer { public async getBuyQuoteForERC20TokenAddressAsync( tokenAddress: string, assetBuyAmount: BigNumber, - options: Partial, + options: Partial = {}, ): Promise { assert.isETHAddressHex('tokenAddress', tokenAddress); assert.isBigNumber('assetBuyAmount', assetBuyAmount); @@ -179,7 +179,10 @@ export class AssetBuyer { * * @return A promise of the txHash. */ - public async executeBuyQuoteAsync(buyQuote: BuyQuote, options: Partial): Promise { + public async executeBuyQuoteAsync( + buyQuote: BuyQuote, + options: Partial = {}, + ): Promise { const { rate, takerAddress, feeRecipient } = { ...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, ...options, -- cgit From 63d97f4c8837dcf8d17afc494dcd8b1ba10fee16 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 4 Oct 2018 14:33:07 -0700 Subject: Update BuyQuote interface --- packages/asset-buyer/src/asset_buyer.ts | 15 ++++------ packages/asset-buyer/src/types.ts | 27 ++++++++++++----- packages/asset-buyer/src/utils/assert.ts | 11 +++++-- .../asset-buyer/src/utils/buy_quote_calculator.ts | 26 ++++++++++------- .../asset-buyer/test/buy_quote_calculator_test.ts | 34 +++++++++++++++------- 5 files changed, 73 insertions(+), 40 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index a56bc01d5..7ec39e012 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -183,19 +183,19 @@ export class AssetBuyer { buyQuote: BuyQuote, options: Partial = {}, ): Promise { - const { rate, takerAddress, feeRecipient } = { + const { ethAmount, takerAddress, feeRecipient } = { ...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, ...options, }; assert.isValidBuyQuote('buyQuote', buyQuote); - if (!_.isUndefined(rate)) { - assert.isBigNumber('rate', rate); + if (!_.isUndefined(ethAmount)) { + assert.isBigNumber('ethAmount', ethAmount); } if (!_.isUndefined(takerAddress)) { assert.isETHAddressHex('takerAddress', takerAddress); } assert.isETHAddressHex('feeRecipient', feeRecipient); - const { orders, feeOrders, feePercentage, assetBuyAmount, maxRate } = buyQuote; + const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote; // if no takerAddress is provided, try to get one from the provider let finalTakerAddress; if (!_.isUndefined(takerAddress)) { @@ -210,15 +210,12 @@ export class AssetBuyer { throw new Error(AssetBuyerError.NoAddressAvailable); } } - // if no rate is provided, default to the maxRate from buyQuote - const desiredRate = rate || maxRate; - // calculate how much eth is required to buy assetBuyAmount at the desired rate - const ethAmount = assetBuyAmount.dividedToIntegerBy(desiredRate); + // if no ethAmount is provided, default to the worst ethAmount from buyQuote const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( orders, assetBuyAmount, finalTakerAddress, - ethAmount, + ethAmount || worstCaseQuoteInfo.totalEthAmount, feeOrders, feePercentage, feeRecipient, diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index 8d3dcbfe6..b96795bb6 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -35,21 +35,32 @@ export interface OrderProvider { /** * assetData: String that represents a specific asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). + * assetBuyAmount: The amount of asset to buy. * orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage. * feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above. - * minRate: Min rate that needs to be paid in order to execute the buy. - * maxRate: Max rate that can be paid in order to execute the buy. - * assetBuyAmount: The amount of asset to buy. * feePercentage: Optional affiliate fee percentage used to calculate the eth amounts above. + * bestCaseQuoteInfo: Info about the best case price for the asset. + * worstCaseQuoteInfo: Info about the worst case price for the asset. */ export interface BuyQuote { assetData: string; + assetBuyAmount: BigNumber; orders: SignedOrder[]; feeOrders: SignedOrder[]; - minRate: BigNumber; - maxRate: BigNumber; - assetBuyAmount: BigNumber; feePercentage?: number; + bestCaseQuoteInfo: BuyQuoteInfo; + worstCaseQuoteInfo: BuyQuoteInfo; +} + +/** + * ethPerAssetPrice: The price of one unit of the desired asset in ETH + * feeEthAmount: The amount of eth required to pay the affiliate fee. + * totalEthAmount: the total amount of eth required to complete the buy. (Filling orders, feeOrders, and paying affiliate fee) + */ +export interface BuyQuoteInfo { + ethPerAssetPrice: BigNumber; + feeEthAmount: BigNumber; + totalEthAmount: BigNumber; } /** @@ -64,12 +75,12 @@ export interface BuyQuoteRequestOpts { } /** - * rate: The desired rate to execute the buy at. Affects the amount of ETH sent with the transaction, defaults to buyQuote.maxRate. + * ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount. * takerAddress: The address to perform the buy. Defaults to the first available address from the provider. * feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000). */ export interface BuyQuoteExecutionOpts { - rate?: BigNumber; + ethAmount?: BigNumber; takerAddress?: string; feeRecipient: string; } diff --git a/packages/asset-buyer/src/utils/assert.ts b/packages/asset-buyer/src/utils/assert.ts index 04f425237..d43b71fee 100644 --- a/packages/asset-buyer/src/utils/assert.ts +++ b/packages/asset-buyer/src/utils/assert.ts @@ -3,7 +3,7 @@ import { schemas } from '@0xproject/json-schemas'; import { SignedOrder } from '@0xproject/types'; import * as _ from 'lodash'; -import { BuyQuote, OrderProvider, OrderProviderRequest } from '../types'; +import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types'; export const assert = { ...sharedAssert, @@ -11,13 +11,18 @@ export const assert = { sharedAssert.isHexString(`${variableName}.assetData`, buyQuote.assetData); sharedAssert.doesConformToSchema(`${variableName}.orders`, buyQuote.orders, schemas.signedOrdersSchema); sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, buyQuote.feeOrders, schemas.signedOrdersSchema); - sharedAssert.isBigNumber(`${variableName}.minRate`, buyQuote.minRate); - sharedAssert.isBigNumber(`${variableName}.maxRate`, buyQuote.maxRate); + assert.isValidBuyQuoteInfo(`${variableName}.bestCaseQuoteInfo`, buyQuote.bestCaseQuoteInfo); + assert.isValidBuyQuoteInfo(`${variableName}.worstCaseQuoteInfo`, buyQuote.worstCaseQuoteInfo); sharedAssert.isBigNumber(`${variableName}.assetBuyAmount`, buyQuote.assetBuyAmount); if (!_.isUndefined(buyQuote.feePercentage)) { sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage); } }, + isValidBuyQuoteInfo(variableName: string, buyQuoteInfo: BuyQuoteInfo): void { + sharedAssert.isBigNumber(`${variableName}.ethPerAssetPrice`, buyQuoteInfo.ethPerAssetPrice); + sharedAssert.isBigNumber(`${variableName}.feeEthAmount`, buyQuoteInfo.feeEthAmount); + sharedAssert.isBigNumber(`${variableName}.totalEthAmount`, buyQuoteInfo.totalEthAmount); + }, isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void { sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync); }, diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 78666356c..cb0fd128c 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -3,7 +3,7 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import { constants } from '../constants'; -import { AssetBuyerError, BuyQuote, OrdersAndFillableAmounts } from '../types'; +import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; // Calculates a buy quote for orders that have WETH as the takerAsset export const buyQuoteCalculator = { @@ -59,37 +59,38 @@ export const buyQuoteCalculator = { orders: resultFeeOrders, remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts, }; - const minRate = calculateRate( + const bestCaseQuoteInfo = calculateQuoteInfo( trimmedOrdersAndFillableAmounts, trimmedFeeOrdersAndFillableAmounts, assetBuyAmount, feePercentage, ); // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate - const maxRate = calculateRate( + const worstCaseQuoteInfo = calculateQuoteInfo( reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts), reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), assetBuyAmount, feePercentage, ); + return { assetData, orders: resultOrders, feeOrders: resultFeeOrders, - minRate, - maxRate, + bestCaseQuoteInfo, + worstCaseQuoteInfo, assetBuyAmount, feePercentage, }; }, }; -function calculateRate( +function calculateQuoteInfo( ordersAndFillableAmounts: OrdersAndFillableAmounts, feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, assetBuyAmount: BigNumber, feePercentage: number, -): BigNumber { +): BuyQuoteInfo { // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right const [ethAmountToBuyAsset, zrxAmountToBuyAsset] = findEthAndZrxAmountNeededToBuyAsset( ordersAndFillableAmounts, @@ -97,10 +98,15 @@ function calculateRate( ); // find the total eth needed to buy fees const ethAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); - const ethAmount = ethAmountToBuyAsset.plus(ethAmountToBuyFees).mul(feePercentage + 1); + const ethAmountBeforeAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyFees); + const totalEthAmount = ethAmountBeforeAffiliateFee.mul(feePercentage + 1); // divide into the assetBuyAmount in order to find rate of makerAsset / WETH - const result = assetBuyAmount.div(ethAmount); - return result; + const ethPerAssetPrice = ethAmountBeforeAffiliateFee.div(assetBuyAmount); + return { + totalEthAmount, + feeEthAmount: totalEthAmount.minus(ethAmountBeforeAffiliateFee), + ethPerAssetPrice, + }; } // given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 667dec051..b987b45a8 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -103,11 +103,17 @@ describe('buyQuoteCalculator', () => { expect(buyQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]); // test if rates are correct // 50 eth to fill the first order + 100 eth for fees - const expectedMinEthToFill = new BigNumber(150); - const expectedMinRate = assetBuyAmount.div(expectedMinEthToFill.mul(feePercentage + 1)); - expect(buyQuote.minRate).to.bignumber.equal(expectedMinRate); + const expectedFillEthAmount = new BigNumber(150); + const expectedTotalEthAmount = expectedFillEthAmount.mul(feePercentage + 1); + const expectedFeeEthAmount = expectedTotalEthAmount.minus(expectedFillEthAmount); + const expectedEthPerAssetPrice = expectedFillEthAmount.div(assetBuyAmount); + expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); + expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); + expect(buyQuote.bestCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedEthPerAssetPrice); // because we have no slippage protection, minRate is equal to maxRate - expect(buyQuote.maxRate).to.bignumber.equal(expectedMinRate); + expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); + expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); + expect(buyQuote.worstCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedEthPerAssetPrice); // test if feePercentage gets passed through expect(buyQuote.feePercentage).to.equal(feePercentage); }); @@ -132,13 +138,21 @@ describe('buyQuoteCalculator', () => { expect(buyQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders); // test if rates are correct // 50 eth to fill the first order + 100 eth for fees - const expectedMinEthToFill = new BigNumber(150); - const expectedMinRate = assetBuyAmount.div(expectedMinEthToFill.mul(feePercentage + 1)); - expect(buyQuote.minRate).to.bignumber.equal(expectedMinRate); + const expectedFillEthAmount = new BigNumber(150); + const expectedTotalEthAmount = expectedFillEthAmount.mul(feePercentage + 1); + const expectedFeeEthAmount = expectedTotalEthAmount.minus(expectedFillEthAmount); + const expectedEthPerAssetPrice = expectedFillEthAmount.div(assetBuyAmount); + expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); + expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); + expect(buyQuote.bestCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedEthPerAssetPrice); // 100 eth to fill the first order + 200 eth for fees - const expectedMaxEthToFill = new BigNumber(300); - const expectedMaxRate = assetBuyAmount.div(expectedMaxEthToFill.mul(feePercentage + 1)); - expect(buyQuote.maxRate).to.bignumber.equal(expectedMaxRate); + const expectedWorstFillEthAmount = new BigNumber(300); + const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.mul(feePercentage + 1); + const expectedWorstFeeEthAmount = expectedWorstTotalEthAmount.minus(expectedWorstFillEthAmount); + const expectedWorstEthPerAssetPrice = expectedWorstFillEthAmount.div(assetBuyAmount); + expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedWorstFeeEthAmount); + expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedWorstTotalEthAmount); + expect(buyQuote.worstCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedWorstEthPerAssetPrice); // test if feePercentage gets passed through expect(buyQuote.feePercentage).to.equal(feePercentage); }); -- cgit