diff options
Diffstat (limited to 'packages/contracts/test')
-rw-r--r-- | packages/contracts/test/asset_proxy/proxies.ts | 993 | ||||
-rw-r--r-- | packages/contracts/test/exchange/core.ts | 383 | ||||
-rw-r--r-- | packages/contracts/test/utils/constants.ts | 2 | ||||
-rw-r--r-- | packages/contracts/test/utils/erc721_wrapper.ts | 6 |
4 files changed, 1171 insertions, 213 deletions
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index b8305993e..8fa1e602a 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -12,7 +12,9 @@ import { DummyMultipleReturnERC20TokenContract } from '../../generated-wrappers/ import { DummyNoReturnERC20TokenContract } from '../../generated-wrappers/dummy_no_return_erc20_token'; import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; +import { IAssetDataContract } from '../../generated-wrappers/i_asset_data'; import { IAssetProxyContract } from '../../generated-wrappers/i_asset_proxy'; +import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; import { artifacts } from '../../src/artifacts'; import { expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; @@ -30,26 +32,35 @@ const assetProxyInterface = new IAssetProxyContract( constants.NULL_ADDRESS, provider, ); +const assetDataInterface = new IAssetDataContract( + artifacts.IAssetData.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); // tslint:disable:no-unnecessary-type-assertion describe('Asset Transfer Proxies', () => { let owner: string; let notAuthorized: string; - let exchangeAddress: string; - let makerAddress: string; - let takerAddress: string; + let authorized: string; + let fromAddress: string; + let toAddress: string; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; + 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 erc721MakerTokenId: BigNumber; + let erc721AFromTokenId: BigNumber; + let erc721BFromTokenId: BigNumber; before(async () => { await blockchainLifecycle.startAsync(); @@ -59,41 +70,73 @@ describe('Asset Transfer Proxies', () => { }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = _.slice( - accounts, - 0, - 5, - )); + const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5)); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - const numDummyErc20ToDeploy = 1; - [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS); + // Deploy AssetProxies erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + artifacts.MultiAssetProxy, + provider, + txDefaults, + ); + + // Configure ERC20Proxy await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, { + 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, ); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerTokenId = erc721Balances[makerAddress][erc721Token.address][0]; + // Configure ERC721Proxy await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, { + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { from: owner, }), constants.AWAIT_TRANSACTION_MINED_MS, ); - erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( - artifacts.DummyERC721Receiver, - provider, - txDefaults, + 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( artifacts.DummyNoReturnERC20Token, @@ -104,30 +147,32 @@ describe('Asset Transfer Proxies', () => { constants.DUMMY_TOKEN_DECIMALS, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ); + multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.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(makerAddress, constants.INITIAL_ERC20_BALANCE), + 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: makerAddress }, + { from: fromAddress }, ), constants.AWAIT_TRANSACTION_MINED_MS, ); - multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync( - artifacts.DummyMultipleReturnERC20Token, - provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ); await web3Wrapper.awaitTransactionSuccessAsync( await multipleReturnErc20Token.setBalance.sendTransactionAsync( - makerAddress, + fromAddress, constants.INITIAL_ERC20_BALANCE, ), constants.AWAIT_TRANSACTION_MINED_MS, @@ -136,10 +181,23 @@ describe('Asset Transfer Proxies', () => { await multipleReturnErc20Token.approve.sendTransactionAsync( erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, - { from: makerAddress }, + { from: fromAddress }, ), constants.AWAIT_TRANSACTION_MINED_MS, ); + + // Deploy and configure ERC721 tokens and receiver + [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( + artifacts.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(); @@ -147,7 +205,8 @@ describe('Asset Transfer Proxies', () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('Transfer Proxy - ERC20', () => { + + describe('ERC20Proxy', () => { it('should revert if undefined function is called', async () => { const undefinedSelector = '0x01020304'; await expectTransactionFailedWithoutReasonAsync( @@ -159,141 +218,146 @@ describe('Asset Transfer Proxies', () => { }), ); }); + 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(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].add(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 makerAddress to takerAddress - const initialMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + // 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance.minus(amount)); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance.plus(amount)); + 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(zrxToken.address)}${extraData}`; - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].add(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(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address], + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address], ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address], + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address], ); }); - it('should throw if allowances are too low', async () => { + it('should revert if allowances are too low', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, + await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, { + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -303,7 +367,7 @@ describe('Asset Transfer Proxies', () => { web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); @@ -311,7 +375,7 @@ describe('Asset Transfer Proxies', () => { expect(newBalances).to.deep.equal(erc20Balances); }); - it('should throw if allowances are too low and token does not return a value', async () => { + 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. @@ -319,42 +383,42 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); - const initialMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + 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: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance); + 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 throw if requesting address is not authorized', async () => { + it('should revert if caller is not authorized', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); const erc20Balances = await erc20Wrapper.getBalancesAsync(); @@ -370,42 +434,36 @@ describe('Asset Transfer Proxies', () => { expect(newBalances).to.deep.equal(erc20Balances); }); - it('should throw if token returns more than 32 bytes', async () => { + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); - const initialMakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(takerAddress); + 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: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newMakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance); + 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); }); }); - - it('should have an id of 0xf47261b0', async () => { - const proxyId = await erc20Proxy.getProxyId.callAsync(); - const expectedProxyId = '0xf47261b0'; - expect(proxyId).to.equal(expectedProxyId); - }); }); - describe('Transfer Proxy - ERC721', () => { + describe('ERC721Proxy', () => { it('should revert if undefined function is called', async () => { const undefinedSelector = '0x01020304'; await expectTransactionFailedWithoutReasonAsync( @@ -417,76 +475,81 @@ describe('Asset Transfer Proxies', () => { }), ); }); + 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(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + 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( - erc721Token.address, - erc721MakerTokenId, + erc721TokenA.address, + erc721AFromTokenId, )}${extraData}`; // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + 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(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, + fromAddress, erc721Receiver.address, amount, ); @@ -495,79 +558,79 @@ describe('Asset Transfer Proxies', () => { await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + 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 newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address); }); - it('should throw if transferring 0 amount of a token', async () => { + it('should revert if transferring 0 amount of a token', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.InvalidAmount, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if transferring > 1 amount of a token', async () => { + it('should revert if transferring > 1 amount of a token', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.InvalidAmount, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if allowances are too low', async () => { + it('should revert if allowances are too low', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Remove transfer approval for makerAddress. + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Remove transfer approval for fromAddress. await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721MakerTokenId, { - from: makerAddress, + await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, { + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -575,34 +638,34 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if requesting address is not authorized', async () => { + it('should revert if caller is not authorized', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + 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, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( @@ -613,16 +676,570 @@ describe('Asset Transfer Proxies', () => { }), RevertReason.SenderNotAuthorized, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); }); - - it('should have an id of 0x02571792', async () => { - const proxyId = await erc721Proxy.getProxyId.callAsync(); - const expectedProxyId = '0x02571792'; + }); + 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 diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index fc8dc5346..9159b0d8f 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -14,6 +14,8 @@ import { DummyNoReturnERC20TokenContract } from '../../generated-wrappers/dummy_ import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; import { ExchangeCancelEventArgs, ExchangeContract } from '../../generated-wrappers/exchange'; +import { IAssetDataContract } from '../../generated-wrappers/i_asset_data'; +import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; import { ReentrantERC20TokenContract } from '../../generated-wrappers/reentrant_erc20_token'; import { TestStaticCallReceiverContract } from '../../generated-wrappers/test_static_call_receiver'; import { artifacts } from '../../src/artifacts'; @@ -31,6 +33,11 @@ import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const assetDataInterface = new IAssetDataContract( + artifacts.IAssetData.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); // tslint:disable:no-unnecessary-type-assertion describe('Exchange core', () => { let makerAddress: string; @@ -47,6 +54,7 @@ describe('Exchange core', () => { let exchange: ExchangeContract; let erc20Proxy: ERC20ProxyContract; let erc721Proxy: ERC721ProxyContract; + let multiAssetProxy: MultiAssetProxyContract; let maliciousWallet: TestStaticCallReceiverContract; let maliciousValidator: TestStaticCallReceiverContract; @@ -76,31 +84,39 @@ describe('Exchange core', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + // Deploy AssetProxies, Exchange, tokens, and malicious contracts + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + artifacts.MultiAssetProxy, + provider, + txDefaults, + ); const numDummyErc20ToDeploy = 3; [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; - exchange = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, assetDataUtils.encodeERC20AssetData(zrxToken.address), ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( + artifacts.TestStaticCallReceiver, + provider, + txDefaults, + ); + reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.ReentrantERC20Token, + provider, + txDefaults, + exchange.address, + ); + // Configure ERC20Proxy await web3Wrapper.awaitTransactionSuccessAsync( await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner, @@ -108,27 +124,64 @@ describe('Exchange core', () => { 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(exchange.address, { from: owner, }), constants.AWAIT_TRANSACTION_MINED_MS, ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( - artifacts.TestStaticCallReceiver, - provider, - txDefaults, + // Configure MultiAssetProxy + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, ); - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - artifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, + 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, ); + // Configure Exchange + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); + + // Configure ERC20 tokens + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + // Configure ERC721 tokens + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; + + // Configure order defaults defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; - const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, exchangeAddress: exchange.address, @@ -707,6 +760,292 @@ describe('Exchange core', () => { }); }); + describe('Testing exchange of multiple assets', () => { + it('should allow multiple assets to be exchanged for a single asset', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(1); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.plus(takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(takerAssetAmount)); + }); + it('should allow multiple assets to be exchanged for multiple assets', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(1); + const takerAmounts = [new BigNumber(10), new BigNumber(1)]; + const takerAssetId = erc721TakerAssetIds[0]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(zrxToken.address), + assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + ]; + const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + takerAmounts, + takerNestedAssetData, + ); + const takerAssetAmount = new BigNumber(1); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalOwnerTakerAsset).to.be.equal(makerAddress); + }); + it('should allow an order selling multiple assets to be partially filled', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(30); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + it('should allow an order buying multiple assets to be partially filled', async () => { + const takerAmounts = [new BigNumber(10), new BigNumber(20)]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + takerAmounts, + takerNestedAssetData, + ); + const takerAssetAmount = new BigNumber(30); + const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const makerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.plus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.plus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.minus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.minus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.minus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.plus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + }); + describe('getOrderInfo', () => { beforeEach(async () => { signedOrder = await orderFactory.newSignedOrderAsync(); diff --git a/packages/contracts/test/utils/constants.ts b/packages/contracts/test/utils/constants.ts index cd21330e9..d2c3ab512 100644 --- a/packages/contracts/test/utils/constants.ts +++ b/packages/contracts/test/utils/constants.ts @@ -35,7 +35,7 @@ export const constants = { DUMMY_TOKEN_TOTAL_SUPPLY: new BigNumber(0), NULL_BYTES: '0x', NUM_DUMMY_ERC20_TO_DEPLOY: 3, - NUM_DUMMY_ERC721_TO_DEPLOY: 1, + NUM_DUMMY_ERC721_TO_DEPLOY: 2, NUM_ERC721_TOKENS_TO_MINT: 2, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), diff --git a/packages/contracts/test/utils/erc721_wrapper.ts b/packages/contracts/test/utils/erc721_wrapper.ts index 3ef4e701d..e9da553d0 100644 --- a/packages/contracts/test/utils/erc721_wrapper.ts +++ b/packages/contracts/test/utils/erc721_wrapper.ts @@ -29,7 +29,8 @@ export class ERC721Wrapper { this._contractOwnerAddress = contractOwnerAddress; } public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> { - for (let i = 0; i < constants.NUM_DUMMY_ERC721_TO_DEPLOY; i++) { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { this._dummyTokenContracts.push( await DummyERC721TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC721Token, @@ -61,7 +62,8 @@ export class ERC721Wrapper { this._initialTokenIdsByOwner = {}; for (const dummyTokenContract of this._dummyTokenContracts) { for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { const tokenId = generatePseudoRandomSalt(); await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { |