From c4ee2d73865a1444c079b9e2836b7630a0adf03e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 12 Nov 2017 22:17:18 -0500 Subject: Switch over to Lerna + Yarn Workspaces setup for a mono-repo approach --- packages/0x.js/src/utils/order_validation_utils.ts | 166 +++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 packages/0x.js/src/utils/order_validation_utils.ts (limited to 'packages/0x.js/src/utils/order_validation_utils.ts') 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 { + 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 { + 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 { + 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 { + 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 { + 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; + } +} -- cgit v1.2.3