aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts15
-rw-r--r--packages/asset-buyer/src/types.ts27
-rw-r--r--packages/asset-buyer/src/utils/assert.ts11
-rw-r--r--packages/asset-buyer/src/utils/buy_quote_calculator.ts26
-rw-r--r--packages/asset-buyer/test/buy_quote_calculator_test.ts34
5 files changed, 73 insertions, 40 deletions
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<BuyQuoteExecutionOpts> = {},
): Promise<string> {
- 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);
});