aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/utils/order_validation_utils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/0x.js/src/utils/order_validation_utils.ts')
-rw-r--r--packages/0x.js/src/utils/order_validation_utils.ts166
1 files changed, 166 insertions, 0 deletions
diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts
new file mode 100644
index 000000000..f03703c4e
--- /dev/null
+++ b/packages/0x.js/src/utils/order_validation_utils.ts
@@ -0,0 +1,166 @@
+import * as _ from 'lodash';
+import BigNumber from 'bignumber.js';
+import {ExchangeContractErrs, SignedOrder, Order, ZeroExError, TradeSide, TransferType} from '../types';
+import {ZeroEx} from '../0x';
+import {TokenWrapper} from '../contract_wrappers/token_wrapper';
+import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
+import {utils} from '../utils/utils';
+import {constants} from '../utils/constants';
+import {ExchangeTransferSimulator} from './exchange_transfer_simulator';
+
+export class OrderValidationUtils {
+ private tokenWrapper: TokenWrapper;
+ private exchangeWrapper: ExchangeWrapper;
+ constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) {
+ this.tokenWrapper = tokenWrapper;
+ this.exchangeWrapper = exchangeWrapper;
+ }
+ public async validateOrderFillableOrThrowAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder, zrxTokenAddress: string,
+ expectedFillTakerTokenAmount?: BigNumber): Promise<void> {
+ const orderHash = utils.getOrderHashHex(signedOrder);
+ const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ this.validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
+ );
+ this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
+ let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ if (!_.isUndefined(expectedFillTakerTokenAmount)) {
+ fillTakerTokenAmount = expectedFillTakerTokenAmount;
+ }
+ const fillMakerTokenAmount = this.getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerTokenAmount,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.makerTokenAddress, signedOrder.maker, signedOrder.taker, fillMakerTokenAmount,
+ TradeSide.Maker, TransferType.Trade,
+ );
+ const makerFeeAmount = this.getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, makerFeeAmount,
+ TradeSide.Maker, TransferType.Fee,
+ );
+ }
+ public async validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber, takerAddress: string,
+ zrxTokenAddress: string): Promise<BigNumber> {
+ if (fillTakerTokenAmount.eq(0)) {
+ throw new Error(ExchangeContractErrs.OrderFillAmountZero);
+ }
+ const orderHash = utils.getOrderHashHex(signedOrder);
+ if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
+ throw new Error(ZeroExError.InvalidSignature);
+ }
+ const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ this.validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
+ );
+ if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
+ throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
+ }
+ this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
+ const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) ?
+ remainingTakerTokenAmount :
+ fillTakerTokenAmount;
+ await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
+ exchangeTradeEmulator, signedOrder, filledTakerTokenAmount, takerAddress, zrxTokenAddress,
+ );
+
+ const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync(
+ filledTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
+ );
+ if (wouldRoundingErrorOccur) {
+ throw new Error(ExchangeContractErrs.OrderFillRoundingError);
+ }
+ return filledTakerTokenAmount;
+ }
+ public async validateFillOrKillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber, takerAddress: string, zrxTokenAddress: string): Promise<void> {
+ const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
+ );
+ if (filledTakerTokenAmount !== fillTakerTokenAmount) {
+ throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
+ }
+ }
+ public async validateCancelOrderThrowIfInvalidAsync(order: Order,
+ cancelTakerTokenAmount: BigNumber,
+ unavailableTakerTokenAmount: BigNumber,
+ ): Promise<void> {
+ if (cancelTakerTokenAmount.eq(0)) {
+ throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
+ }
+ if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
+ }
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderCancelExpired);
+ }
+ }
+ public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber, senderAddress: string, zrxTokenAddress: string): Promise<void> {
+ const fillMakerTokenAmount = this.getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerTokenAmount,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.makerTokenAddress, signedOrder.maker, senderAddress, fillMakerTokenAmount,
+ TradeSide.Maker, TransferType.Trade,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.takerTokenAddress, senderAddress, signedOrder.maker, fillTakerTokenAmount,
+ TradeSide.Taker, TransferType.Trade,
+ );
+ const makerFeeAmount = this.getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, makerFeeAmount, TradeSide.Maker,
+ TransferType.Fee,
+ );
+ const takerFeeAmount = this.getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.takerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxTokenAddress, senderAddress, signedOrder.feeRecipient, takerFeeAmount, TradeSide.Taker,
+ TransferType.Fee,
+ );
+ }
+ private validateRemainingFillAmountNotZeroOrThrow(
+ takerTokenAmount: BigNumber, unavailableTakerTokenAmount: BigNumber,
+ ) {
+ if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ }
+ }
+ private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderFillExpired);
+ }
+ }
+ private getPartialAmount(numerator: BigNumber, denominator: BigNumber,
+ target: BigNumber): BigNumber {
+ const fillMakerTokenAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .round(0);
+ return fillMakerTokenAmount;
+ }
+}