aboutsummaryrefslogblamecommitdiffstats
path: root/packages/0x.js/src/utils/order_validation_utils.ts
blob: b320a3e924d8ed67f85f2e7dd5470d0dc5fdc07c (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                      
                                             

                            

                                                                        
                                                                                      

                                               
 
                                                                          
 
                                   
                                              
                                                    


                                               












                                                                                 




                                                         
                      
                                                                            




                                                      





                                          

                                                      





                                          
          
                                                                      




                                                      




                                     

                             
                                                                      




                                                      




                                     


                             
                                                              

                                               




                                                                               
                                                                                           




                                                                           
                                                                                                                  
                                              


                             

                                    
                                                   
                                                
     
                                                   




                                                         
                                                             

                                                                                                                  

                                         
          
                                                                                                     



                                                                                                   


                                                                                          
                                 
                              
                            

          
                                                      





                                                         
                                         


                                                                      


                                                                                              

                                                                                                                  

                                         
          


                                                                                                 
                                                                                                     
                                                                                                          


                                                                                               
                                                                                          




                                   

          
                                                                                         


                                         



                                                                         
                                      
     
                                                            





                                                         
                                                                                       




                                  
          
                                                              


                                                                                  
 
import { Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';

import { ZeroEx } from '../0x';
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
import { ExchangeContractErrs, TradeSide, TransferType, ZeroExError } 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 = utils.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 = utils.getOrderHashHex(signedOrder);
        if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
            throw new Error(ZeroExError.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);
        }
    }
}