@animoca/ethereum-contracts-assets
Version:
Base assets contracts
261 lines (229 loc) • 10.6 kB
JavaScript
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, expectRevert} = require('@openzeppelin/test-helpers');
const {behaviors, constants} = require('@animoca/ethereum-contracts-core');
const {One, ZeroAddress} = constants;
const interfaces721 = require('../../../../../src/interfaces/ERC165/ERC721');
const {makeNonFungibleTokenId, makeNonFungibleCollectionId, makeFungibleCollectionId} =
require('@animoca/blockchain-inventory_metadata').inventoryIds;
function shouldBehaveLikeERC721Burnable({nfMaskLength, contractName, revertMessages, eventParamsOverrides, interfaces, methods, deploy, mint}) {
const [deployer, owner, other, approved, operator] = accounts;
const {'burnFrom(address,uint256)': burnFrom_ERC721, 'batchBurnFrom(address,uint256[])': batchBurnFrom_ERC721} = methods;
if (burnFrom_ERC721 === undefined) {
console.log(
`ERC721Burnable: non-standard ERC721 method burnFrom(address,uint256)` +
` is not supported by ${contractName}, associated tests will be skipped`
);
}
if (batchBurnFrom_ERC721 === undefined) {
console.log(
`ERC721Burnable: non-standard ERC721 method batchBurnFrom(address,uint256[])` +
`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 burnable ERC721', function () {
const fixtureLoader = createFixtureLoader(accounts, web3.eth.currentProvider);
const fixture = async function () {
this.token = await deploy(deployer);
await mint(this.token, owner, fungibleToken, 1, {from: deployer});
await mint(this.token, owner, nft1, 1, {from: deployer});
await mint(this.token, owner, nft2, 1, {from: deployer});
await mint(this.token, owner, nftOtherCollection, 1, {from: deployer});
await this.token.approve(approved, nft1, {from: owner});
await this.token.approve(approved, nft2, {from: owner});
await this.token.approve(approved, nftOtherCollection, {from: owner});
await this.token.setApprovalForAll(operator, true, {from: owner});
// pre-transfer state
this.nftBalance = await this.token.balanceOf(owner);
if (interfaces.ERC1155Inventory) {
this.nfcSupply = await this.token.totalSupply(nfCollection);
this.otherNFCSupply = await this.token.totalSupply(otherNFCollection);
this.nfcBalance = await this.token.balanceOf(owner, nfCollection);
this.otherNFCBalance = await this.token.balanceOf(owner, otherNFCollection);
}
};
beforeEach(async function () {
await fixtureLoader(fixture, this);
});
let receipt = null;
const burnWasSuccessful = function (tokenIds, options) {
const ids = Array.isArray(tokenIds) ? tokenIds : [tokenIds];
it('transfers the ownership of the token(s)', async function () {
for (const id of ids) {
await expectRevert(this.token.ownerOf(id), revertMessages.NonExistingNFT);
}
});
it('clears the approval for the token(s)', async function () {
for (const id of ids) {
await expectRevert(this.token.getApproved(id), revertMessages.NonExistingNFT);
}
});
it('emits Transfer event(s)', function () {
for (const id of ids) {
expectEventWithParamsOverride(
receipt,
'Transfer',
{
_from: owner,
_to: ZeroAddress,
_tokenId: id,
},
eventParamsOverrides
);
}
});
if (interfaces.ERC1155) {
if (Array.isArray(tokenIds)) {
it('[ERC1155] emits a TransferBatch event', function () {
expectEventWithParamsOverride(
receipt,
'TransferBatch',
{
_operator: options.from,
_from: owner,
_to: ZeroAddress,
_ids: tokenIds,
_values: tokenIds.map(() => 1),
},
eventParamsOverrides
);
});
} else {
it('[ERC1155] emits a TransferSingle event', function () {
expectEventWithParamsOverride(
receipt,
'TransferSingle',
{
_operator: options.from,
_from: owner,
_to: ZeroAddress,
_id: tokenIds,
_value: 1,
},
eventParamsOverrides
);
});
}
}
it('decreases the sender balance', async function () {
(await this.token.balanceOf(owner)).should.be.bignumber.equal(this.nftBalance.subn(ids.length));
});
if (interfaces.ERC1155Inventory) {
const nbCollectionNFTs = ids.filter((id) => id != nftOtherCollection).length;
const nbOtherCollectionNFTs = ids.length - nbCollectionNFTs;
it('[ERC1155Inventory] decreases the sender Non-Fungible Collection(s) balance(s)', async function () {
(await this.token.balanceOf(owner, nfCollection)).should.be.bignumber.equal(this.nfcBalance.subn(nbCollectionNFTs));
(await this.token.balanceOf(owner, otherNFCollection)).should.be.bignumber.equal(this.otherNFCBalance.subn(nbOtherCollectionNFTs));
});
it('[ERC1155Inventory] decreases the Non-Fungible Collections total supply', async function () {
(await this.token.totalSupply(nfCollection)).should.be.bignumber.equal(this.nfcSupply.subn(nbCollectionNFTs));
(await this.token.totalSupply(otherNFCollection)).should.be.bignumber.equal(this.otherNFCSupply.subn(nbOtherCollectionNFTs));
});
}
};
const shouldBurnTokenBySender = function (burnFunction, ids) {
context('when called by the owner', function () {
const options = {from: owner};
beforeEach(async function () {
receipt = await burnFunction.call(this, owner, ids, options);
});
burnWasSuccessful(ids, options);
});
context('when called by a wallet with single token approval', function () {
const options = {from: approved};
beforeEach(async function () {
receipt = await burnFunction.call(this, owner, ids, options);
});
burnWasSuccessful(ids, options);
});
context('when called by an operator', function () {
const options = {from: operator};
beforeEach(async function () {
receipt = await burnFunction.call(this, owner, ids, options);
});
burnWasSuccessful(ids, options);
});
};
const shouldRevertOnPreconditions = function (burnFunction) {
describe('Pre-conditions', function () {
if (interfaces.Pausable) {
it('[Pausable] reverts when paused', async function () {
await this.token.pause({from: deployer});
await expectRevert(burnFunction.call(this, owner, nft1, {from: owner}), revertMessages.AlreadyPaused);
});
}
it('reverts if the token does not exist', async function () {
await expectRevert(burnFunction.call(this, owner, unknownNFT, {from: owner}), revertMessages.NonOwnedNFT);
});
it('reverts if `from` is not the token owner', async function () {
// await this.token.approve(owner, nft1, {from: other});
await expectRevert(burnFunction.call(this, other, nft1, {from: other}), revertMessages.NonOwnedNFT);
});
it('reverts if the sender is not authorized for the token', async function () {
await expectRevert(burnFunction.call(this, owner, nft1, {from: other}), revertMessages.NonApproved);
});
if (interfaces.ERC1155) {
it('[ERC1155] reverts if the id is a Fungible Token', async function () {
await expectRevert(burnFunction.call(this, owner, fungibleToken, {from: owner}), revertMessages.NonOwnedNFT);
});
}
if (interfaces.ERC1155Inventory) {
it('[ERC1155Inventory] reverts if the id is a Non-Fungible Collection', async function () {
await expectRevert(burnFunction.call(this, owner, nfCollection, {from: owner}), revertMessages.NonOwnedNFT);
});
}
});
};
describe('burnFrom(address,uint256)', function () {
if (burnFrom_ERC721 === undefined) {
return;
}
const burnFn = async function (from, tokenId, options) {
return burnFrom_ERC721(this.token, from, tokenId, options);
};
shouldRevertOnPreconditions(burnFn);
shouldBurnTokenBySender(burnFn, nft1);
});
describe('batchBurnFrom(address,uint256[])', function () {
if (batchBurnFrom_ERC721 === undefined) {
return;
}
const burnFn = async function (from, tokenIds, options) {
const ids = Array.isArray(tokenIds) ? tokenIds : [tokenIds];
return batchBurnFrom_ERC721(this.token, from, ids, options);
};
shouldRevertOnPreconditions(burnFn);
context('with an empty list of tokens', function () {
shouldBurnTokenBySender(burnFn, []);
});
context('with a single token', function () {
shouldBurnTokenBySender(burnFn, [nft1]);
});
context('with a list of tokens from the same collection', function () {
shouldBurnTokenBySender(burnFn, [nft1, nft2]);
});
if (interfaces.ERC1155Inventory) {
context('[ERC1155Inventory] with a list of tokens sorted by collection', function () {
shouldBurnTokenBySender(burnFn, [nft1, nft2, nftOtherCollection]);
});
context('[ERC1155Inventory] with an unsorted list of tokens from different collections', function () {
shouldBurnTokenBySender(burnFn, [nft1, nftOtherCollection, nft2]);
});
}
});
if (interfaces.ERC721Burnable) {
behaviors.shouldSupportInterfaces([interfaces721.ERC721Burnable]);
}
});
}
module.exports = {
shouldBehaveLikeERC721Burnable,
};