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


                                                                      
                                                                        
 
                                   
                                       

                                                                               
                                         

                                               
                                                   
                                                                                                            
                                                                  

                                                                                                                 



                                                                                    



                                                                                                   




                                                           



                                                                                                      




                                                     
                                                      
                                                                                         
                                              

          

                                                                                   

                                                              
                                         


                                                                      


                                                                                              
                                                                                                                 


                                                                      


                                                                                                 
                                                                                    



                                                                                                          
                                                                          
                                                                                                      


                                                                                        
                                                                                               



                                                                         
                                      
     

                                                                                   
                                                                                                        

                                                                                                    
          
                                                              


                                                                                  
                                                                     

                                                                                               



                                                                        
                                                                     





                                                                                 

                                                                        
                                                                                   
                                                                                                         




                                                           
                                                      
                                                                                                  
                                                
          
                                                      
                                                                                                  

                                                




                                                     
                                                      
                                                                                                          

                             




                                                     
                                                      
                                                                                                      
                             
          
     
                                                      
                                                                            




                                                                               
                                                                                   




                                                                           

                                                                          



                                                      

                                    
 
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;
    }
}