aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers/src/utils
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-05-12 01:11:27 +0800
committerGitHub <noreply@github.com>2018-05-12 01:11:27 +0800
commit6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b (patch)
tree8d374bf8f4f70a40d75c8bf4b35fe9a169250909 /packages/contract-wrappers/src/utils
parentf42f608f3f97a5244f09f17ae5ee184c0f775de3 (diff)
parentad8e12eeaebd40fdafac7cbef96121d4454909df (diff)
downloaddexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar.gz
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar.bz2
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar.lz
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar.xz
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.tar.zst
dexon-sol-tools-6aed4fb1ae27dabed027c855f2cbdc0bfb4f3b6b.zip
Merge pull request #579 from 0xProject/breakUp0xjs
Split 0x.js into: contract-wrappers & order-watcher
Diffstat (limited to 'packages/contract-wrappers/src/utils')
-rw-r--r--packages/contract-wrappers/src/utils/assert.ts31
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts11
-rw-r--r--packages/contract-wrappers/src/utils/decorators.ts91
-rw-r--r--packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts115
-rw-r--r--packages/contract-wrappers/src/utils/filter_utils.ts95
-rw-r--r--packages/contract-wrappers/src/utils/order_validation_utils.ts198
-rw-r--r--packages/contract-wrappers/src/utils/utils.ts13
7 files changed, 554 insertions, 0 deletions
diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts
new file mode 100644
index 000000000..2588a4d09
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/assert.ts
@@ -0,0 +1,31 @@
+import { assert as sharedAssert } from '@0xproject/assert';
+// We need those two unused imports because they're actually used by sharedAssert which gets injected here
+// tslint:disable-next-line:no-unused-variable
+import { Schema } from '@0xproject/json-schemas';
+// tslint:disable-next-line:no-unused-variable
+import { ECSignature } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+
+import { isValidSignature } from '@0xproject/order-utils';
+
+export const assert = {
+ ...sharedAssert,
+ isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
+ const isValid = isValidSignature(orderHash, ecSignature, signerAddress);
+ this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
+ },
+ async isSenderAddressAsync(
+ variableName: string,
+ senderAddressHex: string,
+ web3Wrapper: Web3Wrapper,
+ ): Promise<void> {
+ sharedAssert.isETHAddressHex(variableName, senderAddressHex);
+ const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
+ sharedAssert.assert(
+ isSenderAddressAvailable,
+ `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
+ );
+ },
+};
diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts
new file mode 100644
index 000000000..07da6745d
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/constants.ts
@@ -0,0 +1,11 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const constants = {
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ TESTRPC_NETWORK_ID: 50,
+ INVALID_JUMP_PATTERN: 'invalid JUMP at',
+ OUT_OF_GAS_PATTERN: 'out of gas',
+ INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
+ UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
+ DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
+};
diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts
new file mode 100644
index 000000000..64123143c
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/decorators.ts
@@ -0,0 +1,91 @@
+import * as _ from 'lodash';
+
+import { AsyncMethod, ContractWrappersError, SyncMethod } from '../types';
+
+import { constants } from './constants';
+
+type ErrorTransformer = (err: Error) => Error;
+
+const contractCallErrorTransformer = (error: Error) => {
+ if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
+ return new Error(ContractWrappersError.InvalidJump);
+ }
+ if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
+ return new Error(ContractWrappersError.OutOfGas);
+ }
+ return error;
+};
+
+const schemaErrorTransformer = (error: Error) => {
+ if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) {
+ const errMsg =
+ 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
+ return new Error(errMsg);
+ }
+ return error;
+};
+
+/**
+ * Source: https://stackoverflow.com/a/29837695/3546986
+ */
+const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
+ const asyncErrorHandlingDecorator = (
+ target: object,
+ key: string | symbol,
+ descriptor: TypedPropertyDescriptor<AsyncMethod>,
+ ) => {
+ const originalMethod = descriptor.value as AsyncMethod;
+
+ // Do not use arrow syntax here. Use a function expression in
+ // order to use the correct value of `this` in this method
+ // tslint:disable-next-line:only-arrow-functions
+ descriptor.value = async function(...args: any[]) {
+ try {
+ const result = await originalMethod.apply(this, args);
+ return result;
+ } catch (error) {
+ const transformedError = errorTransformer(error);
+ throw transformedError;
+ }
+ };
+
+ return descriptor;
+ };
+
+ return asyncErrorHandlingDecorator;
+};
+
+const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
+ const syncErrorHandlingDecorator = (
+ target: object,
+ key: string | symbol,
+ descriptor: TypedPropertyDescriptor<SyncMethod>,
+ ) => {
+ const originalMethod = descriptor.value as SyncMethod;
+
+ // Do not use arrow syntax here. Use a function expression in
+ // order to use the correct value of `this` in this method
+ // tslint:disable-next-line:only-arrow-functions
+ descriptor.value = function(...args: any[]) {
+ try {
+ const result = originalMethod.apply(this, args);
+ return result;
+ } catch (error) {
+ const transformedError = errorTransformer(error);
+ throw transformedError;
+ }
+ };
+
+ return descriptor;
+ };
+
+ return syncErrorHandlingDecorator;
+};
+
+// _.flow(f, g) = f ∘ g
+const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
+
+export const decorators = {
+ asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
+ syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer),
+};
diff --git a/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts
new file mode 100644
index 000000000..b575285a1
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts
@@ -0,0 +1,115 @@
+import { BlockParamLiteral, ExchangeContractErrs } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { TokenWrapper } from '../contract_wrappers/token_wrapper';
+import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
+import { TradeSide, TransferType } from '../types';
+import { constants } from '../utils/constants';
+
+enum FailureReason {
+ Balance = 'balance',
+ ProxyAllowance = 'proxyAllowance',
+}
+
+const ERR_MSG_MAPPING = {
+ [FailureReason.Balance]: {
+ [TradeSide.Maker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
+ },
+ [TradeSide.Taker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
+ },
+ },
+ [FailureReason.ProxyAllowance]: {
+ [TradeSide.Maker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
+ },
+ [TradeSide.Taker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
+ },
+ },
+};
+
+export class ExchangeTransferSimulator {
+ private _store: BalanceAndProxyAllowanceLazyStore;
+ private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;
+ private static _throwValidationError(
+ failureReason: FailureReason,
+ tradeSide: TradeSide,
+ transferType: TransferType,
+ ): never {
+ const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
+ throw new Error(errMsg);
+ }
+ constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
+ this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock);
+ this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ }
+ /**
+ * Simulates transferFrom call performed by a proxy
+ * @param tokenAddress Address of the token to be transferred
+ * @param from Owner of the transferred tokens
+ * @param to Recipient of the transferred tokens
+ * @param amountInBaseUnits The amount of tokens being transferred
+ * @param tradeSide Is Maker/Taker transferring
+ * @param transferType Is it a fee payment or a value transfer
+ */
+ public async transferFromAsync(
+ tokenAddress: string,
+ from: string,
+ to: string,
+ amountInBaseUnits: BigNumber,
+ tradeSide: TradeSide,
+ transferType: TransferType,
+ ): Promise<void> {
+ // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
+ // allowances for the taker. We do however, want to increase the balance of the maker since the maker
+ // might be relying on those funds to fill subsequent orders or pay the order's fees.
+ if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
+ await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
+ return;
+ }
+ const balance = await this._store.getBalanceAsync(tokenAddress, from);
+ const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
+ if (proxyAllowance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
+ }
+ if (balance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
+ }
+ await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
+ await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
+ await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
+ }
+ private async _decreaseProxyAllowanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
+ if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
+ }
+ }
+ private async _increaseBalanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
+ this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
+ }
+ private async _decreaseBalanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
+ this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
+ }
+}
diff --git a/packages/contract-wrappers/src/utils/filter_utils.ts b/packages/contract-wrappers/src/utils/filter_utils.ts
new file mode 100644
index 000000000..c5df7321e
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/filter_utils.ts
@@ -0,0 +1,95 @@
+import {
+ ConstructorAbi,
+ ContractAbi,
+ EventAbi,
+ FallbackAbi,
+ FilterObject,
+ LogEntry,
+ MethodAbi,
+} from '@0xproject/types';
+import * as ethUtil from 'ethereumjs-util';
+import * as jsSHA3 from 'js-sha3';
+import * as _ from 'lodash';
+import * as uuid from 'uuid/v4';
+
+import { BlockRange, ContractEvents, IndexedFilterValues } from '../types';
+
+const TOPIC_LENGTH = 32;
+
+export const filterUtils = {
+ generateUUID(): string {
+ return uuid();
+ },
+ getFilter(
+ address: string,
+ eventName: ContractEvents,
+ indexFilterValues: IndexedFilterValues,
+ abi: ContractAbi,
+ blockRange?: BlockRange,
+ ): FilterObject {
+ const eventAbi = _.find(abi, { name: eventName }) as EventAbi;
+ const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
+ const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
+ const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
+ const topics = [topicForEventSignature, ...topicsForIndexedArgs];
+ let filter: FilterObject = {
+ address,
+ topics,
+ };
+ if (!_.isUndefined(blockRange)) {
+ filter = {
+ ...blockRange,
+ ...filter,
+ };
+ }
+ return filter;
+ },
+ getEventSignatureFromAbiByName(eventAbi: EventAbi, eventName: ContractEvents): string {
+ const types = _.map(eventAbi.inputs, 'type');
+ const signature = `${eventAbi.name}(${types.join(',')})`;
+ return signature;
+ },
+ getTopicsForIndexedArgs(abi: EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> {
+ const topics: Array<string | null> = [];
+ for (const eventInput of abi.inputs) {
+ if (!eventInput.indexed) {
+ continue;
+ }
+ if (_.isUndefined(indexFilterValues[eventInput.name])) {
+ // Null is a wildcard topic in a JSON-RPC call
+ topics.push(null);
+ } else {
+ const value = indexFilterValues[eventInput.name] as string;
+ const buffer = ethUtil.toBuffer(value);
+ const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
+ const topic = ethUtil.bufferToHex(paddedBuffer);
+ topics.push(topic);
+ }
+ }
+ return topics;
+ },
+ matchesFilter(log: LogEntry, filter: FilterObject): boolean {
+ if (!_.isUndefined(filter.address) && log.address !== filter.address) {
+ return false;
+ }
+ if (!_.isUndefined(filter.topics)) {
+ return filterUtils.matchesTopics(log.topics, filter.topics);
+ }
+ return true;
+ },
+ matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
+ const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
+ const matchesTopics = _.every(matchesTopic);
+ return matchesTopics;
+ },
+ matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
+ if (_.isArray(filterTopic)) {
+ return _.includes(filterTopic, logTopic);
+ }
+ if (_.isString(filterTopic)) {
+ return filterTopic === logTopic;
+ }
+ // null topic is a wildcard
+ return true;
+ },
+};
diff --git a/packages/contract-wrappers/src/utils/order_validation_utils.ts b/packages/contract-wrappers/src/utils/order_validation_utils.ts
new file mode 100644
index 000000000..36dfbd800
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/order_validation_utils.ts
@@ -0,0 +1,198 @@
+import { getOrderHashHex, isValidSignature, OrderError } from '@0xproject/order-utils';
+import { ExchangeContractErrs, Order, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
+import { ContractWrappersError, TradeSide, TransferType } from '../types';
+import { constants } from '../utils/constants';
+import { utils } from '../utils/utils';
+
+import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
+
+export class OrderValidationUtils {
+ private _exchangeWrapper: ExchangeWrapper;
+ public static validateCancelOrderThrowIfInvalid(
+ order: Order,
+ cancelTakerTokenAmount: BigNumber,
+ unavailableTakerTokenAmount: BigNumber,
+ ): void {
+ if (cancelTakerTokenAmount.eq(0)) {
+ throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
+ }
+ if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
+ }
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
+ if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderCancelExpired);
+ }
+ }
+ public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator,
+ signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ senderAddress: string,
+ zrxTokenAddress: string,
+ ): Promise<void> {
+ const fillMakerTokenAmount = OrderValidationUtils._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 = OrderValidationUtils._getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxTokenAddress,
+ signedOrder.maker,
+ signedOrder.feeRecipient,
+ makerFeeAmount,
+ TradeSide.Maker,
+ TransferType.Fee,
+ );
+ const takerFeeAmount = OrderValidationUtils._getPartialAmount(
+ fillTakerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.takerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxTokenAddress,
+ senderAddress,
+ signedOrder.feeRecipient,
+ takerFeeAmount,
+ TradeSide.Taker,
+ TransferType.Fee,
+ );
+ }
+ private static _validateRemainingFillAmountNotZeroOrThrow(
+ takerTokenAmount: BigNumber,
+ unavailableTakerTokenAmount: BigNumber,
+ ) {
+ if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ }
+ }
+ private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
+ if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderFillExpired);
+ }
+ }
+ private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
+ const fillMakerTokenAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .round(0);
+ return fillMakerTokenAmount;
+ }
+ constructor(exchangeWrapper: ExchangeWrapper) {
+ this._exchangeWrapper = exchangeWrapper;
+ }
+ public async validateOrderFillableOrThrowAsync(
+ exchangeTradeEmulator: ExchangeTransferSimulator,
+ signedOrder: SignedOrder,
+ zrxTokenAddress: string,
+ expectedFillTakerTokenAmount?: BigNumber,
+ ): Promise<void> {
+ const orderHash = getOrderHashHex(signedOrder);
+ const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount,
+ unavailableTakerTokenAmount,
+ );
+ OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
+ let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ if (!_.isUndefined(expectedFillTakerTokenAmount)) {
+ fillTakerTokenAmount = expectedFillTakerTokenAmount;
+ }
+ await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount,
+ signedOrder.taker,
+ zrxTokenAddress,
+ );
+ }
+ 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 = getOrderHashHex(signedOrder);
+ if (!isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
+ throw new Error(OrderError.InvalidSignature);
+ }
+ const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount,
+ unavailableTakerTokenAmount,
+ );
+ if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
+ throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
+ }
+ OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
+ const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
+ ? remainingTakerTokenAmount
+ : fillTakerTokenAmount;
+ await OrderValidationUtils.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);
+ }
+ }
+}
diff --git a/packages/contract-wrappers/src/utils/utils.ts b/packages/contract-wrappers/src/utils/utils.ts
new file mode 100644
index 000000000..af1125632
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/utils.ts
@@ -0,0 +1,13 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const utils = {
+ spawnSwitchErr(name: string, value: any): Error {
+ return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
+ },
+ getCurrentUnixTimestampSec(): BigNumber {
+ return new BigNumber(Date.now() / 1000).round();
+ },
+ getCurrentUnixTimestampMs(): BigNumber {
+ return new BigNumber(Date.now());
+ },
+};