import { ZeroEx } from '0x.js';
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import * as Web3 from 'web3';
import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher';
import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
import { AssetProxyId, ContractName } from '../../src/utils/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { provider, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Asset Transfer Proxies', () => {
let owner: string;
let notAuthorized: string;
let assetProxyDispatcherAddress: string;
let makerAddress: string;
let takerAddress: string;
let zrx: DummyERC20TokenContract;
let erc721Token: DummyERC721TokenContract;
let erc20Proxy: ERC20ProxyContract;
let erc721Proxy: ERC721ProxyContract;
let erc20Wrapper: ERC20Wrapper;
let erc721Wrapper: ERC721Wrapper;
let erc721MakerTokenId: BigNumber;
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const usedAddresses = ([
owner,
notAuthorized,
assetProxyDispatcherAddress,
makerAddress,
takerAddress,
] = accounts);
erc20Wrapper = new ERC20Wrapper(deployer, provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(deployer, provider, usedAddresses, owner);
[zrx] = await erc20Wrapper.deployDummyERC20TokensAsync();
erc20Proxy = await erc20Wrapper.deployERC20ProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcherAddress, {
from: owner,
});
[erc721Token] = await erc721Wrapper.deployDummyERC721TokensAsync();
erc721Proxy = await erc721Wrapper.deployERC721ProxyAsync();
await erc721Wrapper.setBalancesAndAllowancesAsync();
const erc721Balances = await erc721Wrapper.getBalancesAsync();
erc721MakerTokenId = erc721Balances[makerAddress][erc721Token.address][0];
await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcherAddress, {
from: owner,
});
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('Transfer Proxy - ERC20', () => {
it('should successfully transfer tokens', async () => {
// Construct metadata for ERC20 proxy
const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrx.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: assetProxyDispatcherAddress },
);
// Verify transfer was successful
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
erc20Balances[makerAddress][zrx.address].minus(amount),
);
expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
erc20Balances[takerAddress][zrx.address].add(amount),
);
});
it('should do nothing if transferring 0 amount of a token', async () => {
// Construct metadata for ERC20 proxy
const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrx.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: assetProxyDispatcherAddress },
);
// Verify transfer was successful
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
erc20Balances[makerAddress][zrx.address],
);
expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
erc20Balances[takerAddress][zrx.address],
);
});
it('should throw if allowances are too low', async () => {
// Construct metadata for ERC20 proxy
const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrx.address);
// Create allowance less than transfer amount. Set allowance on proxy.
const allowance = new BigNumber(0);
const transferAmount = new BigNumber(10);
await zrx.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(zrx.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('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: assetProxyDispatcherAddress },
);
// 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: assetProxyDispatcherAddress },
),
).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: assetProxyDispatcherAddress },
),
).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);
});
});
});