199 lines
8.1 KiB
TypeScript
199 lines
8.1 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|