Move order_utils from asset-buyer to order-utils package

This commit is contained in:
Jacob Evans
2019-03-21 17:17:29 +01:00
parent a5f06c577d
commit fa67997424
9 changed files with 144 additions and 97 deletions

View File

@@ -1,4 +1,13 @@
[
{
"version": "6.1.0",
"changes": [
{
"note": "Moves order_utils into `@0x/order-utils` package as `orderCalculationUtils`",
"pr": 1714
}
]
},
{
"timestamp": 1553183790,
"version": "6.0.5",

View File

@@ -1,4 +1,5 @@
import { HttpClient } from '@0x/connect';
import { orderCalculationUtils } from '@0x/order-utils';
import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types';
import * as _ from 'lodash';
@@ -10,7 +11,6 @@ import {
SignedOrderWithRemainingFillableMakerAssetAmount,
} from '../types';
import { assert } from '../utils/assert';
import { orderUtils } from '../utils/order_utils';
export class StandardRelayerAPIOrderProvider implements OrderProvider {
public readonly apiUrl: string;
@@ -31,7 +31,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider {
'remainingTakerAssetAmount',
order.takerAssetAmount,
);
const remainingFillableMakerAssetAmount = orderUtils.getRemainingMakerAmount(
const remainingFillableMakerAssetAmount = orderCalculationUtils.getMakerFillAmount(
order,
remainingFillableTakerAssetAmount,
);

View File

@@ -1,4 +1,4 @@
import { marketUtils, SignedOrder } from '@0x/order-utils';
import { marketUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@@ -6,8 +6,6 @@ import { constants } from '../constants';
import { InsufficientAssetLiquidityError } from '../errors';
import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types';
import { orderUtils } from './order_utils';
// Calculates a buy quote for orders that have WETH as the takerAsset
export const buyQuoteCalculator = {
calculate(
@@ -166,7 +164,7 @@ function findEthAmountNeededToBuyZrx(
const { totalEthAmount, remainingZrxBuyAmount } = acc;
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount);
const [takerFillAmount, adjustedMakerFillAmount] = orderUtils.getTakerFillAmountForFeeOrder(
const [takerFillAmount, adjustedMakerFillAmount] = orderCalculationUtils.getTakerFillAmountForFeeOrder(
order,
makerFillAmount,
);
@@ -200,8 +198,8 @@ function findEthAndZrxAmountNeededToBuyAsset(
const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc;
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount);
const takerFillAmount = orderUtils.getTakerFillAmount(order, makerFillAmount);
const takerFeeAmount = orderUtils.getTakerFeeAmount(order, takerFillAmount);
const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount);
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
return {
totalEthAmount: totalEthAmount.plus(takerFillAmount),
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),

View File

@@ -1,9 +1,8 @@
import { orderCalculationUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../types';
import { orderUtils } from './order_utils';
export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAmounts): LiquidityForAssetData => {
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
const liquidityInBigNumbers = orders.reduce(
@@ -14,7 +13,10 @@ export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAm
}
const tokensAvailableForCurrentOrder = availableMakerAssetAmount;
const ethValueAvailableForCurrentOrder = orderUtils.getTakerFillAmount(order, availableMakerAssetAmount);
const ethValueAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount(
order,
availableMakerAssetAmount,
);
return {
tokensAvailableInBaseUnits: acc.tokensAvailableInBaseUnits.plus(tokensAvailableForCurrentOrder),
ethValueAvailableInWei: acc.ethValueAvailableInWei.plus(ethValueAvailableForCurrentOrder),

View File

@@ -1,5 +1,5 @@
import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0x/contract-wrappers';
import { sortingUtils } from '@0x/order-utils';
import { orderCalculationUtils, sortingUtils } from '@0x/order-utils';
import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
@@ -14,8 +14,6 @@ import {
SignedOrderWithRemainingFillableMakerAssetAmount,
} from '../types';
import { orderUtils } from './order_utils';
export const orderProviderResponseProcessor = {
throwIfInvalidResponse(response: OrderProviderResponse, request: OrderProviderRequest): void {
const { makerAssetData, takerAssetData } = request;
@@ -83,7 +81,10 @@ function filterOutExpiredAndNonOpenOrders(
expiryBufferSeconds: number,
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
const result = _.filter(orders, order => {
return orderUtils.isOpenOrder(order) && !orderUtils.willOrderExpire(order, expiryBufferSeconds);
return (
orderCalculationUtils.isOpenOrder(order) &&
!orderCalculationUtils.willOrderExpire(order, expiryBufferSeconds)
);
});
return result;
}
@@ -112,7 +113,10 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
const transferrableAssetAmount = BigNumber.min(traderInfo.makerAllowance, traderInfo.makerBalance);
const transferrableFeeAssetAmount = BigNumber.min(traderInfo.makerZrxAllowance, traderInfo.makerZrxBalance);
const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount);
const remainingMakerAssetAmount = orderUtils.getRemainingMakerAmount(order, remainingTakerAssetAmount);
const remainingMakerAssetAmount = orderCalculationUtils.getMakerFillAmount(
order,
remainingTakerAssetAmount,
);
const remainingFillableCalculator = new RemainingFillableCalculator(
order.makerFee,
order.makerAssetAmount,

View File

@@ -1,74 +0,0 @@
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { constants } from '../constants';
export const orderUtils = {
isOrderExpired(order: SignedOrder): boolean {
return orderUtils.willOrderExpire(order, 0);
},
willOrderExpire(order: SignedOrder, secondsFromNow: number): boolean {
const millisecondsInSecond = 1000;
const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).integerValue();
return order.expirationTimeSeconds.isLessThan(currentUnixTimestampSec.plus(secondsFromNow));
},
isOpenOrder(order: SignedOrder): boolean {
return order.takerAddress === constants.NULL_ADDRESS;
},
// given a remaining amount of takerAsset, calculate how much makerAsset is available
getRemainingMakerAmount(order: SignedOrder, remainingTakerAmount: BigNumber): BigNumber {
const remainingMakerAmount = remainingTakerAmount
.times(order.makerAssetAmount)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return remainingMakerAmount;
},
// given a desired amount of makerAsset, calculate how much takerAsset is required to fill that amount
getTakerFillAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber {
// Round up because exchange rate favors Maker
const takerFillAmount = makerFillAmount
.multipliedBy(order.takerAssetAmount)
.div(order.makerAssetAmount)
.integerValue(BigNumber.ROUND_CEIL);
return takerFillAmount;
},
// given a desired amount of takerAsset to fill, calculate how much fee is required by the taker to fill that amount
getTakerFeeAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber {
// Round down because Taker fee rate favors Taker
const takerFeeAmount = takerFillAmount
.multipliedBy(order.takerFee)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return takerFeeAmount;
},
// given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled
getMakerFillAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber {
// Round down because exchange rate favors Maker
const makerFillAmount = takerFillAmount
.multipliedBy(order.makerAssetAmount)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFillAmount;
},
// given a desired amount of makerAsset, calculate how much fee is required by the maker to fill that amount
getMakerFeeAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber {
// Round down because Maker fee rate favors Maker
const makerFeeAmount = makerFillAmount
.multipliedBy(order.makerFee)
.div(order.makerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFeeAmount;
},
// given a desired amount of ZRX from a fee order, calculate how much takerAsset is required to fill that amount
// also calculate how much ZRX needs to be bought in order fill the desired amount + takerFee
getTakerFillAmountForFeeOrder(order: SignedOrder, makerFillAmount: BigNumber): [BigNumber, BigNumber] {
// For each unit of TakerAsset we buy (MakerAsset - TakerFee)
const adjustedTakerFillAmount = makerFillAmount
.multipliedBy(order.takerAssetAmount)
.div(order.makerAssetAmount.minus(order.takerFee))
.integerValue(BigNumber.ROUND_CEIL);
// The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees.
const adjustedMakerFillAmount = orderUtils.getMakerFillAmount(order, adjustedTakerFillAmount);
return [adjustedTakerFillAmount, adjustedMakerFillAmount];
},
};

View File

@@ -1211,7 +1211,7 @@ export class ExchangeWrapper extends ContractWrapper {
}
/**
* Validate the transfer from the Maker to the Taker. This is simulated on chain
* via an eth_call. If this call fails the asset is unlikely to be transferrable.
* via an eth_call. If this call fails the asset is not transferrable.
* @param signedOrder SignedOrder of interest
* @param fillTakerAssetAmount Amount we'd like to fill the order for
* @param takerAddress The taker of the order, defaults to signedOrder.takerAddress

View File

@@ -317,6 +317,39 @@ describe('ExchangeWrapper', () => {
}),
).to.eventually.to.be.rejected();
});
it('should throw when the maker does not have enough balance for the remaining order amount', async () => {
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
// Change maker balance to have less than the order amount
const remainingBalance = makerBalance.minus(signedOrder.makerAssetAmount.minus(1));
await web3Wrapper.awaitTransactionSuccessAsync(
await contractWrappers.erc20Token.transferAsync(
makerTokenAddress,
makerAddress,
constants.NULL_ADDRESS,
remainingBalance,
),
);
return expect(
contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder),
).to.eventually.to.be.rejected();
});
it('should validate the order when remaining order amount has some fillable amount', async () => {
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
// Change maker balance to have less than the order amount
const remainingBalance = makerBalance.minus(signedOrder.makerAssetAmount.minus(1));
await web3Wrapper.awaitTransactionSuccessAsync(
await contractWrappers.erc20Token.transferAsync(
makerTokenAddress,
makerAddress,
constants.NULL_ADDRESS,
remainingBalance,
),
);
// An amount is still transferrable
await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, {
validateRemainingOrderAmountIsFillable: false,
});
});
it('should throw when the ERC20 token has transfer restrictions', async () => {
const untransferrableToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
UntransferrableDummyERC20Token,

View File

@@ -5,6 +5,48 @@ import { constants } from './constants';
import { BalanceAndAllowance } from './types';
export const orderCalculationUtils = {
/**
* Determines if the order is expired given the current time
* @param order The order for expiry calculation
*/
isOrderExpired(order: Order): boolean {
return orderCalculationUtils.willOrderExpire(order, 0);
},
/**
* Calculates if the order will expire in the future.
* @param order The order for expiry calculation
* @param secondsFromNow The amount of seconds from current time
*/
willOrderExpire(order: Order, secondsFromNow: number): boolean {
const millisecondsInSecond = 1000;
const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).integerValue();
return order.expirationTimeSeconds.isLessThan(currentUnixTimestampSec.plus(secondsFromNow));
},
/**
* Determines if the order is open and fillable by any taker.
* @param order The order
*/
isOpenOrder(order: Order): boolean {
return order.takerAddress === constants.NULL_ADDRESS;
},
/**
* Given an amount of taker asset, calculate the the amount of maker asset
* @param order The order
* @param makerFillAmount the amount of taker asset
*/
getMakerFillAmount(order: Order, takerFillAmount: BigNumber): BigNumber {
// Round down because exchange rate favors Maker
const makerFillAmount = takerFillAmount
.multipliedBy(order.makerAssetAmount)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFillAmount;
},
/**
* Given an amount of maker asset, calculate the equivalent amount in taker asset
* @param order The order
* @param makerFillAmount the amount of maker asset
*/
getTakerFillAmount(order: Order, makerFillAmount: BigNumber): BigNumber {
// Round up because exchange rate favors Maker
const takerFillAmount = makerFillAmount
@@ -13,14 +55,47 @@ export const orderCalculationUtils = {
.integerValue(BigNumber.ROUND_CEIL);
return takerFillAmount;
},
// given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled
getMakerFillAmount(order: Order, takerFillAmount: BigNumber): BigNumber {
// Round down because exchange rate favors Maker
const makerFillAmount = takerFillAmount
.multipliedBy(order.makerAssetAmount)
/**
* Given an amount of taker asset, calculate the fee amount required for the taker
* @param order The order
* @param takerFillAmount the amount of taker asset
*/
getTakerFeeAmount(order: Order, takerFillAmount: BigNumber): BigNumber {
// Round down because Taker fee rate favors Taker
const takerFeeAmount = takerFillAmount
.multipliedBy(order.takerFee)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFillAmount;
return takerFeeAmount;
},
/**
* Given an amount of maker asset, calculate the fee amount required for the maker
* @param order The order
* @param makerFillAmount the amount of maker asset
*/
getMakerFeeAmount(order: Order, makerFillAmount: BigNumber): BigNumber {
// Round down because Maker fee rate favors Maker
const makerFeeAmount = makerFillAmount
.multipliedBy(order.makerFee)
.div(order.makerAssetAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFeeAmount;
},
/**
* Given a desired amount of ZRX from a fee order, calculate the amount of taker asset required to fill.
* Also calculate how much ZRX needs to be purchased in order to fill the desired amount plus the taker fee amount
* @param order The order
* @param makerFillAmount the amount of maker asset
*/
getTakerFillAmountForFeeOrder(order: Order, makerFillAmount: BigNumber): [BigNumber, BigNumber] {
// For each unit of TakerAsset we buy (MakerAsset - TakerFee)
const adjustedTakerFillAmount = makerFillAmount
.multipliedBy(order.takerAssetAmount)
.div(order.makerAssetAmount.minus(order.takerFee))
.integerValue(BigNumber.ROUND_CEIL);
// The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees.
const adjustedMakerFillAmount = orderCalculationUtils.getMakerFillAmount(order, adjustedTakerFillAmount);
return [adjustedTakerFillAmount, adjustedMakerFillAmount];
},
/**
* Calculates the remaining fillable amount for an order given: