UNPKG

@animoca/ethereum-contracts-assets

Version:
331 lines (296 loc) 14.1 kB
const {artifacts, accounts, web3} = require('hardhat'); const {createFixtureLoader} = require('@animoca/ethereum-contracts-core/test/utils/fixture'); const {expectEventWithParamsOverride} = require('@animoca/ethereum-contracts-core/test/utils/events'); const {BN, expectEvent, expectRevert} = require('@openzeppelin/test-helpers'); const {One, ZeroAddress} = require('@animoca/ethereum-contracts-core').constants; const {makeNonFungibleTokenId, makeNonFungibleCollectionId, makeFungibleCollectionId} = require('@animoca/blockchain-inventory_metadata').inventoryIds; const ReceiverType = require('../../ReceiverType'); const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock'); const ERC1155TokenReceiverMock = artifacts.require('ERC1155TokenReceiverMock'); function shouldBehaveLikeERC721Mintable({nfMaskLength, contractName, revertMessages, eventParamsOverrides, interfaces, methods, deploy}) { const [deployer, owner] = accounts; const { 'mint(address,uint256)': mint_ERC721, 'batchMint(address,uint256[])': batchMint_ERC721, 'safeMint(address,uint256,bytes)': safeMint_ERC721, } = methods; if (mint_ERC721 === undefined) { console.log( `ERC721Mintable: non-standard ERC721 method mint(address,uint256)` + ` is not supported by ${contractName}, associated tests will be skipped` ); } if (batchMint_ERC721 === undefined) { console.log( `ERC721Mintable: non-standard ERC721 method batchMint(address,uint256[])` + `is not supported by ${contractName}, associated tests will be skipped` ); } if (safeMint_ERC721 === undefined) { console.log( `ERC721Mintable: non-standard ERC721 method safeMint(address,uint256,bytes)` + ` is not supported by ${contractName}, associated tests will be skipped` ); } const fungibleToken = makeFungibleCollectionId(1); const nfCollection = makeNonFungibleCollectionId(1, nfMaskLength); const otherNFCollection = makeNonFungibleCollectionId(2, nfMaskLength); const nft1 = makeNonFungibleTokenId(1, 1, nfMaskLength); const nft2 = makeNonFungibleTokenId(2, 1, nfMaskLength); const nftOtherCollection = makeNonFungibleTokenId(1, 2, nfMaskLength); const unknownNFT = makeNonFungibleTokenId(999, 1, nfMaskLength); describe('like a mintable ERC721', function () { const fixtureLoader = createFixtureLoader(accounts, web3.eth.currentProvider); const fixture = async function () { this.token = await deploy(deployer); this.receiver721 = await ERC721ReceiverMock.new(true, this.token.address); this.refusingReceiver721 = await ERC721ReceiverMock.new(false, this.token.address); this.wrongTokenReceiver721 = await ERC721ReceiverMock.new(false, ZeroAddress); this.receiver1155 = await ERC1155TokenReceiverMock.new(true, this.token.address); this.refusingReceiver1155 = await ERC1155TokenReceiverMock.new(false, this.token.address); this.wrongTokenReceiver1155 = await ERC1155TokenReceiverMock.new(false, ZeroAddress); }; beforeEach(async function () { await fixtureLoader(fixture, this); }); const mintWasSuccessful = function (tokenIds, data, options, safe, receiverType) { const ids = Array.isArray(tokenIds) ? tokenIds : [tokenIds]; it('gives the ownership of the token(s) to the given address', async function () { for (const id of ids) { (await this.token.ownerOf(id)).should.be.equal(this.toWhom); } }); it('has an empty approval for the token(s)', async function () { for (const id of ids) { (await this.token.getApproved(id)).should.be.equal(ZeroAddress); } }); it('emits Transfer event(s)', function () { for (const id of ids) { expectEventWithParamsOverride( receipt, 'Transfer', { _from: ZeroAddress, _to: this.toWhom, _tokenId: id, }, eventParamsOverrides ); } }); if (interfaces.ERC1155) { if (Array.isArray(tokenIds)) { it('[ERC1155] emits a TransferBatch event', function () { expectEventWithParamsOverride( receipt, 'TransferBatch', { _operator: options.from, _from: ZeroAddress, _to: this.toWhom, _ids: tokenIds, _values: tokenIds.map(() => 1), }, eventParamsOverrides ); }); } else { it('[ERC1155] emits a TransferSingle event', function () { expectEventWithParamsOverride( receipt, 'TransferSingle', { _operator: options.from, _from: ZeroAddress, _to: this.toWhom, _id: tokenIds, _value: 1, }, eventParamsOverrides ); }); } } it('adjusts recipient balance', async function () { const quantity = new BN(Array.isArray(tokenIds) ? `${tokenIds.length}` : '1'); (await this.token.balanceOf(this.toWhom)).should.be.bignumber.equal(quantity); }); if (interfaces.ERC1155Inventory) { it('[ERC1155Inventory] increases the recipient Non-Fungible Collection(s) balance(s)', async function () { const nbCollectionNFTs = ids.filter((id) => id != nftOtherCollection).length; const nbOtherCollectionNFTs = ids.length - nbCollectionNFTs; (await this.token.balanceOf(this.toWhom, nfCollection)).should.be.bignumber.equal(new BN(nbCollectionNFTs)); (await this.token.balanceOf(this.toWhom, otherNFCollection)).should.be.bignumber.equal(new BN(nbOtherCollectionNFTs)); }); it('[ERC1155Inventory] increases the Non-Fungible Collection(s) total supply', async function () { const nbCollectionNFTs = ids.filter((id) => id != nftOtherCollection).length; const nbOtherCollectionNFTs = ids.length - nbCollectionNFTs; (await this.token.totalSupply(nfCollection)).should.be.bignumber.equal(new BN(nbCollectionNFTs)); (await this.token.totalSupply(otherNFCollection)).should.be.bignumber.equal(new BN(nbOtherCollectionNFTs)); }); } if (safe && receiverType == ReceiverType.ERC721_RECEIVER) { it('should call onERC721Received', async function () { await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { from: ZeroAddress, tokenId: tokenIds, data: data ? data : null, }); }); } else if (interfaces.ERC1155) { if (receiverType == ReceiverType.ERC1155_RECEIVER) { if (Array.isArray(tokenIds)) { it('[ERC1155] should call onERC1155BatchReceived', async function () { await expectEvent.inTransaction(receipt.tx, ERC1155TokenReceiverMock, 'ReceivedBatch', { operator: options.from, from: ZeroAddress, ids: tokenIds, values: tokenIds.map(() => 1), data: data ? data : null, }); }); } else { it('[ERC1155] should call onERC1155Received', async function () { await expectEvent.inTransaction(receipt.tx, ERC1155TokenReceiverMock, 'ReceivedSingle', { operator: options.from, from: ZeroAddress, id: tokenIds, value: 1, data: data ? data : null, }); }); } } } }; const shouldRevertOnPreconditions = function (mintFunction, safe) { describe('Pre-conditions', function () { const data = '0x42'; const options = {from: deployer}; it('reverts if minted to the zero address', async function () { await expectRevert(mintFunction.call(this, ZeroAddress, nft1, data, options), revertMessages.MintToZero); }); it('reverts if the token already exist', async function () { await mintFunction.call(this, owner, unknownNFT, data, options); await expectRevert(mintFunction.call(this, owner, unknownNFT, data, options), revertMessages.ExistingOrBurntNFT); }); it('reverts if sent by a non-minter', async function () { await expectRevert(mintFunction.call(this, owner, nft1, data, {from: owner}), revertMessages.NotMinter); }); if (safe) { it('reverts when sent to a non-receiver contract', async function () { await expectRevert.unspecified(mintFunction.call(this, this.token.address, nft1, data, options)); }); it('reverts when sent to an ERC721Receiver which refuses the transfer', async function () { await expectRevert(mintFunction.call(this, this.refusingReceiver721.address, nft1, data, options), revertMessages.TransferRejected); }); it('reverts when sent to an ERC721Receiver which accepts another token', async function () { await expectRevert.unspecified(mintFunction.call(this, this.wrongTokenReceiver721.address, nft1, data, options)); }); if (interfaces.ERC1155) { it('reverts when sent to an ERC1155TokenReceiver which refuses the transfer', async function () { await expectRevert(mintFunction.call(this, this.refusingReceiver1155.address, nft1, data, options), revertMessages.TransferRejected); }); it('reverts when sent to an ERC1155TokenReceiver which accepts another token', async function () { await expectRevert.unspecified(mintFunction.call(this, this.wrongTokenReceiver1155.address, nft1, data, options)); }); } else { it('reverts when sent to an ERC1155TokenReceiver', async function () { await expectRevert.unspecified(mintFunction.call(this, this.receiver1155.address, nft1, data, options)); }); } } if (interfaces.ERC1155) { it('[ERC1155] reverts if the id is a Fungible Token', async function () { await expectRevert(mintFunction.call(this, owner, fungibleToken, data, options), revertMessages.NotNFT); }); } if (interfaces.ERC1155Inventory) { it('[ERC1155Inventory] reverts if the id is a Non-Fungible Collection', async function () { await expectRevert(mintFunction.call(this, owner, nfCollection, data, options), revertMessages.NotNFT); }); } }); }; const shouldMintTokenToRecipient = function (mintFunction, ids, data, safe) { const options = {from: deployer}; context('when sent to a wallet', function () { beforeEach(async function () { this.toWhom = owner; receipt = await mintFunction.call(this, this.toWhom, ids, data, options); }); mintWasSuccessful(ids, data, options, safe, ReceiverType.WALLET); }); context('when sent to an ERC721Receiver contract', function () { beforeEach(async function () { this.toWhom = this.receiver721.address; receipt = await mintFunction.call(this, this.toWhom, ids, data, options); }); mintWasSuccessful(ids, data, options, safe, ReceiverType.ERC721_RECEIVER); }); if (interfaces.ERC1155) { context('when sent to an ERC1155TokenReceiver contract', function () { beforeEach(async function () { this.toWhom = this.receiver1155.address; receipt = await mintFunction.call(this, this.toWhom, ids, data, options); }); mintWasSuccessful(ids, data, options, safe, ReceiverType.ERC1155_RECEIVER); }); } }; context('mint(address,uint256)', function () { if (mint_ERC721 === undefined) { return; } const mintFn = async function (to, tokenId, _data, options) { return mint_ERC721(this.token, to, tokenId, options); }; const safe = false; shouldRevertOnPreconditions(mintFn, safe); shouldMintTokenToRecipient(mintFn, nft1, undefined, safe); }); context('batchMint(address,uint256[])', function () { if (batchMint_ERC721 === undefined) { return; } const mintFn = async function (to, tokenIds, _data, options) { const ids = Array.isArray(tokenIds) ? tokenIds : [tokenIds]; return batchMint_ERC721(this.token, to, ids, options); }; const safe = false; shouldRevertOnPreconditions(mintFn, safe); context('with an empty list of tokens', function () { shouldMintTokenToRecipient(mintFn, [], undefined, safe); }); context('with a single token', function () { shouldMintTokenToRecipient(mintFn, [nft1], undefined, safe); }); context('with a list of tokens from the same collection', function () { shouldMintTokenToRecipient(mintFn, [nft1, nft2], undefined, safe); }); if (interfaces.ERC1155Inventory) { context('[ERC1155Inventory] with a list of tokens sorted by collection', function () { shouldMintTokenToRecipient(mintFn, [nft1, nft2, nftOtherCollection], undefined, safe); }); context('[ERC1155Inventory] with an unsorted list of tokens from different collections', function () { shouldMintTokenToRecipient(mintFn, [nft1, nftOtherCollection, nft2], undefined, safe); }); } }); context('safeMint(address,uint256,bytes)', function () { if (safeMint_ERC721 === undefined) { return; } const mintFn = async function (to, tokenId, data, options) { return safeMint_ERC721(this.token, to, tokenId, data, options); }; const safe = true; shouldRevertOnPreconditions(mintFn, safe); shouldMintTokenToRecipient(mintFn, nft1, '0x42', safe); }); }); } module.exports = { shouldBehaveLikeERC721Mintable, };