375 lines
18 KiB
TypeScript
375 lines
18 KiB
TypeScript
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
|
|
import {
|
|
artifacts as ERC1155Artifacts,
|
|
ERC1155Events,
|
|
ERC1155MintableContract,
|
|
ERC1155TransferBatchEventArgs,
|
|
Erc1155Wrapper,
|
|
} from '@0x/contracts-erc1155';
|
|
import {
|
|
artifacts as ERC20Artifacts,
|
|
DummyERC20TokenContract,
|
|
ERC20TokenEvents,
|
|
ERC20TokenTransferEventArgs,
|
|
} from '@0x/contracts-erc20';
|
|
import {
|
|
artifacts as ERC721Artifacts,
|
|
DummyERC721TokenContract,
|
|
ERC721TokenEvents,
|
|
ERC721TokenTransferEventArgs,
|
|
} from '@0x/contracts-erc721';
|
|
import {
|
|
blockchainTests,
|
|
constants,
|
|
expect,
|
|
getRandomInteger,
|
|
randomAddress,
|
|
verifyEventsFromLogs,
|
|
} from '@0x/contracts-test-utils';
|
|
import { BigNumber, hexUtils, LibAssetDataTransferRevertErrors } from '@0x/utils';
|
|
import { LogWithDecodedArgs } from 'ethereum-types';
|
|
|
|
import { artifacts } from './artifacts';
|
|
import { TestForwarderContract } from './wrappers';
|
|
|
|
// tslint:disable:no-unnecessary-type-assertion
|
|
blockchainTests.resets('Supported asset type unit tests', env => {
|
|
let forwarder: TestForwarderContract;
|
|
let assetDataEncoder: IAssetDataContract;
|
|
let bridgeAddress: string;
|
|
let bridgeData: string;
|
|
let receiver: string;
|
|
|
|
let erc20Token: DummyERC20TokenContract;
|
|
let erc721Token: DummyERC721TokenContract;
|
|
let erc1155Token: ERC1155MintableContract;
|
|
let erc1155Wrapper: Erc1155Wrapper;
|
|
let nftId: BigNumber;
|
|
|
|
let erc20AssetData: string;
|
|
let erc721AssetData: string;
|
|
let erc20BridgeAssetData: string;
|
|
let staticCallAssetData: string;
|
|
let multiAssetData: string;
|
|
|
|
before(async () => {
|
|
[receiver] = await env.getAccountAddressesAsync();
|
|
assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
|
|
|
forwarder = await TestForwarderContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestForwarder,
|
|
env.provider,
|
|
env.txDefaults,
|
|
{ ...artifacts, ...ERC20Artifacts, ...ERC721Artifacts, ...ERC1155Artifacts },
|
|
);
|
|
|
|
erc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
|
ERC20Artifacts.DummyERC20Token,
|
|
env.provider,
|
|
env.txDefaults,
|
|
ERC20Artifacts,
|
|
constants.DUMMY_TOKEN_NAME,
|
|
constants.DUMMY_TOKEN_SYMBOL,
|
|
constants.DUMMY_TOKEN_DECIMALS,
|
|
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
|
|
);
|
|
erc20AssetData = assetDataEncoder.ERC20Token(erc20Token.address).getABIEncodedTransactionData();
|
|
|
|
erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
|
|
ERC721Artifacts.DummyERC721Token,
|
|
env.provider,
|
|
env.txDefaults,
|
|
ERC721Artifacts,
|
|
constants.DUMMY_TOKEN_NAME,
|
|
constants.DUMMY_TOKEN_SYMBOL,
|
|
);
|
|
nftId = getRandomInteger(0, constants.MAX_UINT256);
|
|
erc721AssetData = assetDataEncoder.ERC721Token(erc721Token.address, nftId).getABIEncodedTransactionData();
|
|
|
|
erc1155Token = await ERC1155MintableContract.deployFrom0xArtifactAsync(
|
|
ERC1155Artifacts.ERC1155Mintable,
|
|
env.provider,
|
|
env.txDefaults,
|
|
ERC1155Artifacts,
|
|
);
|
|
erc1155Wrapper = new Erc1155Wrapper(erc1155Token, receiver);
|
|
|
|
bridgeAddress = randomAddress();
|
|
bridgeData = hexUtils.random();
|
|
erc20BridgeAssetData = assetDataEncoder
|
|
.ERC20Bridge(erc20Token.address, bridgeAddress, bridgeData)
|
|
.getABIEncodedTransactionData();
|
|
|
|
staticCallAssetData = assetDataEncoder
|
|
.StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL)
|
|
.getABIEncodedTransactionData();
|
|
|
|
multiAssetData = assetDataEncoder
|
|
.MultiAsset([new BigNumber(1)], [erc20AssetData])
|
|
.getABIEncodedTransactionData();
|
|
});
|
|
|
|
describe('_areUnderlyingAssetsEqual', () => {
|
|
it('returns true if assetData1 == assetData2 are ERC20', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, erc20AssetData).callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns true if assetData1 == assetData2 are ERC20Bridge', async () => {
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(erc20BridgeAssetData, erc20BridgeAssetData)
|
|
.callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns true if assetData2 is the ERC20Bridge equivalent of assetData1', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, erc20BridgeAssetData).callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns false if assetData1 != assetData2 are ERC20', async () => {
|
|
const differentERC20AssetData = assetDataEncoder.ERC20Token(randomAddress()).getABIEncodedTransactionData();
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(erc20AssetData, differentERC20AssetData)
|
|
.callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns false if assetData1 is ERC20 and assetData2 is ERC721', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, erc721AssetData).callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns false if assetData2 is ERC20Bridge, but for a different token than assetData1', async () => {
|
|
const mismatchedErc20BridgeAssetData = assetDataEncoder
|
|
.ERC20Bridge(randomAddress(), bridgeAddress, bridgeData)
|
|
.getABIEncodedTransactionData();
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(erc20AssetData, mismatchedErc20BridgeAssetData)
|
|
.callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns true if assetData1 == assetData2 are ERC721', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(erc721AssetData, erc721AssetData).callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns false if assetData1 != assetData2 are ERC721', async () => {
|
|
const differentErc721AssetData = assetDataEncoder
|
|
.ERC721Token(randomAddress(), getRandomInteger(0, constants.MAX_UINT256))
|
|
.getABIEncodedTransactionData();
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(erc721AssetData, differentErc721AssetData)
|
|
.callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns true if assetData1 == assetData2 are StaticCall', async () => {
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(staticCallAssetData, staticCallAssetData)
|
|
.callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns false if assetData1 != assetData2 are StaticCall', async () => {
|
|
const differentStaticCallAssetData = assetDataEncoder
|
|
.StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL)
|
|
.getABIEncodedTransactionData();
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(staticCallAssetData, differentStaticCallAssetData)
|
|
.callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns false if assetData1 is ERC20 and assetData2 is MultiAsset', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, multiAssetData).callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
it('returns true if assetData1 == assetData2 are MultiAsset (single nested asset)', async () => {
|
|
const result = await forwarder.areUnderlyingAssetsEqual(multiAssetData, multiAssetData).callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns true if assetData1 == assetData2 are MultiAsset (multiple nested assets)', async () => {
|
|
const assetData = assetDataEncoder
|
|
.MultiAsset(
|
|
[getRandomInteger(0, constants.MAX_UINT256), new BigNumber(1)],
|
|
[erc20AssetData, erc721AssetData],
|
|
)
|
|
.getABIEncodedTransactionData();
|
|
const result = await forwarder.areUnderlyingAssetsEqual(assetData, assetData).callAsync();
|
|
expect(result).to.be.true();
|
|
});
|
|
it('returns false if assetData1 != assetData2 are MultiAsset', async () => {
|
|
const differentMultiAssetData = assetDataEncoder
|
|
.MultiAsset([getRandomInteger(0, constants.MAX_UINT256)], [erc721AssetData])
|
|
.getABIEncodedTransactionData();
|
|
const result = await forwarder
|
|
.areUnderlyingAssetsEqual(multiAssetData, differentMultiAssetData)
|
|
.callAsync();
|
|
expect(result).to.be.false();
|
|
});
|
|
});
|
|
|
|
describe('transferOut', () => {
|
|
const TRANSFER_AMOUNT = new BigNumber(1);
|
|
before(async () => {
|
|
await erc20Token
|
|
.setBalance(forwarder.address, constants.INITIAL_ERC20_BALANCE)
|
|
.awaitTransactionSuccessAsync();
|
|
await erc721Token.mint(forwarder.address, nftId).awaitTransactionSuccessAsync();
|
|
});
|
|
|
|
it('transfers an ERC20 token given ERC20 assetData', async () => {
|
|
const txReceipt = await forwarder
|
|
.transferOut(erc20AssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
verifyEventsFromLogs<ERC20TokenTransferEventArgs>(
|
|
txReceipt.logs,
|
|
[{ _from: forwarder.address, _to: receiver, _value: TRANSFER_AMOUNT }],
|
|
ERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('transfers an ERC721 token given ERC721 assetData and amount == 1', async () => {
|
|
const txReceipt = await forwarder
|
|
.transferOut(erc721AssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
verifyEventsFromLogs<ERC721TokenTransferEventArgs>(
|
|
txReceipt.logs,
|
|
[{ _from: forwarder.address, _to: receiver, _tokenId: nftId }],
|
|
ERC721TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('reverts if attempting to transfer an ERC721 token with amount != 1', async () => {
|
|
const invalidAmount = new BigNumber(2);
|
|
const tx = forwarder
|
|
.transferOut(erc721AssetData, invalidAmount)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
const expectedError = new LibAssetDataTransferRevertErrors.Erc721AmountMustEqualOneError(invalidAmount);
|
|
return expect(tx).to.revertWith(expectedError);
|
|
});
|
|
it('transfers a single ERC1155 token', async () => {
|
|
const values = [new BigNumber(1)];
|
|
const amount = new BigNumber(1);
|
|
const ids = [await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], values)];
|
|
const assetData = assetDataEncoder
|
|
.ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES)
|
|
.getABIEncodedTransactionData();
|
|
const txReceipt = await forwarder
|
|
.transferOut(assetData, amount)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
verifyEventsFromLogs<ERC1155TransferBatchEventArgs>(
|
|
txReceipt.logs,
|
|
[{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values }],
|
|
ERC1155Events.TransferBatch,
|
|
);
|
|
});
|
|
it('transfers multiple ids of an ERC1155 token', async () => {
|
|
const amount = new BigNumber(1);
|
|
const ids = [
|
|
await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [amount]),
|
|
await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [amount]),
|
|
];
|
|
const values = [amount, amount];
|
|
const assetData = assetDataEncoder
|
|
.ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES)
|
|
.getABIEncodedTransactionData();
|
|
const txReceipt = await forwarder.transferOut(assetData, amount).awaitTransactionSuccessAsync();
|
|
verifyEventsFromLogs<ERC1155TransferBatchEventArgs>(
|
|
txReceipt.logs,
|
|
[{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values }],
|
|
ERC1155Events.TransferBatch,
|
|
);
|
|
});
|
|
it('scales up values when transfering ERC1155 tokens', async () => {
|
|
const amount = new BigNumber(2);
|
|
const values = [new BigNumber(1), new BigNumber(2)];
|
|
const scaledValues = values.map(value => value.times(amount));
|
|
const ids = [
|
|
await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [scaledValues[0]]),
|
|
await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [scaledValues[1]]),
|
|
];
|
|
const assetData = assetDataEncoder
|
|
.ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES)
|
|
.getABIEncodedTransactionData();
|
|
const txReceipt = await forwarder.transferOut(assetData, amount).awaitTransactionSuccessAsync();
|
|
verifyEventsFromLogs<ERC1155TransferBatchEventArgs>(
|
|
txReceipt.logs,
|
|
[{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values: scaledValues }],
|
|
ERC1155Events.TransferBatch,
|
|
);
|
|
});
|
|
it('transfers a single ERC20 token wrapped as MultiAsset', async () => {
|
|
const nestedAmount = new BigNumber(1337);
|
|
const erc20MultiAssetData = assetDataEncoder
|
|
.MultiAsset([nestedAmount], [erc20AssetData])
|
|
.getABIEncodedTransactionData();
|
|
const multiAssetAmount = new BigNumber(2);
|
|
const txReceipt = await forwarder
|
|
.transferOut(erc20MultiAssetData, multiAssetAmount)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
verifyEventsFromLogs<ERC20TokenTransferEventArgs>(
|
|
txReceipt.logs,
|
|
[{ _from: forwarder.address, _to: receiver, _value: multiAssetAmount.times(nestedAmount) }],
|
|
ERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('transfers ERC20, ERC721, and StaticCall assets wrapped as MultiAsset', async () => {
|
|
const nestedAmounts = [new BigNumber(1337), TRANSFER_AMOUNT, TRANSFER_AMOUNT];
|
|
const assortedMultiAssetData = assetDataEncoder
|
|
.MultiAsset(nestedAmounts, [erc20AssetData, erc721AssetData, staticCallAssetData])
|
|
.getABIEncodedTransactionData();
|
|
const txReceipt = await forwarder
|
|
.transferOut(assortedMultiAssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
expect(txReceipt.logs.length).to.equal(2);
|
|
// tslint:disable:no-unnecessary-type-assertion
|
|
const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args;
|
|
const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args;
|
|
// tslint:enable:no-unnecessary-type-assertion
|
|
expect(erc20TransferEvent).to.deep.equal({
|
|
_from: forwarder.address,
|
|
_to: receiver,
|
|
_value: nestedAmounts[0],
|
|
});
|
|
expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId });
|
|
});
|
|
it('performs nested MultiAsset transfers', async () => {
|
|
const nestedAmounts = [TRANSFER_AMOUNT, TRANSFER_AMOUNT, TRANSFER_AMOUNT];
|
|
const assortedMultiAssetData = assetDataEncoder
|
|
.MultiAsset(nestedAmounts, [multiAssetData, erc721AssetData, staticCallAssetData])
|
|
.getABIEncodedTransactionData();
|
|
const txReceipt = await forwarder
|
|
.transferOut(assortedMultiAssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
expect(txReceipt.logs.length).to.equal(2);
|
|
// tslint:disable:no-unnecessary-type-assertion
|
|
const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args;
|
|
const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args;
|
|
// tslint:enable:no-unnecessary-type-assertion
|
|
expect(erc20TransferEvent).to.deep.equal({
|
|
_from: forwarder.address,
|
|
_to: receiver,
|
|
_value: TRANSFER_AMOUNT,
|
|
});
|
|
expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId });
|
|
});
|
|
it('transfers an ERC20 token given ERC20Bridge assetData', async () => {
|
|
const txReceipt = await forwarder
|
|
.transferOut(erc20BridgeAssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
verifyEventsFromLogs<ERC20TokenTransferEventArgs>(
|
|
txReceipt.logs,
|
|
[{ _from: forwarder.address, _to: receiver, _value: TRANSFER_AMOUNT }],
|
|
ERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('noops (emits no events) for StaticCall assetData', async () => {
|
|
const txReceipt = await forwarder
|
|
.transferOut(staticCallAssetData, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
expect(txReceipt.logs.length).to.equal(0);
|
|
});
|
|
it('reverts if assetData is unsupported', async () => {
|
|
const randomBytes = hexUtils.random();
|
|
const tx = forwarder
|
|
.transferOut(randomBytes, TRANSFER_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: receiver });
|
|
const expectedError = new LibAssetDataTransferRevertErrors.UnsupportedAssetProxyError(
|
|
hexUtils.slice(randomBytes, 0, 4),
|
|
);
|
|
return expect(tx).to.revertWith(expectedError);
|
|
});
|
|
});
|
|
});
|