aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils')
-rw-r--r--packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts2
-rw-r--r--packages/order-utils/src/exchange_transfer_simulator.ts3
-rw-r--r--packages/order-utils/src/index.ts1
-rw-r--r--packages/order-utils/src/order_state_utils.ts267
-rw-r--r--packages/order-utils/src/order_validation_utils.ts193
-rw-r--r--packages/order-utils/src/remaining_fillable_calculator.ts6
-rw-r--r--packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts4
-rw-r--r--packages/order-utils/src/utils.ts7
-rw-r--r--packages/order-utils/test/exchange_transfer_simulator_test.ts25
9 files changed, 322 insertions, 186 deletions
diff --git a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
index ec398a11e..865ea4e43 100644
--- a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
+++ b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
@@ -3,5 +3,5 @@ import { BigNumber } from '@0xproject/utils';
export abstract class AbstractOrderFilledCancelledFetcher {
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
public abstract async isOrderCancelledAsync(orderHash: string): Promise<boolean>;
- public abstract getZRXTokenAddress(): string;
+ public abstract getZRXAssetData(): string;
}
diff --git a/packages/order-utils/src/exchange_transfer_simulator.ts b/packages/order-utils/src/exchange_transfer_simulator.ts
index 32d53d6a2..72ed85406 100644
--- a/packages/order-utils/src/exchange_transfer_simulator.ts
+++ b/packages/order-utils/src/exchange_transfer_simulator.ts
@@ -90,6 +90,9 @@ export class ExchangeTransferSimulator {
amountInBaseUnits: BigNumber,
): Promise<void> {
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
+ // HACK: This code assumes that all tokens with an UNLIMITED_ALLOWANCE_IN_BASE_UNITS set,
+ // are UnlimitedAllowanceTokens. This is however not true, it just so happens that all
+ // DummyERC20Tokens we use in tests are.
if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index 514488e02..b4a7a6b67 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -16,6 +16,7 @@ export { generatePseudoRandomSalt } from './salt';
export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types';
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
+export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
export { OrderStateUtils } from './order_state_utils';
export { assetProxyUtils } from './asset_proxy_utils';
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index 40f235da7..dd45a1298 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -10,40 +10,81 @@ import { BigNumber } from '@0xproject/utils';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
-import { assetProxyUtils } from './asset_proxy_utils';
import { orderHashUtils } from './order_hash';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
+import { utils } from './utils';
+
+interface SidedOrderRelevantState {
+ isMakerSide: boolean;
+ traderBalance: BigNumber;
+ traderProxyAllowance: BigNumber;
+ traderFeeBalance: BigNumber;
+ traderFeeProxyAllowance: BigNumber;
+ filledTakerAssetAmount: BigNumber;
+ remainingFillableAssetAmount: BigNumber;
+}
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
export class OrderStateUtils {
private _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
- private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
- const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(orderRelevantState.filledTakerAssetAmount);
+ private static _validateIfOrderIsValid(
+ signedOrder: SignedOrder,
+ sidedOrderRelevantState: SidedOrderRelevantState,
+ ): void {
+ const isMakerSide = sidedOrderRelevantState.isMakerSide;
+ const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(
+ sidedOrderRelevantState.filledTakerAssetAmount,
+ );
if (availableTakerAssetAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
- if (orderRelevantState.makerBalance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
+ if (sidedOrderRelevantState.traderBalance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerBalance
+ : ExchangeContractErrs.InsufficientTakerBalance,
+ );
}
- if (orderRelevantState.makerProxyAllowance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
+ if (sidedOrderRelevantState.traderProxyAllowance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerAllowance
+ : ExchangeContractErrs.InsufficientTakerAllowance,
+ );
}
if (!signedOrder.makerFee.eq(0)) {
- if (orderRelevantState.makerFeeBalance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
+ if (sidedOrderRelevantState.traderFeeBalance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeBalance
+ : ExchangeContractErrs.InsufficientTakerFeeBalance,
+ );
}
- if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
+ if (sidedOrderRelevantState.traderFeeProxyAllowance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeAllowance
+ : ExchangeContractErrs.InsufficientTakerFeeAllowance,
+ );
}
}
- const minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
- .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
- .dividedBy(signedOrder.makerAssetAmount);
+
+ let minFillableTakerAssetAmountWithinNoRoundingErrorRange;
+ if (isMakerSide) {
+ minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
+ .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
+ .dividedBy(signedOrder.makerAssetAmount);
+ } else {
+ minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.makerAssetAmount
+ .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
+ .dividedBy(signedOrder.takerAssetAmount);
+ }
+
if (
- orderRelevantState.remainingFillableTakerAssetAmount.lessThan(
+ sidedOrderRelevantState.remainingFillableAssetAmount.lessThan(
minFillableTakerAssetAmountWithinNoRoundingErrorRange,
)
) {
@@ -57,11 +98,20 @@ export class OrderStateUtils {
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
}
- public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
- const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
+ public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
+ const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const sidedOrderRelevantState = {
+ isMakerSide: true,
+ traderBalance: orderRelevantState.makerBalance,
+ traderProxyAllowance: orderRelevantState.makerProxyAllowance,
+ traderFeeBalance: orderRelevantState.makerFeeBalance,
+ traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
+ filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
+ remainingFillableAssetAmount: orderRelevantState.remainingFillableMakerAssetAmount,
+ };
try {
- OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
+ OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState);
const orderState: OrderStateValid = {
isValid: true,
orderHash,
@@ -77,63 +127,158 @@ export class OrderStateUtils {
return orderState;
}
}
- public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
- const zrxTokenAddress = this._orderFilledCancelledFetcher.getZRXTokenAddress();
- const makerProxyData = assetProxyUtils.decodeERC20AssetData(signedOrder.makerAssetData);
- const makerAssetAddress = makerProxyData.tokenAddress;
- const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
- const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- makerAssetAddress,
- signedOrder.makerAddress,
+ public async getOpenOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
+ const isMaker = true;
+ const sidedOrderRelevantState = await this._getSidedOrderRelevantStateAsync(
+ isMaker,
+ signedOrder,
+ signedOrder.takerAddress,
);
- const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- makerAssetAddress,
- signedOrder.makerAddress,
+ const remainingFillableTakerAssetAmount = sidedOrderRelevantState.remainingFillableAssetAmount
+ .times(signedOrder.takerAssetAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+
+ const orderRelevantState = {
+ makerBalance: sidedOrderRelevantState.traderBalance,
+ makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance,
+ makerFeeBalance: sidedOrderRelevantState.traderFeeBalance,
+ makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance,
+ filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount,
+ remainingFillableMakerAssetAmount: sidedOrderRelevantState.remainingFillableAssetAmount,
+ remainingFillableTakerAssetAmount,
+ };
+ return orderRelevantState;
+ }
+ public async getMaxFillableTakerAssetAmountAsync(
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<BigNumber> {
+ // Get max fillable amount for an order, considering the makers ability to fill
+ let isMaker = true;
+ const orderRelevantMakerState = await this._getSidedOrderRelevantStateAsync(
+ isMaker,
+ signedOrder,
+ signedOrder.takerAddress,
);
- const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : utils.getPartialAmount(
+ orderRelevantMakerState.remainingFillableAssetAmount,
+ signedOrder.makerAssetAmount,
+ signedOrder.takerAssetAmount,
+ );
+
+ // Get max fillable amount for an order, considering the takers ability to fill
+ isMaker = false;
+ const orderRelevantTakerState = await this._getSidedOrderRelevantStateAsync(isMaker, signedOrder, takerAddress);
+ const remainingFillableTakerAssetAmountGivenTakersStatus = orderRelevantTakerState.remainingFillableAssetAmount;
+
+ // The min of these two in the actualy max fillable by either party
+ const fillableTakerAssetAmount = BigNumber.min([
+ remainingFillableTakerAssetAmountGivenMakersStatus,
+ remainingFillableTakerAssetAmountGivenTakersStatus,
+ ]);
+
+ return fillableTakerAssetAmount;
+ }
+ public async getMaxFillableTakerAssetAmountForFailingOrderAsync(
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<BigNumber> {
+ // Get min of taker balance & allowance
+ const takerAssetBalanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
+ signedOrder.takerAssetData,
+ takerAddress,
);
- const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const takerAssetAllowanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ signedOrder.takerAssetData,
+ takerAddress,
);
+ const minTakerAssetAmount = BigNumber.min([takerAssetBalanceOfTaker, takerAssetAllowanceOfTaker]);
+
+ // get remainingFillAmount
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
+ const remainingFillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerAssetAmount);
+
+ if (minTakerAssetAmount.gte(remainingFillTakerAssetAmount)) {
+ return remainingFillTakerAssetAmount;
+ } else {
+ return minTakerAssetAmount;
+ }
+ }
+ private async _getSidedOrderRelevantStateAsync(
+ isMakerSide: boolean,
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<SidedOrderRelevantState> {
+ let traderAddress;
+ let assetData;
+ let assetAmount;
+ let feeAmount;
+ if (isMakerSide) {
+ traderAddress = signedOrder.makerAddress;
+ assetData = signedOrder.makerAssetData;
+ assetAmount = signedOrder.makerAssetAmount;
+ feeAmount = signedOrder.makerFee;
+ } else {
+ traderAddress = takerAddress;
+ assetData = signedOrder.takerAssetData;
+ assetAmount = signedOrder.takerAssetAmount;
+ feeAmount = signedOrder.takerFee;
+ }
+ const zrxAssetData = this._orderFilledCancelledFetcher.getZRXAssetData();
+ const isAssetZRX = assetData === zrxAssetData;
+
+ const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
+ const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ assetData,
+ traderAddress,
+ );
+ const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
+ zrxAssetData,
+ traderAddress,
+ );
+ const traderFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ zrxAssetData,
+ traderAddress,
+ );
+
+ const transferrableTraderAssetAmount = BigNumber.min([traderProxyAllowance, traderBalance]);
+ const transferrableFeeAssetAmount = BigNumber.min([traderFeeProxyAllowance, traderFeeBalance]);
+
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
- const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
+ const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
const remainingTakerAssetAmount = isOrderCancelled
? new BigNumber(0)
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
- const remainingMakerAssetAmount = remainingTakerAssetAmount
- .times(totalMakerAssetAmount)
- .dividedToIntegerBy(totalTakerAssetAmount);
- const transferrableMakerAssetAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
- const transferrableFeeAssetAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
-
- const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxTokenAddress);
- const isMakerAssetZRX = signedOrder.makerAssetData === zrxAssetData;
+ const remainingMakerAssetAmount = remainingTakerAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : remainingTakerAssetAmount.times(totalMakerAssetAmount).dividedToIntegerBy(totalTakerAssetAmount);
+ const remainingAssetAmount = isMakerSide ? remainingMakerAssetAmount : remainingTakerAssetAmount;
+
const remainingFillableCalculator = new RemainingFillableCalculator(
- signedOrder.makerFee,
- signedOrder.makerAssetAmount,
- isMakerAssetZRX,
- transferrableMakerAssetAmount,
+ feeAmount,
+ assetAmount,
+ isAssetZRX,
+ transferrableTraderAssetAmount,
transferrableFeeAssetAmount,
- remainingMakerAssetAmount,
+ remainingAssetAmount,
);
- const remainingFillableMakerAssetAmount = remainingFillableCalculator.computeRemainingFillable();
- const remainingFillableTakerAssetAmount = remainingFillableMakerAssetAmount
- .times(signedOrder.takerAssetAmount)
- .dividedToIntegerBy(signedOrder.makerAssetAmount);
- const orderRelevantState = {
- makerBalance,
- makerProxyAllowance,
- makerFeeBalance,
- makerFeeProxyAllowance,
+ const remainingFillableAssetAmount = remainingFillableCalculator.computeRemainingFillable();
+
+ const sidedOrderRelevantState = {
+ isMakerSide,
+ traderBalance,
+ traderProxyAllowance,
+ traderFeeBalance,
+ traderFeeProxyAllowance,
filledTakerAssetAmount,
- remainingFillableMakerAssetAmount,
- remainingFillableTakerAssetAmount,
+ remainingFillableAssetAmount,
};
- return orderRelevantState;
+ return sidedOrderRelevantState;
}
}
diff --git a/packages/order-utils/src/order_validation_utils.ts b/packages/order-utils/src/order_validation_utils.ts
index 3a6704f26..54428f77d 100644
--- a/packages/order-utils/src/order_validation_utils.ts
+++ b/packages/order-utils/src/order_validation_utils.ts
@@ -1,24 +1,19 @@
-import { ExchangeContractErrs, Order, SignedOrder } from '@0xproject/types';
+import { RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { OrderError, TradeSide, TransferType } from './types';
+import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
import { constants } from './constants';
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
-import { ExchangeContract } from './generated_contract_wrappers/exchange';
import { orderHashUtils } from './order_hash';
import { isValidSignatureAsync } from './signature_utils';
import { utils } from './utils';
export class OrderValidationUtils {
- private _exchangeContract: ExchangeContract;
- // TODO: Write some tests for the function
- // const numerator = new BigNumber(20);
- // const denominator = new BigNumber(999);
- // const target = new BigNumber(50);
- // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
+ private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean {
// Solidity's mulmod() in JS
// Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions
@@ -36,136 +31,120 @@ export class OrderValidationUtils {
const isError = errPercentageTimes1000000.gt(1000);
return isError;
}
- public static validateCancelOrderThrowIfInvalid(
- order: Order,
- cancelTakerTokenAmount: BigNumber,
- filledTakerTokenAmount: BigNumber,
- ): void {
- if (cancelTakerTokenAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
- }
- if (order.takerAssetAmount.eq(filledTakerTokenAmount)) {
- throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
- }
- const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
- if (order.expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderCancelExpired);
- }
- }
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
senderAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<void> {
- const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.makerAssetAmount,
- );
- await exchangeTradeEmulator.transferFromAsync(
- signedOrder.makerAssetData,
- signedOrder.makerAddress,
- senderAddress,
- fillMakerTokenAmount,
- TradeSide.Maker,
- TransferType.Trade,
- );
- await exchangeTradeEmulator.transferFromAsync(
- signedOrder.takerAssetData,
- senderAddress,
- signedOrder.makerAddress,
- fillTakerTokenAmount,
- TradeSide.Taker,
- TransferType.Trade,
- );
- const makerFeeAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.makerFee,
- );
- await exchangeTradeEmulator.transferFromAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
- signedOrder.feeRecipientAddress,
- makerFeeAmount,
- TradeSide.Maker,
- TransferType.Fee,
- );
- const takerFeeAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.takerFee,
- );
- await exchangeTradeEmulator.transferFromAsync(
- zrxTokenAddress,
- senderAddress,
- signedOrder.feeRecipientAddress,
- takerFeeAmount,
- TradeSide.Taker,
- TransferType.Fee,
- );
+ try {
+ const fillMakerTokenAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.makerAssetData,
+ signedOrder.makerAddress,
+ senderAddress,
+ fillMakerTokenAmount,
+ TradeSide.Maker,
+ TransferType.Trade,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.takerAssetData,
+ senderAddress,
+ signedOrder.makerAddress,
+ fillTakerAssetAmount,
+ TradeSide.Taker,
+ TransferType.Trade,
+ );
+ const makerFeeAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxAssetData,
+ signedOrder.makerAddress,
+ signedOrder.feeRecipientAddress,
+ makerFeeAmount,
+ TradeSide.Maker,
+ TransferType.Fee,
+ );
+ const takerFeeAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxAssetData,
+ senderAddress,
+ signedOrder.feeRecipientAddress,
+ takerFeeAmount,
+ TradeSide.Taker,
+ TransferType.Fee,
+ );
+ } catch (err) {
+ throw new Error(RevertReason.TransferFailed);
+ }
}
private static _validateRemainingFillAmountNotZeroOrThrow(
takerAssetAmount: BigNumber,
filledTakerTokenAmount: BigNumber,
): void {
if (takerAssetAmount.eq(filledTakerTokenAmount)) {
- throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ throw new Error(RevertReason.OrderUnfillable);
}
}
private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderFillExpired);
+ throw new Error(RevertReason.OrderUnfillable);
}
}
- private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
- const fillMakerTokenAmount = numerator
- .mul(target)
- .div(denominator)
- .round(0);
- return fillMakerTokenAmount;
- }
- constructor(exchangeContract: ExchangeContract) {
- this._exchangeContract = exchangeContract;
+ constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) {
+ this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
}
public async validateOrderFillableOrThrowAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
- zrxTokenAddress: string,
+ zrxAssetData: string,
expectedFillTakerTokenAmount?: BigNumber,
): Promise<void> {
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
- const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
+ const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerAssetAmount,
filledTakerTokenAmount,
);
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
- let fillTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
+ let fillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
- fillTakerTokenAmount = expectedFillTakerTokenAmount;
+ fillTakerAssetAmount = expectedFillTakerTokenAmount;
}
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
- fillTakerTokenAmount,
+ fillTakerAssetAmount,
signedOrder.takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
}
public async validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
provider: Provider,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
takerAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<BigNumber> {
- if (fillTakerTokenAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderFillAmountZero);
+ if (fillTakerAssetAmount.eq(0)) {
+ throw new Error(RevertReason.InvalidTakerAmount);
+ }
+ if (signedOrder.makerAssetAmount.eq(0) || signedOrder.takerAssetAmount.eq(0)) {
+ throw new Error(RevertReason.OrderUnfillable);
}
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
const isValid = await isValidSignatureAsync(
@@ -177,34 +156,34 @@ export class OrderValidationUtils {
if (!isValid) {
throw new Error(OrderError.InvalidSignature);
}
- const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
+ const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerAssetAmount,
filledTakerTokenAmount,
);
if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
- throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
+ throw new Error(RevertReason.InvalidTaker);
}
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
const remainingTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
- const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
+ const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerAssetAmount)
? remainingTakerTokenAmount
- : fillTakerTokenAmount;
+ : fillTakerAssetAmount;
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
desiredFillTakerTokenAmount,
takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError(
- filledTakerTokenAmount,
+ desiredFillTakerTokenAmount,
signedOrder.takerAssetAmount,
signedOrder.makerAssetAmount,
);
if (wouldRoundingErrorOccur) {
- throw new Error(ExchangeContractErrs.OrderFillRoundingError);
+ throw new Error(RevertReason.RoundingError);
}
return filledTakerTokenAmount;
}
@@ -212,20 +191,20 @@ export class OrderValidationUtils {
exchangeTradeEmulator: ExchangeTransferSimulator,
provider: Provider,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
takerAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<void> {
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator,
provider,
signedOrder,
- fillTakerTokenAmount,
+ fillTakerAssetAmount,
takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
- if (filledTakerTokenAmount !== fillTakerTokenAmount) {
- throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
+ if (filledTakerTokenAmount !== fillTakerAssetAmount) {
+ throw new Error(RevertReason.OrderUnfillable);
}
}
}
diff --git a/packages/order-utils/src/remaining_fillable_calculator.ts b/packages/order-utils/src/remaining_fillable_calculator.ts
index bc146e931..29e19e5ab 100644
--- a/packages/order-utils/src/remaining_fillable_calculator.ts
+++ b/packages/order-utils/src/remaining_fillable_calculator.ts
@@ -23,9 +23,9 @@ export class RemainingFillableCalculator {
this._transferrableAssetAmount = transferrableAssetAmount;
this._transferrableFeeAmount = transferrableFeeAmount;
this._remainingOrderAssetAmount = remainingOrderAssetAmount;
- this._remainingOrderFeeAmount = remainingOrderAssetAmount
- .times(this._orderFee)
- .dividedToIntegerBy(this._orderAssetAmount);
+ this._remainingOrderFeeAmount = orderAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : remainingOrderAssetAmount.times(orderFee).dividedToIntegerBy(orderAssetAmount);
}
public computeRemainingFillable(): BigNumber {
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
diff --git a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
index 08d50b924..e7352119d 100644
--- a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
+++ b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
@@ -19,8 +19,8 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx
[userAddress: string]: BigNumber;
};
};
- constructor(token: AbstractBalanceAndProxyAllowanceFetcher) {
- this._balanceAndProxyAllowanceFetcher = token;
+ constructor(balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher) {
+ this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
this._balance = {};
this._proxyAllowance = {};
}
diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts
index 6149316f6..7aaaf0609 100644
--- a/packages/order-utils/src/utils.ts
+++ b/packages/order-utils/src/utils.ts
@@ -12,4 +12,11 @@ export const utils = {
const milisecondsInSecond = 1000;
return new BigNumber(Date.now() / milisecondsInSecond).round();
},
+ getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
+ const fillMakerTokenAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .round(0);
+ return fillMakerTokenAmount;
+ },
};
diff --git a/packages/order-utils/test/exchange_transfer_simulator_test.ts b/packages/order-utils/test/exchange_transfer_simulator_test.ts
index 52385f611..bed04d879 100644
--- a/packages/order-utils/test/exchange_transfer_simulator_test.ts
+++ b/packages/order-utils/test/exchange_transfer_simulator_test.ts
@@ -4,6 +4,7 @@ import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { artifacts } from '../src/artifacts';
+import { assetProxyUtils } from '../src/asset_proxy_utils';
import { constants } from '../src/constants';
import { ExchangeTransferSimulator } from '../src/exchange_transfer_simulator';
import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token';
@@ -27,7 +28,7 @@ describe('ExchangeTransferSimulator', async () => {
let coinbase: string;
let sender: string;
let recipient: string;
- let exampleTokenAddress: string;
+ let exampleAssetData: string;
let exchangeTransferSimulator: ExchangeTransferSimulator;
let txHash: string;
let erc20ProxyAddress: string;
@@ -65,7 +66,7 @@ describe('ExchangeTransferSimulator', async () => {
totalSupply,
);
- exampleTokenAddress = dummyERC20Token.address;
+ exampleAssetData = assetProxyUtils.encodeERC20AssetData(dummyERC20Token.address);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -91,7 +92,7 @@ describe('ExchangeTransferSimulator', async () => {
it("throws if the user doesn't have enough allowance", async () => {
return expect(
exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -107,7 +108,7 @@ describe('ExchangeTransferSimulator', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
return expect(
exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -128,7 +129,7 @@ describe('ExchangeTransferSimulator', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -136,9 +137,9 @@ describe('ExchangeTransferSimulator', async () => {
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
- const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
- const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
- const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
+ const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
+ const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
+ const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(0);
@@ -157,7 +158,7 @@ describe('ExchangeTransferSimulator', async () => {
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -165,9 +166,9 @@ describe('ExchangeTransferSimulator', async () => {
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
- const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
- const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
- const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
+ const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
+ const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
+ const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);