aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/asset-proxy/test
diff options
context:
space:
mode:
Diffstat (limited to 'contracts/asset-proxy/test')
-rw-r--r--contracts/asset-proxy/test/authorizable.ts210
-rw-r--r--contracts/asset-proxy/test/global_hooks.ts17
-rw-r--r--contracts/asset-proxy/test/proxies.ts1291
-rw-r--r--contracts/asset-proxy/test/utils/erc20_wrapper.ts179
-rw-r--r--contracts/asset-proxy/test/utils/erc721_wrapper.ts236
-rw-r--r--contracts/asset-proxy/test/utils/index.ts2
6 files changed, 1935 insertions, 0 deletions
diff --git a/contracts/asset-proxy/test/authorizable.ts b/contracts/asset-proxy/test/authorizable.ts
new file mode 100644
index 000000000..087121235
--- /dev/null
+++ b/contracts/asset-proxy/test/authorizable.ts
@@ -0,0 +1,210 @@
+import {
+ chaiSetup,
+ constants,
+ expectTransactionFailedAsync,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+
+import { artifacts, MixinAuthorizableContract } from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Authorizable', () => {
+ let owner: string;
+ let notOwner: string;
+ let address: string;
+ let authorizable: MixinAuthorizableContract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ [owner, address, notOwner] = _.slice(accounts, 0, 3);
+ authorizable = await MixinAuthorizableContract.deployFrom0xArtifactAsync(
+ artifacts.MixinAuthorizable,
+ provider,
+ txDefaults,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('addAuthorizedAddress', () => {
+ it('should throw if not called by owner', async () => {
+ return expectTransactionFailedAsync(
+ authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ it('should allow owner to add an authorized address', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.true();
+ });
+ it('should throw if owner attempts to authorize a duplicate address', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ return expectTransactionFailedAsync(
+ authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ RevertReason.TargetAlreadyAuthorized,
+ );
+ });
+ });
+
+ describe('removeAuthorizedAddress', () => {
+ it('should throw if not called by owner', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: notOwner,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+
+ it('should allow owner to remove an authorized address', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.false();
+ });
+
+ it('should throw if owner attempts to remove an address that is not authorized', async () => {
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ RevertReason.TargetNotAuthorized,
+ );
+ });
+ });
+
+ describe('removeAuthorizedAddressAtIndex', () => {
+ it('should throw if not called by owner', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: notOwner,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ it('should throw if index is >= authorities.length', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(1);
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ RevertReason.IndexOutOfBounds,
+ );
+ });
+ it('should throw if owner attempts to remove an address that is not authorized', async () => {
+ const index = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ RevertReason.TargetNotAuthorized,
+ );
+ });
+ it('should throw if address at index does not match target', async () => {
+ const address1 = address;
+ const address2 = notOwner;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address1, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address2, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const address1Index = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, {
+ from: owner,
+ }),
+ RevertReason.AuthorizedAddressMismatch,
+ );
+ });
+ it('should allow owner to remove an authorized address', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(0);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.false();
+ });
+ });
+
+ describe('getAuthorizedAddresses', () => {
+ it('should return all authorized addresses', async () => {
+ const initial = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(initial).to.have.length(0);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const afterAdd = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(afterAdd).to.have.length(1);
+ expect(afterAdd).to.include(address);
+
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const afterRemove = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(afterRemove).to.have.length(0);
+ });
+ });
+});
diff --git a/contracts/asset-proxy/test/global_hooks.ts b/contracts/asset-proxy/test/global_hooks.ts
new file mode 100644
index 000000000..f8ace376a
--- /dev/null
+++ b/contracts/asset-proxy/test/global_hooks.ts
@@ -0,0 +1,17 @@
+import { env, EnvVars } from '@0x/dev-utils';
+
+import { coverage, profiler, provider } from '@0x/contracts-test-utils';
+before('start web3 provider', () => {
+ provider.start();
+});
+after('generate coverage report', async () => {
+ if (env.parseBoolean(EnvVars.SolidityCoverage)) {
+ const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
+ await coverageSubprovider.writeCoverageAsync();
+ }
+ if (env.parseBoolean(EnvVars.SolidityProfiler)) {
+ const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
+ await profilerSubprovider.writeProfilerOutputAsync();
+ }
+ provider.stop();
+});
diff --git a/contracts/asset-proxy/test/proxies.ts b/contracts/asset-proxy/test/proxies.ts
new file mode 100644
index 000000000..797787135
--- /dev/null
+++ b/contracts/asset-proxy/test/proxies.ts
@@ -0,0 +1,1291 @@
+import {
+ artifacts as erc20Artifacts,
+ DummyERC20TokenContract,
+ DummyERC20TokenTransferEventArgs,
+ DummyMultipleReturnERC20TokenContract,
+ DummyNoReturnERC20TokenContract,
+} from '@0x/contracts-erc20';
+import {
+ artifacts as erc721Artifacts,
+ DummyERC721ReceiverContract,
+ DummyERC721TokenContract,
+} from '@0x/contracts-erc721';
+import {
+ chaiSetup,
+ constants,
+ expectTransactionFailedAsync,
+ expectTransactionFailedWithoutReasonAsync,
+ LogDecoder,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import { LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import {
+ artifacts,
+ ERC20ProxyContract,
+ ERC20Wrapper,
+ ERC721ProxyContract,
+ ERC721Wrapper,
+ IAssetDataContract,
+ IAssetProxyContract,
+ MultiAssetProxyContract,
+} from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+const assetProxyInterface = new IAssetProxyContract(
+ artifacts.IAssetProxy.compilerOutput.abi,
+ 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 authorized: string;
+ let fromAddress: string;
+ let toAddress: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let erc721TokenA: DummyERC721TokenContract;
+ let erc721TokenB: DummyERC721TokenContract;
+ let erc721Receiver: DummyERC721ReceiverContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+ let noReturnErc20Token: DummyNoReturnERC20TokenContract;
+ let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract;
+ let multiAssetProxy: MultiAssetProxyContract;
+
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+ let erc721AFromTokenId: BigNumber;
+ let erc721BFromTokenId: BigNumber;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5));
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ // Deploy AssetProxies
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
+ artifacts.MultiAssetProxy,
+ provider,
+ txDefaults,
+ );
+
+ // Configure ERC20Proxy
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ // Configure ERC721Proxy
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ // Configure MultiAssetProxy
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ // Deploy and configure ERC20 tokens
+ const numDummyErc20ToDeploy = 2;
+ [erc20TokenA, erc20TokenB] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync(
+ erc20Artifacts.DummyNoReturnERC20Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ constants.DUMMY_TOKEN_DECIMALS,
+ constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+ );
+ multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync(
+ erc20Artifacts.DummyMultipleReturnERC20Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ constants.DUMMY_TOKEN_DECIMALS,
+ constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+ );
+
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await noReturnErc20Token.approve.sendTransactionAsync(
+ erc20Proxy.address,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ { from: fromAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await multipleReturnErc20Token.setBalance.sendTransactionAsync(
+ fromAddress,
+ constants.INITIAL_ERC20_BALANCE,
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await multipleReturnErc20Token.approve.sendTransactionAsync(
+ erc20Proxy.address,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ { from: fromAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ // Deploy and configure ERC721 tokens and receiver
+ [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync(
+ erc721Artifacts.DummyERC721Receiver,
+ provider,
+ txDefaults,
+ );
+
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0];
+ erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0];
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('ERC20Proxy', () => {
+ it('should revert if undefined function is called', async () => {
+ const undefinedSelector = '0x01020304';
+ await expectTransactionFailedWithoutReasonAsync(
+ web3Wrapper.sendTransactionAsync({
+ from: owner,
+ to: erc20Proxy.address,
+ value: constants.ZERO_AMOUNT,
+ data: undefinedSelector,
+ }),
+ );
+ });
+ it('should have an id of 0xf47261b0', async () => {
+ const proxyId = await erc20Proxy.getProxyId.callAsync();
+ const expectedProxyId = '0xf47261b0';
+ expect(proxyId).to.equal(expectedProxyId);
+ });
+ describe('transferFrom', () => {
+ it('should successfully transfer tokens', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
+ // Perform a transfer from fromAddress to toAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[fromAddress][erc20TokenA.address].minus(amount),
+ );
+ expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[toAddress][erc20TokenA.address].plus(amount),
+ );
+ });
+
+ it('should successfully transfer tokens that do not return a value', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address);
+ // Perform a transfer from fromAddress to toAddress
+ const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
+ expect(newFromBalance).to.be.bignumber.equal(initialFromBalance.minus(amount));
+ expect(newToBalance).to.be.bignumber.equal(initialToBalance.plus(amount));
+ });
+
+ it('should successfully transfer tokens and ignore extra assetData', async () => {
+ // Construct ERC20 asset data
+ const extraData = '0102030405060708';
+ const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(erc20TokenA.address)}${extraData}`;
+ // Perform a transfer from fromAddress to toAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[fromAddress][erc20TokenA.address].minus(amount),
+ );
+ expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[toAddress][erc20TokenA.address].plus(amount),
+ );
+ });
+
+ it('should do nothing if transferring 0 amount of a token', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
+ // Perform a transfer from fromAddress to toAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(0);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[fromAddress][erc20TokenA.address],
+ );
+ expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
+ erc20Balances[toAddress][erc20TokenA.address],
+ );
+ });
+
+ it('should revert if allowances are too low', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
+ // Create allowance less than transfer amount. Set allowance on proxy.
+ const allowance = new BigNumber(0);
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: fromAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ // Perform a transfer; expect this to fail.
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.TransferFailed,
+ );
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.deep.equal(erc20Balances);
+ });
+
+ it('should revert if allowances are too low and token does not return a value', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address);
+ // Create allowance less than transfer amount. Set allowance on proxy.
+ const allowance = new BigNumber(0);
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: fromAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
+ // Perform a transfer; expect this to fail.
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.TransferFailed,
+ );
+ const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress);
+ expect(newFromBalance).to.be.bignumber.equal(initialFromBalance);
+ expect(newToBalance).to.be.bignumber.equal(initialToBalance);
+ });
+
+ it('should revert if caller is not authorized', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: notAuthorized,
+ }),
+ RevertReason.SenderNotAuthorized,
+ );
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.deep.equal(erc20Balances);
+ });
+
+ it('should revert if token returns more than 32 bytes', async () => {
+ // Construct ERC20 asset data
+ const encodedAssetData = assetDataUtils.encodeERC20AssetData(multipleReturnErc20Token.address);
+ const amount = new BigNumber(10);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ const initialFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const initialToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress);
+ // Perform a transfer; expect this to fail.
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc20Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.TransferFailed,
+ );
+ const newFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress);
+ const newToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress);
+ expect(newFromBalance).to.be.bignumber.equal(initialFromBalance);
+ expect(newToBalance).to.be.bignumber.equal(initialToBalance);
+ });
+ });
+ });
+
+ describe('ERC721Proxy', () => {
+ it('should revert if undefined function is called', async () => {
+ const undefinedSelector = '0x01020304';
+ await expectTransactionFailedWithoutReasonAsync(
+ web3Wrapper.sendTransactionAsync({
+ from: owner,
+ to: erc721Proxy.address,
+ value: constants.ZERO_AMOUNT,
+ data: undefinedSelector,
+ }),
+ );
+ });
+ it('should have an id of 0x02571792', async () => {
+ const proxyId = await erc721Proxy.getProxyId.callAsync();
+ const expectedProxyId = '0x02571792';
+ expect(proxyId).to.equal(expectedProxyId);
+ });
+ describe('transferFrom', () => {
+ it('should successfully transfer tokens', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(1);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress);
+ });
+
+ it('should successfully transfer tokens and ignore extra assetData', async () => {
+ // Construct ERC721 asset data
+ const extraData = '0102030405060708';
+ const encodedAssetData = `${assetDataUtils.encodeERC721AssetData(
+ erc721TokenA.address,
+ erc721AFromTokenId,
+ )}${extraData}`;
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(1);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress);
+ });
+
+ it('should not call onERC721Received when transferring to a smart contract', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(1);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ erc721Receiver.address,
+ amount,
+ );
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...erc721Artifacts });
+ const tx = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ gas: constants.MAX_TRANSFER_FROM_GAS,
+ }),
+ );
+ // Verify that no log was emitted by erc721 receiver
+ expect(tx.logs.length).to.be.equal(1);
+ // Verify transfer was successful
+ const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address);
+ });
+
+ it('should revert if transferring 0 amount of a token', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(0);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.InvalidAmount,
+ );
+ const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwner).to.be.equal(ownerFromAsset);
+ });
+
+ it('should revert if transferring > 1 amount of a token', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(500);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.InvalidAmount,
+ );
+ const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwner).to.be.equal(ownerFromAsset);
+ });
+
+ it('should revert if allowances are too low', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Remove transfer approval for fromAddress.
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, {
+ from: fromAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Perform a transfer; expect this to fail.
+ const amount = new BigNumber(1);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: authorized,
+ }),
+ RevertReason.TransferFailed,
+ );
+ const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwner).to.be.equal(ownerFromAsset);
+ });
+
+ it('should revert if caller is not authorized', async () => {
+ // Construct ERC721 asset data
+ const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
+ // Verify pre-condition
+ const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(ownerFromAsset).to.be.equal(fromAddress);
+ // Perform a transfer from fromAddress to toAddress
+ const amount = new BigNumber(1);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ encodedAssetData,
+ fromAddress,
+ toAddress,
+ amount,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: erc721Proxy.address,
+ data,
+ from: notAuthorized,
+ }),
+ RevertReason.SenderNotAuthorized,
+ );
+ const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
+ expect(newOwner).to.be.equal(ownerFromAsset);
+ });
+ });
+ });
+ describe('MultiAssetProxy', () => {
+ it('should revert if undefined function is called', async () => {
+ const undefinedSelector = '0x01020304';
+ await expectTransactionFailedWithoutReasonAsync(
+ web3Wrapper.sendTransactionAsync({
+ from: owner,
+ to: multiAssetProxy.address,
+ value: constants.ZERO_AMOUNT,
+ data: undefinedSelector,
+ }),
+ );
+ });
+ it('should have an id of 0x94cfcdd7', async () => {
+ const proxyId = await multiAssetProxy.getProxyId.callAsync();
+ // first 4 bytes of `keccak256('MultiAsset(uint256[],bytes[])')`
+ const expectedProxyId = '0x94cfcdd7';
+ expect(proxyId).to.equal(expectedProxyId);
+ });
+ describe('transferFrom', () => {
+ it('should transfer a single ERC20 token', async () => {
+ const inputAmount = new BigNumber(1);
+ const erc20Amount = new BigNumber(10);
+ const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
+ const amounts = [erc20Amount];
+ const nestedAssetData = [erc20AssetData];
+ const assetData = assetDataUtils.encodeMultiAssetData(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].plus(totalAmount),
+ );
+ });
+ it('should dispatch an ERC20 transfer when input amount is 0', async () => {
+ const inputAmount = constants.ZERO_AMOUNT;
+ 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();
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...erc20Artifacts });
+ const tx = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ to: multiAssetProxy.address,
+ data,
+ from: authorized,
+ }),
+ );
+ expect(tx.logs.length).to.be.equal(1);
+ const log = tx.logs[0] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>;
+ const transferEventName = 'Transfer';
+ expect(log.event).to.equal(transferEventName);
+ expect(log.args._value).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.deep.equal(erc20Balances);
+ });
+ 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 = assetDataUtils.encodeMultiAssetData(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].plus(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 = assetDataUtils.encodeMultiAssetData(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].plus(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].plus(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 = assetDataUtils.encodeMultiAssetData(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 = assetDataUtils.encodeMultiAssetData(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 = assetDataUtils.encodeMultiAssetData(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 = assetDataUtils.encodeMultiAssetData(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].plus(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 = `${assetDataUtils.encodeMultiAssetData(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].plus(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 = assetDataUtils.encodeMultiAssetData(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].plus(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].plus(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 = assetDataUtils.encodeMultiAssetData(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].plus(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].plus(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 = assetDataUtils.encodeMultiAssetData(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];
+ // HACK: This is used to get around validation built into assetDataUtils
+ 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];
+ // HACK: This is used to get around validation built into assetDataUtils
+ 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 = assetDataUtils.encodeMultiAssetData(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];
+ // HACK: This is used to get around validation built into assetDataUtils
+ 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 = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
+ const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
+ assetData,
+ fromAddress,
+ toAddress,
+ inputAmount,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ to: multiAssetProxy.address,
+ data,
+ from: notAuthorized,
+ }),
+ RevertReason.SenderNotAuthorized,
+ );
+ });
+ });
+ });
+});
+// tslint:enable:no-unnecessary-type-assertion
+// tslint:disable:max-file-line-count
diff --git a/contracts/asset-proxy/test/utils/erc20_wrapper.ts b/contracts/asset-proxy/test/utils/erc20_wrapper.ts
new file mode 100644
index 000000000..12cd06ece
--- /dev/null
+++ b/contracts/asset-proxy/test/utils/erc20_wrapper.ts
@@ -0,0 +1,179 @@
+import { artifacts as tokensArtifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
+import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy';
+import { artifacts } from '../../src/artifacts';
+
+export class ERC20Wrapper {
+ private readonly _tokenOwnerAddresses: string[];
+ private readonly _contractOwnerAddress: string;
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _provider: Provider;
+ private readonly _dummyTokenContracts: DummyERC20TokenContract[];
+ private _proxyContract?: ERC20ProxyContract;
+ private _proxyIdIfExists?: string;
+ /**
+ * Instanitates an ERC20Wrapper
+ * @param provider Web3 provider to use for all JSON RPC requests
+ * @param tokenOwnerAddresses Addresses that we want to endow as owners for dummy ERC20 tokens
+ * @param contractOwnerAddress Desired owner of the contract
+ * Instance of ERC20Wrapper
+ */
+ constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
+ this._dummyTokenContracts = [];
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._provider = provider;
+ this._tokenOwnerAddresses = tokenOwnerAddresses;
+ this._contractOwnerAddress = contractOwnerAddress;
+ }
+ public async deployDummyTokensAsync(
+ numberToDeploy: number,
+ decimals: BigNumber,
+ ): Promise<DummyERC20TokenContract[]> {
+ for (let i = 0; i < numberToDeploy; i++) {
+ this._dummyTokenContracts.push(
+ await DummyERC20TokenContract.deployFrom0xArtifactAsync(
+ tokensArtifacts.DummyERC20Token,
+ this._provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ decimals,
+ constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+ ),
+ );
+ }
+ return this._dummyTokenContracts;
+ }
+ public async deployProxyAsync(): Promise<ERC20ProxyContract> {
+ this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync(
+ artifacts.ERC20Proxy,
+ this._provider,
+ txDefaults,
+ );
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
+ return this._proxyContract;
+ }
+ public getProxyId(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
+ public async setBalancesAndAllowancesAsync(): Promise<void> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateProxyContractExistsOrThrow();
+ for (const dummyTokenContract of this._dummyTokenContracts) {
+ for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await dummyTokenContract.setBalance.sendTransactionAsync(
+ tokenOwnerAddress,
+ constants.INITIAL_ERC20_BALANCE,
+ { from: this._contractOwnerAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await dummyTokenContract.approve.sendTransactionAsync(
+ (this._proxyContract as ERC20ProxyContract).address,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ { from: tokenOwnerAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ }
+ }
+ public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress));
+ return balance;
+ }
+ public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress));
+ return allowance;
+ }
+ public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, {
+ from: userAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
+ this._validateDummyTokenContractsExistOrThrow();
+ const balancesByOwner: ERC20BalancesByOwner = {};
+ const balances: BigNumber[] = [];
+ const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = [];
+ for (const dummyTokenContract of this._dummyTokenContracts) {
+ for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
+ balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress));
+ balanceInfo.push({
+ tokenOwnerAddress,
+ tokenAddress: dummyTokenContract.address,
+ });
+ }
+ }
+ _.forEach(balances, (balance, balanceIndex) => {
+ const tokenAddress = balanceInfo[balanceIndex].tokenAddress;
+ const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress;
+ if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) {
+ balancesByOwner[tokenOwnerAddress] = {};
+ }
+ const wrappedBalance = new BigNumber(balance);
+ balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance;
+ });
+ return balancesByOwner;
+ }
+ public addDummyTokenContract(dummy: DummyERC20TokenContract): void {
+ if (!_.isUndefined(this._dummyTokenContracts)) {
+ this._dummyTokenContracts.push(dummy);
+ }
+ }
+ public addTokenOwnerAddress(address: string): void {
+ this._tokenOwnerAddresses.push(address);
+ }
+ public getTokenOwnerAddresses(): string[] {
+ return this._tokenOwnerAddresses;
+ }
+ public getTokenAddresses(): string[] {
+ const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
+ return tokenAddresses;
+ }
+ private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract {
+ const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData);
+ const tokenAddress = erc20ProxyData.tokenAddress;
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ return tokenContractIfExists;
+ }
+ private _validateDummyTokenContractsExistOrThrow(): void {
+ if (_.isUndefined(this._dummyTokenContracts)) {
+ throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
+ }
+ }
+ private _validateProxyContractExistsOrThrow(): void {
+ if (_.isUndefined(this._proxyContract)) {
+ throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"');
+ }
+ }
+}
diff --git a/contracts/asset-proxy/test/utils/erc721_wrapper.ts b/contracts/asset-proxy/test/utils/erc721_wrapper.ts
new file mode 100644
index 000000000..fc43d8c52
--- /dev/null
+++ b/contracts/asset-proxy/test/utils/erc721_wrapper.ts
@@ -0,0 +1,236 @@
+import { artifacts as tokensArtifacts, DummyERC721TokenContract } from '@0x/contracts-erc721';
+import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils';
+import { generatePseudoRandomSalt } from '@0x/order-utils';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy';
+import { artifacts } from '../../src/artifacts';
+
+export class ERC721Wrapper {
+ private readonly _tokenOwnerAddresses: string[];
+ private readonly _contractOwnerAddress: string;
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _provider: Provider;
+ private readonly _dummyTokenContracts: DummyERC721TokenContract[];
+ private _proxyContract?: ERC721ProxyContract;
+ private _proxyIdIfExists?: string;
+ private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
+ constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._provider = provider;
+ this._dummyTokenContracts = [];
+ this._tokenOwnerAddresses = tokenOwnerAddresses;
+ this._contractOwnerAddress = contractOwnerAddress;
+ }
+ public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> {
+ // tslint:disable-next-line:no-unused-variable
+ for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) {
+ this._dummyTokenContracts.push(
+ await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokensArtifacts.DummyERC721Token,
+ this._provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ ),
+ );
+ }
+ return this._dummyTokenContracts;
+ }
+ public async deployProxyAsync(): Promise<ERC721ProxyContract> {
+ this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync(
+ artifacts.ERC721Proxy,
+ this._provider,
+ txDefaults,
+ );
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
+ return this._proxyContract;
+ }
+ public getProxyId(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
+ public async setBalancesAndAllowancesAsync(): Promise<void> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateProxyContractExistsOrThrow();
+ this._initialTokenIdsByOwner = {};
+ for (const dummyTokenContract of this._dummyTokenContracts) {
+ for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
+ // 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])) {
+ this._initialTokenIdsByOwner[tokenOwnerAddress] = {
+ [dummyTokenContract.address]: [],
+ };
+ }
+ if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
+ this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
+ }
+ this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
+
+ await this.approveProxyAsync(dummyTokenContract.address, tokenId);
+ }
+ }
+ }
+ }
+ public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const owner = await tokenContract.ownerOf.callAsync(tokenId);
+ const doesExist = owner !== constants.NULL_ADDRESS;
+ return doesExist;
+ }
+ public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this.approveAsync(proxyAddress, tokenAddress, tokenId);
+ }
+ public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(to, tokenId, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async transferFromAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ currentOwner: string,
+ userAddress: string,
+ ): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, {
+ from: currentOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.burn.sendTransactionAsync(owner, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const owner = await tokenContract.ownerOf.callAsync(tokenId);
+ return owner;
+ }
+ public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId);
+ const isOwner = tokenOwner === userAddress;
+ return isOwner;
+ }
+ public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const operator = (this._proxyContract as ERC721ProxyContract).address;
+ const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
+ return didApproveAll;
+ }
+ public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const approvedAddress = await tokenContract.getApproved.callAsync(tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ const isProxyAnApprovedOperator = approvedAddress === proxyAddress;
+ return isProxyAnApprovedOperator;
+ }
+ public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateBalancesAndAllowancesSetOrThrow();
+ const tokenIdsByOwner: ERC721TokenIdsByOwner = {};
+ const tokenOwnerAddresses: string[] = [];
+ const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = [];
+ for (const dummyTokenContract of this._dummyTokenContracts) {
+ for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
+ const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][
+ dummyTokenContract.address
+ ];
+ for (const tokenId of initialTokenOwnerIds) {
+ tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId));
+ tokenInfo.push({
+ tokenId,
+ tokenAddress: dummyTokenContract.address,
+ });
+ }
+ }
+ }
+ _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => {
+ const tokenAddress = tokenInfo[ownerIndex].tokenAddress;
+ const tokenId = tokenInfo[ownerIndex].tokenId;
+ if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) {
+ tokenIdsByOwner[tokenOwnerAddress] = {
+ [tokenAddress]: [],
+ };
+ }
+ if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) {
+ tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = [];
+ }
+ tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId);
+ });
+ return tokenIdsByOwner;
+ }
+ public getTokenOwnerAddresses(): string[] {
+ return this._tokenOwnerAddresses;
+ }
+ public getTokenAddresses(): string[] {
+ const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
+ return tokenAddresses;
+ }
+ private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract {
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ return tokenContractIfExists;
+ }
+ private _validateDummyTokenContractsExistOrThrow(): void {
+ if (_.isUndefined(this._dummyTokenContracts)) {
+ throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
+ }
+ }
+ private _validateProxyContractExistsOrThrow(): void {
+ if (_.isUndefined(this._proxyContract)) {
+ throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"');
+ }
+ }
+ private _validateBalancesAndAllowancesSetOrThrow(): void {
+ if (_.keys(this._initialTokenIdsByOwner).length === 0) {
+ throw new Error(
+ 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"',
+ );
+ }
+ }
+}
diff --git a/contracts/asset-proxy/test/utils/index.ts b/contracts/asset-proxy/test/utils/index.ts
new file mode 100644
index 000000000..b11f6a45d
--- /dev/null
+++ b/contracts/asset-proxy/test/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './erc20_wrapper';
+export * from './erc721_wrapper';