aboutsummaryrefslogblamecommitdiffstats
path: root/contracts/protocol/test/asset_proxy/proxies.ts
blob: c4bd95905091cfd5e4c83e098afe6f1b94fefcb4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
                                                                                                                     









                                              







                                          



                                                    
                             
                            
 

                                                                            
                                                                                     
                                                

                                                        



                                                                 
                                                    
                                                       


                           
                                                  
                                                      


                           
 
                                               
                                          
                      
                              


                            
 



                                               
                                                    

                                         
                                                            
                                                                        
                                                 


                                     

                                      

                        





                                                
                                                                        
                                                                                                                     
 

                                                                          
 
                              
                                                           







                                                                                  
                                                       






                                                                                                 



                                                 
 
                                
                                                       
                                                                                     



                                                 































                                                                                                  
          
                                                                                             
                                                    






                                               
                                                                                                         
                                                          








                                                           
                                                       
                                                                                                                   





                                                                  
                                      


                                                 

                                                                           
                            







                                                                        
                                      


                                                 



                                                                                     
                                                







                                                                                  






                                                

                                  










                                                                         




                                                                    

                                                                   
                                             

                                                                                                  

                                                                            

                                                                                           

                                

                           
                                                               


                                                            
                                         
                       
                                                         


                                                                          

                                                                                            
                  

                                                                                          
                  
               
 


                                                                                                         


                                                                                                     


                                                                                           

                                





                                                               
                                         



                                                         



                                                                                                 




                                                                                              

                                                                                                                   



                                                                                           

                                





                                                               
                                         




                                                                          

                                                                                            
                  

                                                                                          


                  
                                                                                     
                                             

                                                                                                  

                                                                            

                                                                                           

                                

                           
                                                               


                                                            
                                         
                       
                                                         


                                                                          

                                                                                            
                  

                                                                                          


                  
                                                                       
                                             
                                                                                                  

                                                                                      


                                                                                           

                                

                           
                                                               

                                                                                                   


                                                         
                                                                            
                                                           
                                                   


                                                      
                                         
                       
                                                
                  



                                                                          
                                                                                                         






                                                                                                         

                                



                                                                                                          
                                          


                                                         

                                                                                                     




                                                           
                                         


                                                



                                                                                                 

               
                                                                         
                                             

                                                                                                  
                                                 

                                                                                           

                                

                           
                                                                            
                                                   


                                                      

                                            
                                                     
                  

                                                                          
               
 
                                                                                 




                                                                                                               

                                

                           

                                                                                                           




                                                           
                                         


                                                



                                                                                                       
               
           

       
                                   










                                                                         




                                                                     

                                                                   
                                              
                                                                                                                        
                                       


                                                                                                


                                                                                           

                                





                                                               
                                         



                                                         

                                                                                                   





                                                                                              

                                         

                                       


                                                                                                
                                                

                                                                                           

                                

                           
                                                               


                                                            
                                         
                       
                                                         

                                                 

                                                                                                   

               
                                                                                                      
                                              
                                                                                                                        
                                       


                                                                                                
                                                
                                                                                           
                                     
                                

                                           
                  
                                                                                                     



                                                                      
                                         
                                                             

                       
                                                                    
                                                      
                                                 

                                                                                                   

               
                                                                                 
                                              
                                                                                                                        
                                       


                                                                                                
                                                

                                                                                           

                                

                           
                                                   


                                                      
                                         
                       
                                               
                  

                                                                                          

               
                                                                                   
                                              
                                                                                                                        
                                       


                                                                                                
                                                  

                                                                                           

                                

                           
                                                   


                                                      
                                         
                       
                                               
                  

                                                                                          

               
                                                                       
                                              
                                                                                                                        
                                       


                                                                                                
                                                               

                                                                                                                 


                                                         

                                                           

                                                                                           

                                

                           
                                                   


                                                      
                                         
                       
                                                
                  

                                                                                          

               
                                                                         
                                              
                                                                                                                        
                                       


                                                                                                
                                                

                                                                                           

                                

                           
                                                   




                                                      
                                                     
                  

                                                                                          
               
           
       
                                       












                                                                         
                                                                            
                                                 
                                                      
           






















                                                                                                                       
































                                                                                                                       
































                                                                                                                       














































































































                                                                                                                        



















































































































































































































































































































































                                                                                                                        
           

       
                                              
                                     
import { artifacts as interfacesArtifacts, IAssetDataContract, IAssetProxyContract } from '@0x/contracts-interfaces';
import {
    chaiSetup,
    constants,
    expectTransactionFailedAsync,
    expectTransactionFailedWithoutReasonAsync,
    LogDecoder,
    provider,
    txDefaults,
    web3Wrapper,
} from '@0x/contracts-test-utils';
import {
    artifacts as tokensArtifacts,
    DummyERC20TokenContract,
    DummyERC721ReceiverContract,
    DummyERC721TokenContract,
    DummyMultipleReturnERC20TokenContract,
    DummyNoReturnERC20TokenContract,
} from '@0x/contracts-tokens';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { assetDataUtils } from '@0x/order-utils';
import { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as _ from 'lodash';

import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy';
import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy';
import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy';
import { artifacts } from '../../src/artifacts';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';

chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const assetProxyInterface = new IAssetProxyContract(
    interfacesArtifacts.IAssetProxy.compilerOutput.abi,
    constants.NULL_ADDRESS,
    provider,
);
const assetDataInterface = new IAssetDataContract(
    interfacesArtifacts.IAssetData.compilerOutput.abi,
    constants.NULL_ADDRESS,
    provider,
);

// tslint:disable:no-unnecessary-type-assertion
describe('Asset Transfer Proxies', () => {
    let owner: string;
    let notAuthorized: string;
    let authorized: string;
    let fromAddress: string;
    let toAddress: string;

    let erc20TokenA: DummyERC20TokenContract;
    let erc20TokenB: DummyERC20TokenContract;
    let erc721TokenA: DummyERC721TokenContract;
    let erc721TokenB: DummyERC721TokenContract;
    let erc721Receiver: DummyERC721ReceiverContract;
    let erc20Proxy: ERC20ProxyContract;
    let erc721Proxy: ERC721ProxyContract;
    let noReturnErc20Token: DummyNoReturnERC20TokenContract;
    let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract;
    let multiAssetProxy: MultiAssetProxyContract;

    let erc20Wrapper: ERC20Wrapper;
    let erc721Wrapper: ERC721Wrapper;
    let erc721AFromTokenId: BigNumber;
    let erc721BFromTokenId: BigNumber;

    before(async () => {
        await blockchainLifecycle.startAsync();
    });
    after(async () => {
        await blockchainLifecycle.revertAsync();
    });
    before(async () => {
        const accounts = await web3Wrapper.getAvailableAddressesAsync();
        const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5));

        erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
        erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);

        // Deploy AssetProxies
        erc20Proxy = await erc20Wrapper.deployProxyAsync();
        erc721Proxy = await erc721Wrapper.deployProxyAsync();
        multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
            artifacts.MultiAssetProxy,
            provider,
            txDefaults,
        );

        // Configure ERC20Proxy
        await web3Wrapper.awaitTransactionSuccessAsync(
            await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );

        // Configure ERC721Proxy
        await web3Wrapper.awaitTransactionSuccessAsync(
            await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );

        // Configure MultiAssetProxy
        await web3Wrapper.awaitTransactionSuccessAsync(
            await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, {
                from: owner,
            }),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );

        // Deploy and configure ERC20 tokens
        const numDummyErc20ToDeploy = 2;
        [erc20TokenA, erc20TokenB] = await erc20Wrapper.deployDummyTokensAsync(
            numDummyErc20ToDeploy,
            constants.DUMMY_TOKEN_DECIMALS,
        );
        noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync(
            tokensArtifacts.DummyNoReturnERC20Token,
            provider,
            txDefaults,
            constants.DUMMY_TOKEN_NAME,
            constants.DUMMY_TOKEN_SYMBOL,
            constants.DUMMY_TOKEN_DECIMALS,
            constants.DUMMY_TOKEN_TOTAL_SUPPLY,
        );
        multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync(
            tokensArtifacts.DummyMultipleReturnERC20Token,
            provider,
            txDefaults,
            constants.DUMMY_TOKEN_NAME,
            constants.DUMMY_TOKEN_SYMBOL,
            constants.DUMMY_TOKEN_DECIMALS,
            constants.DUMMY_TOKEN_TOTAL_SUPPLY,
        );

        await erc20Wrapper.setBalancesAndAllowancesAsync();
        await web3Wrapper.awaitTransactionSuccessAsync(
            await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await noReturnErc20Token.approve.sendTransactionAsync(
                erc20Proxy.address,
                constants.INITIAL_ERC20_ALLOWANCE,
                { from: fromAddress },
            ),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await multipleReturnErc20Token.setBalance.sendTransactionAsync(
                fromAddress,
                constants.INITIAL_ERC20_BALANCE,
            ),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );
        await web3Wrapper.awaitTransactionSuccessAsync(
            await multipleReturnErc20Token.approve.sendTransactionAsync(
                erc20Proxy.address,
                constants.INITIAL_ERC20_ALLOWANCE,
                { from: fromAddress },
            ),
            constants.AWAIT_TRANSACTION_MINED_MS,
        );

        // Deploy and configure ERC721 tokens and receiver
        [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync();
        erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync(
            tokensArtifacts.DummyERC721Receiver,
            provider,
            txDefaults,
        );

        await erc721Wrapper.setBalancesAndAllowancesAsync();
        const erc721Balances = await erc721Wrapper.getBalancesAsync();
        erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0];
        erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0];
    });
    beforeEach(async () => {
        await blockchainLifecycle.startAsync();
    });
    afterEach(async () => {
        await blockchainLifecycle.revertAsync();
    });

    describe('ERC20Proxy', () => {
        it('should revert if undefined function is called', async () => {
            const undefinedSelector = '0x01020304';
            await expectTransactionFailedWithoutReasonAsync(
                web3Wrapper.sendTransactionAsync({
                    from: owner,
                    to: erc20Proxy.address,
                    value: constants.ZERO_AMOUNT,
                    data: undefinedSelector,
                }),
            );
        });
        it('should have an id of 0xf47261b0', async () => {
            const proxyId = await erc20Proxy.getProxyId.callAsync();
            const expectedProxyId = '0xf47261b0';
            expect(proxyId).to.equal(expectedProxyId);
        });
        describe('transferFrom', () => {
            it('should successfully transfer tokens', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                // Perform a transfer from fromAddress to toAddress
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newBalances = await erc20Wrapper.getBalancesAsync();
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(amount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(amount),
                );
            });

            it('should successfully transfer tokens that do not return a value', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address);
                // Perform a transfer from fromAddress to toAddress
                const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
                const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
                const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
                expect(newFromBalance).to.be.bignumber.equal(initialFromBalance.minus(amount));
                expect(newToBalance).to.be.bignumber.equal(initialToBalance.plus(amount));
            });

            it('should successfully transfer tokens and ignore extra assetData', async () => {
                // Construct ERC20 asset data
                const extraData = '0102030405060708';
                const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(erc20TokenA.address)}${extraData}`;
                // Perform a transfer from fromAddress to toAddress
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newBalances = await erc20Wrapper.getBalancesAsync();
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(amount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(amount),
                );
            });

            it('should do nothing if transferring 0 amount of a token', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                // Perform a transfer from fromAddress to toAddress
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                const amount = new BigNumber(0);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newBalances = await erc20Wrapper.getBalancesAsync();
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address],
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address],
                );
            });

            it('should revert if allowances are too low', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                // Create allowance less than transfer amount. Set allowance on proxy.
                const allowance = new BigNumber(0);
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
                        from: fromAddress,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                // Perform a transfer; expect this to fail.
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.TransferFailed,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                expect(newBalances).to.deep.equal(erc20Balances);
            });

            it('should revert if allowances are too low and token does not return a value', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address);
                // Create allowance less than transfer amount. Set allowance on proxy.
                const allowance = new BigNumber(0);
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
                        from: fromAddress,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
                const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
                // Perform a transfer; expect this to fail.
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.TransferFailed,
                );
                const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
                const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
                expect(newFromBalance).to.be.bignumber.equal(initialFromBalance);
                expect(newToBalance).to.be.bignumber.equal(initialToBalance);
            });

            it('should revert if caller is not authorized', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: notAuthorized,
                    }),
                    RevertReason.SenderNotAuthorized,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                expect(newBalances).to.deep.equal(erc20Balances);
            });

            it('should revert if token returns more than 32 bytes', async () => {
                // Construct ERC20 asset data
                const encodedAssetData = assetDataUtils.encodeERC20AssetData(multipleReturnErc20Token.address);
                const amount = new BigNumber(10);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                const initialFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress);
                const initialToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress);
                // Perform a transfer; expect this to fail.
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc20Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.TransferFailed,
                );
                const newFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress);
                const newToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress);
                expect(newFromBalance).to.be.bignumber.equal(initialFromBalance);
                expect(newToBalance).to.be.bignumber.equal(initialToBalance);
            });
        });
    });

    describe('ERC721Proxy', () => {
        it('should revert if undefined function is called', async () => {
            const undefinedSelector = '0x01020304';
            await expectTransactionFailedWithoutReasonAsync(
                web3Wrapper.sendTransactionAsync({
                    from: owner,
                    to: erc721Proxy.address,
                    value: constants.ZERO_AMOUNT,
                    data: undefinedSelector,
                }),
            );
        });
        it('should have an id of 0x02571792', async () => {
            const proxyId = await erc721Proxy.getProxyId.callAsync();
            const expectedProxyId = '0x02571792';
            expect(proxyId).to.equal(expectedProxyId);
        });
        describe('transferFrom', () => {
            it('should successfully transfer tokens', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(1);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress);
            });

            it('should successfully transfer tokens and ignore extra assetData', async () => {
                // Construct ERC721 asset data
                const extraData = '0102030405060708';
                const encodedAssetData = `${assetDataUtils.encodeERC721AssetData(
                    erc721TokenA.address,
                    erc721AFromTokenId,
                )}${extraData}`;
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(1);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Verify transfer was successful
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress);
            });

            it('should not call onERC721Received when transferring to a smart contract', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(1);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    erc721Receiver.address,
                    amount,
                );
                const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts });
                const tx = await logDecoder.getTxWithDecodedLogsAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                        gas: constants.MAX_TRANSFER_FROM_GAS,
                    }),
                );
                // Verify that no log was emitted by erc721 receiver
                expect(tx.logs.length).to.be.equal(1);
                // Verify transfer was successful
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address);
            });

            it('should revert if transferring 0 amount of a token', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(0);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.InvalidAmount,
                );
                const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwner).to.be.equal(ownerFromAsset);
            });

            it('should revert if transferring > 1 amount of a token', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(500);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.InvalidAmount,
                );
                const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwner).to.be.equal(ownerFromAsset);
            });

            it('should revert if allowances are too low', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Remove transfer approval for fromAddress.
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, {
                        from: fromAddress,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                // Perform a transfer; expect this to fail.
                const amount = new BigNumber(1);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.TransferFailed,
                );
                const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwner).to.be.equal(ownerFromAsset);
            });

            it('should revert if caller is not authorized', async () => {
                // Construct ERC721 asset data
                const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                // Verify pre-condition
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                // Perform a transfer from fromAddress to toAddress
                const amount = new BigNumber(1);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    encodedAssetData,
                    fromAddress,
                    toAddress,
                    amount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: erc721Proxy.address,
                        data,
                        from: notAuthorized,
                    }),
                    RevertReason.SenderNotAuthorized,
                );
                const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwner).to.be.equal(ownerFromAsset);
            });
        });
    });
    describe('MultiAssetProxy', () => {
        it('should revert if undefined function is called', async () => {
            const undefinedSelector = '0x01020304';
            await expectTransactionFailedWithoutReasonAsync(
                web3Wrapper.sendTransactionAsync({
                    from: owner,
                    to: multiAssetProxy.address,
                    value: constants.ZERO_AMOUNT,
                    data: undefinedSelector,
                }),
            );
        });
        it('should have an id of 0x94cfcdd7', async () => {
            const proxyId = await multiAssetProxy.getProxyId.callAsync();
            // first 4 bytes of `keccak256('MultiAsset(uint256[],bytes[])')`
            const expectedProxyId = '0x94cfcdd7';
            expect(proxyId).to.equal(expectedProxyId);
        });
        describe('transferFrom', () => {
            it('should transfer a single ERC20 token', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const amounts = [erc20Amount];
                const nestedAssetData = [erc20AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalAmount = inputAmount.times(erc20Amount);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalAmount),
                );
            });
            it('should successfully transfer multiple of the same ERC20 token', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount1 = new BigNumber(10);
                const erc20Amount2 = new BigNumber(20);
                const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const amounts = [erc20Amount1, erc20Amount2];
                const nestedAssetData = [erc20AssetData1, erc20AssetData2];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalAmount = inputAmount.times(erc20Amount1).plus(inputAmount.times(erc20Amount2));
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalAmount),
                );
            });
            it('should successfully transfer multiple different ERC20 tokens', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount1 = new BigNumber(10);
                const erc20Amount2 = new BigNumber(20);
                const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address);
                const amounts = [erc20Amount1, erc20Amount2];
                const nestedAssetData = [erc20AssetData1, erc20AssetData2];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalErc20AAmount = inputAmount.times(erc20Amount1);
                const totalErc20BAmount = inputAmount.times(erc20Amount2);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount),
                );
                expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount),
                );
                expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount),
                );
            });
            it('should transfer a single ERC721 token', async () => {
                const inputAmount = new BigNumber(1);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc721Amount];
                const nestedAssetData = [erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.equal(toAddress);
            });
            it('should successfully transfer multiple of the same ERC721 token', async () => {
                const erc721Balances = await erc721Wrapper.getBalancesAsync();
                const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1];
                const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(
                    erc721TokenA.address,
                    erc721AFromTokenId2,
                );
                const inputAmount = new BigNumber(1);
                const erc721Amount = new BigNumber(1);
                const amounts = [erc721Amount, erc721Amount];
                const nestedAssetData = [erc721AssetData1, erc721AssetData2];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset1).to.be.equal(fromAddress);
                const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2);
                expect(ownerFromAsset2).to.be.equal(fromAddress);
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                        gas: constants.MAX_TRANSFER_FROM_GAS,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2);
                expect(newOwnerFromAsset1).to.be.equal(toAddress);
                expect(newOwnerFromAsset2).to.be.equal(toAddress);
            });
            it('should successfully transfer multiple different ERC721 tokens', async () => {
                const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId);
                const inputAmount = new BigNumber(1);
                const erc721Amount = new BigNumber(1);
                const amounts = [erc721Amount, erc721Amount];
                const nestedAssetData = [erc721AssetData1, erc721AssetData2];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset1).to.be.equal(fromAddress);
                const ownerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId);
                expect(ownerFromAsset2).to.be.equal(fromAddress);
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                        gas: constants.MAX_TRANSFER_FROM_GAS,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                const newOwnerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId);
                expect(newOwnerFromAsset1).to.be.equal(toAddress);
                expect(newOwnerFromAsset2).to.be.equal(toAddress);
            });
            it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalAmount = inputAmount.times(erc20Amount);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalAmount),
                );
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.equal(toAddress);
            });
            it('should successfully transfer tokens and ignore extra assetData', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const extraData = '0102030405060708';
                const assetData = `${assetDataInterface.MultiAsset.getABIEncodedTransactionData(
                    amounts,
                    nestedAssetData,
                )}${extraData}`;
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset).to.be.equal(fromAddress);
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalAmount = inputAmount.times(erc20Amount);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalAmount),
                );
                const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(newOwnerFromAsset).to.be.equal(toAddress);
            });
            it('should successfully transfer correct amounts when the `amount` > 1', async () => {
                const inputAmount = new BigNumber(100);
                const erc20Amount1 = new BigNumber(10);
                const erc20Amount2 = new BigNumber(20);
                const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address);
                const amounts = [erc20Amount1, erc20Amount2];
                const nestedAssetData = [erc20AssetData1, erc20AssetData2];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalErc20AAmount = inputAmount.times(erc20Amount1);
                const totalErc20BAmount = inputAmount.times(erc20Amount2);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount),
                );
                expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount),
                );
                expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount),
                );
            });
            it('should successfully transfer a large amount of tokens', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount1 = new BigNumber(10);
                const erc20Amount2 = new BigNumber(20);
                const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address);
                const erc721Amount = new BigNumber(1);
                const erc721Balances = await erc721Wrapper.getBalancesAsync();
                const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1];
                const erc721BFromTokenId2 = erc721Balances[fromAddress][erc721TokenB.address][1];
                const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(
                    erc721TokenA.address,
                    erc721AFromTokenId2,
                );
                const erc721AssetData3 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId);
                const erc721AssetData4 = assetDataUtils.encodeERC721AssetData(
                    erc721TokenB.address,
                    erc721BFromTokenId2,
                );
                const amounts = [erc721Amount, erc20Amount1, erc721Amount, erc20Amount2, erc721Amount, erc721Amount];
                const nestedAssetData = [
                    erc721AssetData1,
                    erc20AssetData1,
                    erc721AssetData2,
                    erc20AssetData2,
                    erc721AssetData3,
                    erc721AssetData4,
                ];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                expect(ownerFromAsset1).to.be.equal(fromAddress);
                const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2);
                expect(ownerFromAsset2).to.be.equal(fromAddress);
                const ownerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId);
                expect(ownerFromAsset3).to.be.equal(fromAddress);
                const ownerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2);
                expect(ownerFromAsset4).to.be.equal(fromAddress);
                const erc20Balances = await erc20Wrapper.getBalancesAsync();
                await web3Wrapper.awaitTransactionSuccessAsync(
                    await web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                        gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
                    }),
                    constants.AWAIT_TRANSACTION_MINED_MS,
                );
                const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
                const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2);
                const newOwnerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId);
                const newOwnerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2);
                expect(newOwnerFromAsset1).to.be.equal(toAddress);
                expect(newOwnerFromAsset2).to.be.equal(toAddress);
                expect(newOwnerFromAsset3).to.be.equal(toAddress);
                expect(newOwnerFromAsset4).to.be.equal(toAddress);
                const newBalances = await erc20Wrapper.getBalancesAsync();
                const totalErc20AAmount = inputAmount.times(erc20Amount1);
                const totalErc20BAmount = inputAmount.times(erc20Amount2);
                expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount),
                );
                expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount),
                );
                expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount),
                );
                expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal(
                    erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount),
                );
            });
            it('should revert if a single transfer fails', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                // 2 is an invalid erc721 amount
                const erc721Amount = new BigNumber(2);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.InvalidAmount,
                );
            });
            it('should revert if an AssetProxy is not registered', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const invalidProxyId = '0x12345678';
                const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`;
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, invalidErc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.AssetProxyDoesNotExist,
                );
            });
            it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc20Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.LengthMismatch,
                );
            });
            it('should revert if amounts multiplication results in an overflow', async () => {
                const inputAmount = new BigNumber(2).pow(128);
                const erc20Amount = new BigNumber(2).pow(128);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const amounts = [erc20Amount];
                const nestedAssetData = [erc20AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.Uint256Overflow,
                );
            });
            it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = '0x123456';
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: authorized,
                    }),
                    RevertReason.LengthGreaterThan3Required,
                );
            });
            it('should revert if caller is not authorized', async () => {
                const inputAmount = new BigNumber(1);
                const erc20Amount = new BigNumber(10);
                const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
                const erc721Amount = new BigNumber(1);
                const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
                const amounts = [erc20Amount, erc721Amount];
                const nestedAssetData = [erc20AssetData, erc721AssetData];
                const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
                const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
                    assetData,
                    fromAddress,
                    toAddress,
                    inputAmount,
                );
                await expectTransactionFailedAsync(
                    web3Wrapper.sendTransactionAsync({
                        to: multiAssetProxy.address,
                        data,
                        from: notAuthorized,
                    }),
                    RevertReason.SenderNotAuthorized,
                );
            });
        });
    });
});
// tslint:enable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count