diff options
6 files changed, 446 insertions, 180 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/IAssetProxy.sol index 43f45d200..df993a0ab 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/IAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/IAssetProxy.sol @@ -17,6 +17,7 @@ */ pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; import "../../utils/Authorizable/IAuthorizable.sol"; @@ -24,9 +25,9 @@ contract IAssetProxy is IAuthorizable { /// @dev Transfers assets. Either succeeds or throws. /// @param assetMetadata Byte array encoded for the respective asset proxy. - /// @param from Address to transfer token from. - /// @param to Address to transfer token to. - /// @param amount Amount of token to transfer. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. function transferFrom( bytes assetMetadata, address from, @@ -34,6 +35,18 @@ contract IAssetProxy is IAuthorizable { uint256 amount) external; + /// @dev Makes multiple transfers of assets. Either succeeds or throws. + /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy. + /// @param from Array of addresses to transfer assets from. + /// @param to Array of addresses to transfer assets to. + /// @param amounts Array of amounts of assets to transfer. + function batchTransferFrom( + bytes[] memory assetMetadata, + address[] memory from, + address[] memory to, + uint256[] memory amounts) + public; + /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. function getProxyId() @@ -41,3 +54,4 @@ contract IAssetProxy is IAuthorizable { view returns (uint8); } + diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol new file mode 100644 index 000000000..23dce6a8b --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol @@ -0,0 +1,75 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; + +import "./mixins/MAssetProxy.sol"; +import "./IAssetProxy.sol"; +import "../../utils/Authorizable/Authorizable.sol"; + +contract MixinAssetProxy is + IAssetProxy, + MAssetProxy, + Authorizable +{ + + /// @dev Transfers assets. Either succeeds or throws. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFrom( + bytes assetMetadata, + address from, + address to, + uint256 amount) + external + onlyAuthorized + { + transferFromInternal( + assetMetadata, + from, + to, + amount + ); + } + + /// @dev Makes multiple transfers of assets. Either succeeds or throws. + /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy. + /// @param from Array of addresses to transfer assets from. + /// @param to Array of addresses to transfer assets to. + /// @param amounts Array of amounts of assets to transfer. + function batchTransferFrom( + bytes[] memory assetMetadata, + address[] memory from, + address[] memory to, + uint256[] memory amounts) + public + onlyAuthorized + { + for (uint256 i = 0; i < assetMetadata.length; i++) { + transferFromInternal( + assetMetadata[i], + from[i], + to[i], + amounts[i] + ); + } + } +} diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol new file mode 100644 index 000000000..8e9b3bc65 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol @@ -0,0 +1,35 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; + +contract MAssetProxy { + + /// @dev Internal version of `transferFrom`. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFromInternal( + bytes memory assetMetadata, + address from, + address to, + uint256 amount) + internal; +} diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC20Proxy.sol index c3cfd8c2e..713e8a6e6 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC20Proxy.sol @@ -17,32 +17,31 @@ */ pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; + -import "../IAssetProxy.sol"; import "../../../utils/LibBytes/LibBytes.sol"; -import "../../../utils/Authorizable/Authorizable.sol"; import "../../../tokens/ERC20Token/IERC20Token.sol"; +import "../MixinAssetProxy.sol"; contract ERC20Proxy is LibBytes, - Authorizable, - IAssetProxy + MixinAssetProxy { uint8 constant PROXY_ID = 1; - /// @dev Transfers ERC20 tokens. Either succeeds or throws. - /// @param assetMetadata ERC20-encoded byte array. - /// @param from Address to transfer token from. - /// @param to Address to transfer token to. - /// @param amount Amount of token to transfer. - function transferFrom( - bytes assetMetadata, + /// @dev Internal version of `transferFrom`. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFromInternal( + bytes memory assetMetadata, address from, address to, uint256 amount) - external - onlyAuthorized + internal { // Data must be intended for this proxy. require(uint8(assetMetadata[0]) == PROXY_ID); diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC721Proxy.sol index e11de6744..6a4475d48 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/proxies/ERC721Proxy.sol @@ -17,32 +17,30 @@ */ pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; -import "../IAssetProxy.sol"; import "../../../utils/LibBytes/LibBytes.sol"; -import "../../../utils/Authorizable/Authorizable.sol"; import "../../../tokens/ERC721Token/ERC721Token.sol"; +import "../MixinAssetProxy.sol"; contract ERC721Proxy is LibBytes, - Authorizable, - IAssetProxy + MixinAssetProxy { uint8 constant PROXY_ID = 2; - /// @dev Transfers ERC721 tokens. Either succeeds or throws. - /// @param assetMetadata ERC721-encoded byte array - /// @param from Address to transfer token from. - /// @param to Address to transfer token to. - /// @param amount Amount of token to transfer. - function transferFrom( - bytes assetMetadata, + /// @dev Internal version of `transferFrom`. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFromInternal( + bytes memory assetMetadata, address from, address to, uint256 amount) - external - onlyAuthorized + internal { // Data must be intended for this proxy. require(uint8(assetMetadata[0]) == PROXY_ID); diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index bdd0a8696..a304da585 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -2,6 +2,7 @@ import { ZeroEx } from '0x.js'; import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; +import * as _ from 'lodash'; import * as Web3 from 'web3'; import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; @@ -37,6 +38,8 @@ describe('Asset Transfer Proxies', () => { let erc721Wrapper: ERC721Wrapper; let erc721MakerTokenId: BigNumber; + let zeroEx: ZeroEx; + before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = accounts); @@ -59,6 +62,10 @@ describe('Asset Transfer Proxies', () => { await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, { from: owner, }); + + zeroEx = new ZeroEx(provider, { + networkId: constants.TESTRPC_NETWORK_ID, + }); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -67,83 +74,143 @@ describe('Asset Transfer Proxies', () => { await blockchainLifecycle.revertAsync(); }); describe('Transfer Proxy - ERC20', () => { - it('should successfully transfer tokens', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - await erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, - makerAddress, - takerAddress, - amount, - { from: exchangeAddress }, - ); - // 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[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].add(amount), - ); - }); - - it('should do nothing if transferring 0 amount of a token', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(0); - await erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, - makerAddress, - takerAddress, - amount, - { from: exchangeAddress }, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address], - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address], - ); - }); - - it('should throw if allowances are too low', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); - // Create allowance less than transfer amount. Set allowance on proxy. - const allowance = new BigNumber(0); - const transferAmount = new BigNumber(10); - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, + describe('transferFrom', () => { + it('should successfully transfer tokens', async () => { + // Construct metadata for ERC20 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(10); + await erc20Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { from: exchangeAddress }, + ); + // 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[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].add(amount), + ); }); - // Perform a transfer; expect this to fail. - return expect( - erc20Proxy.transferFrom.sendTransactionAsync( + + it('should do nothing if transferring 0 amount of a token', async () => { + // Construct metadata for ERC20 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(0); + await erc20Proxy.transferFrom.sendTransactionAsync( encodedProxyMetadata, makerAddress, takerAddress, - transferAmount, - { from: notAuthorized }, - ), - ).to.be.rejectedWith(constants.REVERT); + amount, + { from: exchangeAddress }, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address], + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address], + ); + }); + + it('should throw if allowances are too low', async () => { + // Construct metadata for ERC20 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Create allowance less than transfer amount. Set allowance on proxy. + const allowance = new BigNumber(0); + const transferAmount = new BigNumber(10); + await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, allowance, { + from: makerAddress, + }); + // Perform a transfer; expect this to fail. + return expect( + erc20Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + transferAmount, + { from: notAuthorized }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if requesting address is not authorized', async () => { + // Construct metadata for ERC20 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(10); + return expect( + erc20Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { + from: notAuthorized, + }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); }); - it('should throw if requesting address is not authorized', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress - const amount = new BigNumber(10); - return expect( - erc20Proxy.transferFrom.sendTransactionAsync(encodedProxyMetadata, makerAddress, takerAddress, amount, { - from: notAuthorized, - }), - ).to.be.rejectedWith(constants.REVERT); + describe('batchTransferFrom', () => { + it('should succesfully make multiple token transfers', async () => { + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const amount = new BigNumber(10); + const numTransfers = 2; + const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata); + const fromAddresses = _.times(numTransfers, () => makerAddress); + const toAddresses = _.times(numTransfers, () => takerAddress); + const amounts = _.times(numTransfers, () => amount); + + const txHash = await erc20Proxy.batchTransferFrom.sendTransactionAsync( + assetMetadata, + fromAddresses, + toAddresses, + amounts, + { from: exchangeAddress }, + ); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const newBalances = await erc20Wrapper.getBalancesAsync(); + + expect(res.logs.length).to.equal(numTransfers); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(amount.times(numTransfers)), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].add(amount.times(numTransfers)), + ); + }); + + it('should throw if not called by an authorized address', async () => { + const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const amount = new BigNumber(10); + const numTransfers = 2; + const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata); + const fromAddresses = _.times(numTransfers, () => makerAddress); + const toAddresses = _.times(numTransfers, () => takerAddress); + const amounts = _.times(numTransfers, () => amount); + + expect( + erc20Proxy.batchTransferFrom.sendTransactionAsync( + assetMetadata, + fromAddresses, + toAddresses, + amounts, + { from: notAuthorized }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); }); it('should have an id of 1', async () => { @@ -153,97 +220,175 @@ describe('Asset Transfer Proxies', () => { }); describe('Transfer Proxy - ERC721', () => { - it('should successfully transfer tokens', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(erc721Token.address, erc721MakerTokenId); - // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(1); - await erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, - makerAddress, - takerAddress, - amount, - { from: exchangeAddress }, - ); - // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); - }); - - it('should throw if transferring 0 amount of a token', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(erc721Token.address, erc721MakerTokenId); - // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(0); - return expect( - erc721Proxy.transferFrom.sendTransactionAsync( + describe('transferFrom', () => { + it('should successfully transfer tokens', async () => { + // Construct metadata for ERC721 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + erc721Token.address, + erc721MakerTokenId, + ); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(1); + await erc721Proxy.transferFrom.sendTransactionAsync( encodedProxyMetadata, makerAddress, takerAddress, amount, { from: exchangeAddress }, - ), - ).to.be.rejectedWith(constants.REVERT); + ); + // Verify transfer was successful + const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + }); + + it('should throw if transferring 0 amount of a token', async () => { + // Construct metadata for ERC721 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + erc721Token.address, + erc721MakerTokenId, + ); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(0); + return expect( + erc721Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { from: exchangeAddress }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if transferring > 1 amount of a token', async () => { + // Construct metadata for ERC721 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + erc721Token.address, + erc721MakerTokenId, + ); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(500); + return expect( + erc721Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { from: exchangeAddress }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if allowances are too low', async () => { + // Construct metadata for ERC721 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + erc721Token.address, + erc721MakerTokenId, + ); + // Remove transfer approval for makerAddress. + await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, { + from: makerAddress, + }); + // Perform a transfer; expect this to fail. + const amount = new BigNumber(1); + return expect( + erc20Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { + from: notAuthorized, + }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if requesting address is not authorized', async () => { + // Construct metadata for ERC721 proxy + const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + erc721Token.address, + erc721MakerTokenId, + ); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(1); + return expect( + erc721Proxy.transferFrom.sendTransactionAsync( + encodedProxyMetadata, + makerAddress, + takerAddress, + amount, + { from: notAuthorized }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); }); - it('should throw if transferring > 1 amount of a token', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(erc721Token.address, erc721MakerTokenId); - // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(500); - return expect( - erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, - makerAddress, - takerAddress, - amount, + describe('batchTransferFrom', () => { + it('should succesfully make multiple token transfers', async () => { + const erc721TokensById = await erc721Wrapper.getBalancesAsync(); + const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address]; + + const numTransfers = 2; + const assetMetadata = [ + assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA), + assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB), + ]; + const fromAddresses = _.times(numTransfers, () => makerAddress); + const toAddresses = _.times(numTransfers, () => takerAddress); + const amounts = _.times(numTransfers, () => new BigNumber(1)); + + const txHash = await erc721Proxy.batchTransferFrom.sendTransactionAsync( + assetMetadata, + fromAddresses, + toAddresses, + amounts, { from: exchangeAddress }, - ), - ).to.be.rejectedWith(constants.REVERT); - }); + ); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + expect(res.logs.length).to.equal(numTransfers); - it('should throw if allowances are too low', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(erc721Token.address, erc721MakerTokenId); - // Remove transfer approval for makerAddress. - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, { - from: makerAddress, + const newOwnerMakerAssetA = await erc721Token.ownerOf.callAsync(makerTokenIdA); + const newOwnerMakerAssetB = await erc721Token.ownerOf.callAsync(makerTokenIdB); + expect(newOwnerMakerAssetA).to.be.bignumber.equal(takerAddress); + expect(newOwnerMakerAssetB).to.be.bignumber.equal(takerAddress); }); - // Perform a transfer; expect this to fail. - const amount = new BigNumber(1); - return expect( - erc20Proxy.transferFrom.sendTransactionAsync(encodedProxyMetadata, makerAddress, takerAddress, amount, { - from: notAuthorized, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - it('should throw if requesting address is not authorized', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(erc721Token.address, erc721MakerTokenId); - // Perform a transfer from makerAddress to takerAddress - const amount = new BigNumber(1); - return expect( - erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, - makerAddress, - takerAddress, - amount, - { from: notAuthorized }, - ), - ).to.be.rejectedWith(constants.REVERT); + it('should throw if not called by an authorized address', async () => { + const erc721TokensById = await erc721Wrapper.getBalancesAsync(); + const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address]; + + const numTransfers = 2; + const assetMetadata = [ + assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA), + assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB), + ]; + const fromAddresses = _.times(numTransfers, () => makerAddress); + const toAddresses = _.times(numTransfers, () => takerAddress); + const amounts = _.times(numTransfers, () => new BigNumber(1)); + + expect( + erc721Proxy.batchTransferFrom.sendTransactionAsync( + assetMetadata, + fromAddresses, + toAddresses, + amounts, + { from: notAuthorized }, + ), + ).to.be.rejectedWith(constants.REVERT); + }); }); it('should have an id of 2', async () => { |