aboutsummaryrefslogblamecommitdiffstats
path: root/packages/order-watcher/test/order_watcher_test.ts
blob: 41dc884d5145bfdc8ede7667cfbabe0d2d2cf613 (plain) (tree)
1
2
3
4
5
6
                                               




                                                                              






                         


                                               



                             



                                                             

                                                                  
 

                                               
                                                   
                                                             
 

                       

                           
                                                                 
 
                                
                                           


                                     







                                  
                                 
                                   
                                            
                                                                                    
                        
                                                           
                                               
                                                       




                                                                  
                                                                       
                                                     



                                          


                                          



                                                                                          

                                                                   
          
                                      
                                                                                                    


                                                
       





                                                

                                                                     
                                                                             



                               
                               
              
                                                                          
                                                          
                                                                        

                                         






                                                                                                                         

                                                                            

                                         

                                                                                                                      

                                                                           
                                                                             



                               
                               
              
                                                                          




                                                       
                                                           
           
       

                                        
                                       
           
                                                                      

                                                                                                                        

           

                                                                                                       







                                                                             
                                                                   
                                                          
                                                                   
                                                
                                                                   

           

                                                
                                       
                                                                          
                                                
           
                                                                                                                            

                                                                                 



                                   
                                   
                  
                                                                              
                                                              
                                                                                                                   
                                                             
                                                                              

                                                                                                                 
                   
                                                 




                                                                         

                             


                                                                                                                   



                                   
                                   
                  
                                                              
                                                                                                                    

                                                                                      
                                                 
                                                     
                                                  
                                                        

                                                                



                                   

                                  
                               

                             
                                                                                                                           

                                                                                 



                                   
                                   
                  
                                                                              
                                                              
                                                                                                                   
                                                             
                                                                              

                                                                                                               
                   
                                                 







                                                                                                                        




                                                                                                     



                                   
                                   
                  
                                                                              
                                                              
 
                                                                                                                   
                                                             
                                                                              

                                                                                                                   
                   
                                                 
 
                                                                                                          

                             

























                                                                                                                                   


                                                                                                       



                                   
                                   
                  
 
                                                                                                                        
                                                               
                                                                              
                                                              
 
                                                                                                                   
                                                            
                                                                          


                                                                                          
                                                                                          
                                                                                                       

                                          
                                                                                                       

                                          
                                                                                                         
                   
                                                 
                                                                                                                 

                             
                                                                                                             
                          

                                                                                          
                                                                                         

                                   

                             

                                 
                                   
                                 
                  
                                                                                        
                                                              
                                                 




                                                                         

                             
                                                                 

                                                                                       

                                                                                                          
                                                                                               



                                       
                                            

                                            
                                                                                                           
                                                                                  
                                                                  
                                                                                                                       
                                                                
                                                                              

                                                                                      
                                                                                                           
                                                                                      
                          
                                                                                                           
                                                                                     
                          
                       
                                                     
                                                                                                                     




                                                                                                       



                                       
                                       
                      
 
                                                                                                                
                                                                  
 
                                                                                                                       
                                                                              
                                                                                      
                                                                                                           

                                                       
                                                                                                           

                                                       
                       
                                                     


                                                                             

                                                   




                                                                                                     



                                       
                                       
                      
 



                                                                                           
 
                                                                                                     
                                                                             
                                                                  
 
                                                                                                                       
                                                                
                                                                              
                                                                                      
                                                                                                           

                                            
                                                                                                           

                                            
                       
                                                     


                                                                    


                                               

                                 

                                                                                                 

                                                                                              
                                                                                             

                                       

                                 

                                     


                                       
 
                                                                                                        
 
                                                                                                          
                                                                                   
                                                                  
 
                                                                                                                       
                                                                              
                                                                                      
                                                                                                           

                                               
                       
                                                     







                                                                             
                                               

                                            

                                 

                                                                                                             

                                                                                              
                                                                                             

                                       

                                 

                                     


                                       
 
                                                                  
 
                                                                                                                       
                                                                              
                                                                                      
                                                                                                           

                                           
                       
                                                     


                                                                             
                                                                                   
                      

                                 
           
                                                                                                  

                                                                                 



                                   
                                   
                  
                                                                              
                                                              
 
                                                                                                                   
                                                             
                                                                              
                                                                               
                                                                                                     
                   
                                                 
                                                                              

                             
                                                                                                                           
                          



                                                                                           



                                   

                                     
                  
                                                                              
                                                              
                                                                                                                   
                                                             
                                                                              

                                                                                                             
                   
                                                 
                                                                                                                 

                             




                                                                                  
                                                                                                          

























































































                                                                                                                                 























































































































































































                                                                                                                                                 










                                                                                                              
 






                                                                                                                       
 


                                                                                                              
           
       
                                         
// tslint:disable:no-unnecessary-type-assertion
import { ContractWrappers } from '@0x/contract-wrappers';
import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils';
import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils';
import { FillScenarios } from '@0x/fill-scenarios';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import {
    DoneCallback,
    ExchangeContractErrs,
    OrderState,
    OrderStateInvalid,
    OrderStateValid,
    SignedOrder,
} from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import * as _ from 'lodash';
import 'mocha';

import {
    DependentOrderHashesTracker,
    OrderHashesByERC20ByMakerAddress,
} from '../src/order_watcher/dependent_order_hashes_tracker';
import { OrderWatcher } from '../src/order_watcher/order_watcher';
import { OrderWatcherError } from '../src/types';

import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { migrateOnceAsync } from './utils/migrate';
import { provider, web3Wrapper } from './utils/web3_wrapper';

const TIMEOUT_MS = 150;

chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);

describe('OrderWatcher', () => {
    let contractWrappers: ContractWrappers;
    let fillScenarios: FillScenarios;
    let userAddresses: string[];
    let zrxTokenAddress: string;
    let makerAssetData: string;
    let takerAssetData: string;
    let makerTokenAddress: string;
    let takerTokenAddress: string;
    let makerAddress: string;
    let takerAddress: string;
    let coinbase: string;
    let feeRecipient: string;
    let signedOrder: SignedOrder;
    let orderWatcher: OrderWatcher;
    const decimals = constants.ZRX_DECIMALS;
    const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals);
    before(async () => {
        const contractAddresses = await migrateOnceAsync();
        await blockchainLifecycle.startAsync();
        const networkId = constants.TESTRPC_NETWORK_ID;
        const config = {
            networkId,
            contractAddresses,
        };
        contractWrappers = new ContractWrappers(provider, config);
        userAddresses = await web3Wrapper.getAvailableAddressesAsync();
        zrxTokenAddress = contractAddresses.zrxToken;
        fillScenarios = new FillScenarios(
            provider,
            userAddresses,
            zrxTokenAddress,
            contractAddresses.exchange,
            contractAddresses.erc20Proxy,
            contractAddresses.erc721Proxy,
        );
        [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
        [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
        [makerAssetData, takerAssetData] = [
            assetDataUtils.encodeERC20AssetData(makerTokenAddress),
            assetDataUtils.encodeERC20AssetData(takerTokenAddress),
        ];
        const orderWatcherConfig = {};
        orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig);
    });
    after(async () => {
        await blockchainLifecycle.revertAsync();
    });
    beforeEach(async () => {
        await blockchainLifecycle.startAsync();
    });
    afterEach(async () => {
        await blockchainLifecycle.revertAsync();
    });
    describe('#removeOrder', async () => {
        it('should successfully remove existing order', async () => {
            signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                makerAssetData,
                takerAssetData,
                makerAddress,
                takerAddress,
                fillableAmount,
            );
            const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
            await orderWatcher.addOrderAsync(signedOrder);
            expect((orderWatcher as any)._orderByOrderHash).to.include({
                [orderHash]: signedOrder,
            });
            const dependentOrderHashesTracker = (orderWatcher as any)
                ._dependentOrderHashesTracker as DependentOrderHashesTracker;
            let orderHashesByERC20ByMakerAddress: OrderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any)
                ._orderHashesByERC20ByMakerAddress;
            expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][makerTokenAddress]).to.have.keys(
                orderHash,
            );
            orderWatcher.removeOrder(orderHash);
            expect((orderWatcher as any)._orderByOrderHash).to.not.include({
                [orderHash]: signedOrder,
            });
            orderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any)._orderHashesByERC20ByMakerAddress;
            expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress]).to.be.undefined();
        });
        it('should no-op when removing a non-existing order', async () => {
            signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                makerAssetData,
                takerAssetData,
                makerAddress,
                takerAddress,
                fillableAmount,
            );
            const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
            const nonExistentOrderHash = `0x${orderHash
                .substr(2)
                .split('')
                .reverse()
                .join('')}`;
            orderWatcher.removeOrder(nonExistentOrderHash);
        });
    });
    describe('#subscribe', async () => {
        afterEach(async () => {
            orderWatcher.unsubscribe();
        });
        it('should fail when trying to subscribe twice', async () => {
            orderWatcher.subscribe(_.noop.bind(_));
            expect(() => orderWatcher.subscribe(_.noop.bind(_))).to.throw(OrderWatcherError.SubscriptionAlreadyPresent);
        });
    });
    describe('#getStats', async () => {
        it('orderCount should increment and decrement with order additions and removals', async () => {
            signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                makerAssetData,
                takerAssetData,
                makerAddress,
                takerAddress,
                fillableAmount,
            );
            const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
            expect(orderWatcher.getStats().orderCount).to.be.eq(0);
            await orderWatcher.addOrderAsync(signedOrder);
            expect(orderWatcher.getStats().orderCount).to.be.eq(1);
            orderWatcher.removeOrder(orderHash);
            expect(orderWatcher.getStats().orderCount).to.be.eq(0);
        });
    });
    describe('tests with cleanup', async () => {
        afterEach(async () => {
            orderWatcher.unsubscribe();
            const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
            orderWatcher.removeOrder(orderHash);
        });
        it('should emit orderStateInvalid when makerAddress allowance set to 0 for watched order', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                    expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                });
                orderWatcher.subscribe(callback);
                await contractWrappers.erc20Token.setProxyAllowanceAsync(
                    makerTokenAddress,
                    makerAddress,
                    new BigNumber(0),
                );
            })().catch(done);
        });
        it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                await orderWatcher.addOrderAsync(signedOrder);
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => {
                    throw new Error('OrderState callback fired for irrelevant order');
                });
                orderWatcher.subscribe(callback);
                const notTheMaker = userAddresses[0];
                const anyRecipient = takerAddress;
                const transferAmount = new BigNumber(2);
                await contractWrappers.erc20Token.transferAsync(
                    makerTokenAddress,
                    notTheMaker,
                    anyRecipient,
                    transferAmount,
                );
                setTimeout(() => {
                    done();
                }, TIMEOUT_MS);
            })().catch(done);
        });
        it('should emit orderStateInvalid when makerAddress moves balance backing watched order', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                    expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
                });
                orderWatcher.subscribe(callback);
                const anyRecipient = takerAddress;
                const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
                await contractWrappers.erc20Token.transferAsync(
                    makerTokenAddress,
                    makerAddress,
                    anyRecipient,
                    makerBalance,
                );
            })().catch(done);
        });
        it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);

                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                    expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
                });
                orderWatcher.subscribe(callback);

                await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress);
            })().catch(done);
        });
        it('should include transactionHash in emitted orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                await orderWatcher.addOrderAsync(signedOrder);

                let transactionHash: string;
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.transactionHash).to.be.equal(transactionHash);
                });
                orderWatcher.subscribe(callback);

                transactionHash = await contractWrappers.exchange.fillOrderAsync(
                    signedOrder,
                    fillableAmount,
                    takerAddress,
                );
            })().catch(done);
        });
        it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );

                const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
                const fillAmountInBaseUnits = new BigNumber(2);
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);

                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.true();
                    const validOrderState = orderState as OrderStateValid;
                    expect(validOrderState.orderHash).to.be.equal(orderHash);
                    const orderRelevantState = validOrderState.orderRelevantState;
                    const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits);
                    const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
                    expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                        remainingFillable,
                    );
                    expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
                        remainingFillable,
                    );
                    expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
                });
                orderWatcher.subscribe(callback);
                await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
            })().catch(done);
        });
        it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
            (async () => {
                const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
                const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
                signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
                    makerAssetData,
                    takerAssetData,
                    makerFee,
                    takerFee,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                    takerAddress,
                );
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)();
                await orderWatcher.addOrderAsync(signedOrder);
                orderWatcher.subscribe(callback);
                await contractWrappers.erc20Token.setProxyAllowanceAsync(
                    zrxTokenAddress,
                    makerAddress,
                    new BigNumber(0),
                );
            })().catch(done);
        });
        describe('remainingFillable(M|T)akerTokenAmount', () => {
            it('should calculate correct remaining fillable', (done: DoneCallback) => {
                (async () => {
                    const takerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), decimals);
                    const makerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(20), decimals);
                    signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
                        makerAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        makerFillableAmount,
                        takerFillableAmount,
                    );
                    const fillAmountInBaseUnits = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.true();
                        const validOrderState = orderState as OrderStateValid;
                        expect(validOrderState.orderHash).to.be.equal(orderHash);
                        const orderRelevantState = validOrderState.orderRelevantState;
                        expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                            Web3Wrapper.toBaseUnitAmount(new BigNumber(16), decimals),
                        );
                        expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
                            Web3Wrapper.toBaseUnitAmount(new BigNumber(8), decimals),
                        );
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
                })().catch(done);
            });
            it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                    );

                    const changedMakerApprovalAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals);
                    await orderWatcher.addOrderAsync(signedOrder);

                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        const validOrderState = orderState as OrderStateValid;
                        const orderRelevantState = validOrderState.orderRelevantState;
                        expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                            changedMakerApprovalAmount,
                        );
                        expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
                            changedMakerApprovalAmount,
                        );
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc20Token.setProxyAllowanceAsync(
                        makerTokenAddress,
                        makerAddress,
                        changedMakerApprovalAmount,
                    );
                })().catch(done);
            });
            it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                    );

                    const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(
                        makerTokenAddress,
                        makerAddress,
                    );

                    const remainingAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals);
                    const transferAmount = makerBalance.sub(remainingAmount);
                    await orderWatcher.addOrderAsync(signedOrder);

                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.true();
                        const validOrderState = orderState as OrderStateValid;
                        const orderRelevantState = validOrderState.orderRelevantState;
                        expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                            remainingAmount,
                        );
                        expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
                            remainingAmount,
                        );
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc20Token.transferAsync(
                        makerTokenAddress,
                        makerAddress,
                        constants.NULL_ADDRESS,
                        transferAmount,
                    );
                })().catch(done);
            });
            it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => {
                (async () => {
                    const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
                    const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals);
                    signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
                        makerAssetData,
                        takerAssetData,
                        makerFee,
                        takerFee,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                        feeRecipient,
                    );

                    const remainingFeeAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals);

                    const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals);
                    const transferTokenAmount = makerFee.sub(remainingTokenAmount);
                    await orderWatcher.addOrderAsync(signedOrder);

                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        const validOrderState = orderState as OrderStateValid;
                        const orderRelevantState = validOrderState.orderRelevantState;
                        expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                            remainingFeeAmount,
                        );
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc20Token.setProxyAllowanceAsync(
                        zrxTokenAddress,
                        makerAddress,
                        remainingFeeAmount,
                    );
                    await contractWrappers.erc20Token.transferAsync(
                        makerTokenAddress,
                        makerAddress,
                        constants.NULL_ADDRESS,
                        transferTokenAmount,
                    );
                })().catch(done);
            });
            it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => {
                (async () => {
                    const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
                    const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
                    signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
                        makerAssetData,
                        takerAssetData,
                        makerFee,
                        takerFee,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                        feeRecipient,
                    );

                    await orderWatcher.addOrderAsync(signedOrder);

                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        const validOrderState = orderState as OrderStateValid;
                        const orderRelevantState = validOrderState.orderRelevantState;
                        expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
                            fillableAmount,
                        );
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc20Token.setProxyAllowanceAsync(
                        makerTokenAddress,
                        makerAddress,
                        Web3Wrapper.toBaseUnitAmount(new BigNumber(100), decimals),
                    );
                })().catch(done);
            });
        });
        it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
            (async () => {
                signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    fillableAmount,
                );
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);

                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                    expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderCancelled);
                });
                orderWatcher.subscribe(callback);
                await contractWrappers.exchange.cancelOrderAsync(signedOrder);
            })().catch(done);
        });
        it('should emit orderStateInvalid when within rounding error range after a partial fill', (done: DoneCallback) => {
            (async () => {
                const fillAmountInBaseUnits = new BigNumber(2);
                const makerAssetAmount = new BigNumber(1001);
                const takerAssetAmount = new BigNumber(3);
                signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
                    makerAssetData,
                    takerAssetData,
                    makerAddress,
                    takerAddress,
                    makerAssetAmount,
                    takerAssetAmount,
                );
                const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                await orderWatcher.addOrderAsync(signedOrder);
                const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                    expect(orderState.isValid).to.be.false();
                    const invalidOrderState = orderState as OrderStateInvalid;
                    expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                    expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
                });
                orderWatcher.subscribe(callback);
                await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
            })().catch(done);
        });
        describe('erc721', () => {
            let makerErc721AssetData: string;
            let makerErc721TokenAddress: string;
            const tokenId = new BigNumber(42);
            [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
            makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId);
            const fillableErc721Amount = new BigNumber(1);
            it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerErc721AssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc721Token.setApprovalAsync(
                        makerErc721TokenAddress,
                        constants.NULL_ADDRESS,
                        tokenId,
                    );
                })().catch(done);
            });
            it('should emit orderStateInvalid when maker allowance for all set to 0 for watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerErc721AssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    await contractWrappers.erc721Token.setApprovalAsync(
                        makerErc721TokenAddress,
                        constants.NULL_ADDRESS,
                        tokenId,
                    );
                    let isApproved = true;
                    await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
                        makerErc721TokenAddress,
                        makerAddress,
                        isApproved,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                    });
                    orderWatcher.subscribe(callback);
                    isApproved = false;
                    await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
                        makerErc721TokenAddress,
                        makerAddress,
                        isApproved,
                    );
                })().catch(done);
            });
            it('should emit orderStateInvalid when maker moves NFT backing watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerErc721AssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc721Token.transferFromAsync(
                        makerErc721TokenAddress,
                        coinbase,
                        makerAddress,
                        tokenId,
                    );
                })().catch(done);
            });
        });
        describe('multiAsset', async () => {
            const tokenId = new BigNumber(42);
            const [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
            const makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId);
            const fillableErc721Amount = new BigNumber(1);
            const [makerErc20TokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
            const makerErc20AssetData = assetDataUtils.encodeERC20AssetData(makerErc20TokenAddress);
            const fillableErc20Amount = new BigNumber(2);
            const multiAssetAmounts = [fillableErc721Amount, fillableErc20Amount];
            const nestedAssetData = [makerErc721AssetData, makerErc20AssetData];
            const makerMultiAssetData = assetDataUtils.encodeMultiAssetData(multiAssetAmounts, nestedAssetData);
            it('should emit orderStateInvalid when maker allowance of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc721Token.setApprovalAsync(
                        makerErc721TokenAddress,
                        constants.NULL_ADDRESS,
                        tokenId,
                    );
                })().catch(done);
            });
            it('should emit orderStateInvalid when maker allowance for all of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    await contractWrappers.erc721Token.setApprovalAsync(
                        makerErc721TokenAddress,
                        constants.NULL_ADDRESS,
                        tokenId,
                    );
                    let isApproved = true;
                    await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
                        makerErc721TokenAddress,
                        makerAddress,
                        isApproved,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                    });
                    orderWatcher.subscribe(callback);
                    isApproved = false;
                    await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
                        makerErc721TokenAddress,
                        makerAddress,
                        isApproved,
                    );
                })().catch(done);
            });
            it('should emit orderStateInvalid when maker moves ERC721 backing watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc721Token.transferFromAsync(
                        makerErc721TokenAddress,
                        coinbase,
                        makerAddress,
                        tokenId,
                    );
                })().catch(done);
            });
            it('should emit orderStateInvalid when maker allowance of ERC20 token set to 0 for watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableErc721Amount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
                    });
                    orderWatcher.subscribe(callback);
                    await contractWrappers.erc20Token.setProxyAllowanceAsync(
                        makerErc20TokenAddress,
                        makerAddress,
                        new BigNumber(0),
                    );
                })().catch(done);
            });
            it('should not emit an orderState event when irrelevant ERC20 Transfer event received', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                    );
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => {
                        throw new Error('OrderState callback fired for irrelevant order');
                    });
                    orderWatcher.subscribe(callback);
                    const notTheMaker = userAddresses[0];
                    const anyRecipient = takerAddress;
                    const transferAmount = new BigNumber(2);
                    await contractWrappers.erc20Token.transferAsync(
                        makerTokenAddress,
                        notTheMaker,
                        anyRecipient,
                        transferAmount,
                    );
                    setTimeout(() => {
                        done();
                    }, TIMEOUT_MS);
                })().catch(done);
            });
            it('should emit orderStateInvalid when makerAddress moves ERC20 balance backing watched order', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);
                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
                    });
                    orderWatcher.subscribe(callback);
                    const anyRecipient = takerAddress;
                    const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(
                        makerTokenAddress,
                        makerAddress,
                    );
                    await contractWrappers.erc20Token.transferAsync(
                        makerTokenAddress,
                        makerAddress,
                        anyRecipient,
                        makerBalance,
                    );
                })().catch(done);
            });
            // TODO(abandeali1): The following test will fail until the MAP has been deployed and activated.
            it.skip('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
                (async () => {
                    signedOrder = await fillScenarios.createFillableSignedOrderAsync(
                        makerMultiAssetData,
                        takerAssetData,
                        makerAddress,
                        takerAddress,
                        fillableAmount,
                    );
                    const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
                    await orderWatcher.addOrderAsync(signedOrder);

                    const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
                        expect(orderState.isValid).to.be.false();
                        const invalidOrderState = orderState as OrderStateInvalid;
                        expect(invalidOrderState.orderHash).to.be.equal(orderHash);
                        expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
                    });
                    orderWatcher.subscribe(callback);

                    await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress);
                })().catch(done);
            });
        });
    });
}); // tslint:disable:max-file-line-count