You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
9.6 KiB
JavaScript
210 lines
9.6 KiB
JavaScript
2 years ago
|
const { deployContract, getBlockTimestamp, mineBlockTimestamp, offsettedIndex } = require('../helpers.js');
|
||
|
const { expect } = require('chai');
|
||
|
const { constants } = require('@openzeppelin/test-helpers');
|
||
|
const { ZERO_ADDRESS } = constants;
|
||
|
|
||
|
const createTestSuite = ({ contract, constructorArgs }) =>
|
||
|
function () {
|
||
|
let offsetted;
|
||
|
|
||
|
context(`${contract}`, function () {
|
||
|
beforeEach(async function () {
|
||
|
this.erc721aBurnable = await deployContract(contract, constructorArgs);
|
||
|
|
||
|
this.startTokenId = this.erc721aBurnable.startTokenId
|
||
|
? (await this.erc721aBurnable.startTokenId()).toNumber()
|
||
|
: 0;
|
||
|
|
||
|
offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
|
||
|
});
|
||
|
|
||
|
beforeEach(async function () {
|
||
|
const [owner, addr1, addr2, spender] = await ethers.getSigners();
|
||
|
this.owner = owner;
|
||
|
this.addr1 = addr1;
|
||
|
this.addr2 = addr2;
|
||
|
this.spender = spender;
|
||
|
this.numTestTokens = 10;
|
||
|
this.burnedTokenId = 5;
|
||
|
this.notBurnedTokenId = 6;
|
||
|
await this.erc721aBurnable['safeMint(address,uint256)'](this.addr1.address, this.numTestTokens);
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(this.burnedTokenId);
|
||
|
});
|
||
|
|
||
|
context('totalSupply()', function () {
|
||
|
it('has the expected value', async function () {
|
||
|
expect(await this.erc721aBurnable.totalSupply()).to.equal(9);
|
||
|
});
|
||
|
|
||
|
it('is reduced by burns', async function () {
|
||
|
const supplyBefore = await this.erc721aBurnable.totalSupply();
|
||
|
|
||
|
for (let i = 0; i < offsetted(2); ++i) {
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
|
||
|
|
||
|
const supplyNow = await this.erc721aBurnable.totalSupply();
|
||
|
expect(supplyNow).to.equal(supplyBefore - (i + 1));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('changes numberBurned', async function () {
|
||
|
expect(await this.erc721aBurnable.numberBurned(this.addr1.address)).to.equal(1);
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(0));
|
||
|
expect(await this.erc721aBurnable.numberBurned(this.addr1.address)).to.equal(2);
|
||
|
});
|
||
|
|
||
|
it('changes totalBurned', async function () {
|
||
|
const totalBurnedBefore = (await this.erc721aBurnable.totalBurned()).toNumber();
|
||
|
expect(totalBurnedBefore).to.equal(1);
|
||
|
|
||
|
for (let i = 0; i < offsetted(2); ++i) {
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
|
||
|
|
||
|
const totalBurnedNow = (await this.erc721aBurnable.totalBurned()).toNumber();
|
||
|
expect(totalBurnedNow).to.equal(totalBurnedBefore + (i + 1));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('changes exists', async function () {
|
||
|
expect(await this.erc721aBurnable.exists(this.burnedTokenId)).to.be.false;
|
||
|
expect(await this.erc721aBurnable.exists(this.notBurnedTokenId)).to.be.true;
|
||
|
});
|
||
|
|
||
|
it('cannot burn a non-existing token', async function () {
|
||
|
const query = this.erc721aBurnable.connect(this.addr1).burn(offsetted(this.numTestTokens));
|
||
|
await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
});
|
||
|
|
||
|
it('cannot burn a burned token', async function () {
|
||
|
const query = this.erc721aBurnable.connect(this.addr1).burn(this.burnedTokenId);
|
||
|
await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
});
|
||
|
|
||
|
it('cannot burn with wrong caller or spender', async function () {
|
||
|
const tokenIdToBurn = this.notBurnedTokenId;
|
||
|
|
||
|
// sanity check
|
||
|
await this.erc721aBurnable.connect(this.addr1).approve(ZERO_ADDRESS, tokenIdToBurn);
|
||
|
await this.erc721aBurnable.connect(this.addr1).setApprovalForAll(this.spender.address, false);
|
||
|
|
||
|
const query = this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
|
||
|
await expect(query).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
|
||
|
});
|
||
|
|
||
|
it('spender can burn with specific approved tokenId', async function () {
|
||
|
const tokenIdToBurn = this.notBurnedTokenId;
|
||
|
|
||
|
await this.erc721aBurnable.connect(this.addr1).approve(this.spender.address, tokenIdToBurn);
|
||
|
await this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
|
||
|
expect(await this.erc721aBurnable.exists(tokenIdToBurn)).to.be.false;
|
||
|
});
|
||
|
|
||
|
it('spender can burn with one-time approval', async function () {
|
||
|
const tokenIdToBurn = this.notBurnedTokenId;
|
||
|
|
||
|
await this.erc721aBurnable.connect(this.addr1).setApprovalForAll(this.spender.address, true);
|
||
|
await this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
|
||
|
expect(await this.erc721aBurnable.exists(tokenIdToBurn)).to.be.false;
|
||
|
});
|
||
|
|
||
|
it('cannot transfer a burned token', async function () {
|
||
|
const query = this.erc721aBurnable
|
||
|
.connect(this.addr1)
|
||
|
.transferFrom(this.addr1.address, this.addr2.address, this.burnedTokenId);
|
||
|
await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
});
|
||
|
|
||
|
it('does not affect _totalMinted', async function () {
|
||
|
const totalMintedBefore = await this.erc721aBurnable.totalMinted();
|
||
|
expect(totalMintedBefore).to.equal(this.numTestTokens);
|
||
|
for (let i = 0; i < 2; ++i) {
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
|
||
|
}
|
||
|
expect(await this.erc721aBurnable.totalMinted()).to.equal(totalMintedBefore);
|
||
|
});
|
||
|
|
||
|
it('adjusts owners balances', async function () {
|
||
|
expect(await this.erc721aBurnable.balanceOf(this.addr1.address)).to.be.equal(this.numTestTokens - 1);
|
||
|
});
|
||
|
|
||
|
it('startTimestamp updated correctly', async function () {
|
||
|
const tokenIdToBurn = this.burnedTokenId + 1;
|
||
|
const ownershipBefore = await this.erc721aBurnable.getOwnershipAt(tokenIdToBurn);
|
||
|
const timestampBefore = parseInt(ownershipBefore.startTimestamp);
|
||
|
const timestampToMine = (await getBlockTimestamp()) + 12345;
|
||
|
await mineBlockTimestamp(timestampToMine);
|
||
|
const timestampMined = await getBlockTimestamp();
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(tokenIdToBurn);
|
||
|
const ownershipAfter = await this.erc721aBurnable.getOwnershipAt(tokenIdToBurn);
|
||
|
const timestampAfter = parseInt(ownershipAfter.startTimestamp);
|
||
|
expect(timestampBefore).to.be.lt(timestampToMine);
|
||
|
expect(timestampAfter).to.be.gte(timestampToMine);
|
||
|
expect(timestampAfter).to.be.lt(timestampToMine + 10);
|
||
|
expect(timestampToMine).to.be.eq(timestampMined);
|
||
|
});
|
||
|
|
||
|
describe('ownerships correctly set', async function () {
|
||
|
it('with token before previously burnt token transferred and burned', async function () {
|
||
|
const tokenIdToBurn = this.burnedTokenId - 1;
|
||
|
await this.erc721aBurnable
|
||
|
.connect(this.addr1)
|
||
|
.transferFrom(this.addr1.address, this.addr2.address, tokenIdToBurn);
|
||
|
expect(await this.erc721aBurnable.ownerOf(tokenIdToBurn)).to.be.equal(this.addr2.address);
|
||
|
await this.erc721aBurnable.connect(this.addr2).burn(tokenIdToBurn);
|
||
|
for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
|
||
|
if (i == tokenIdToBurn || i == this.burnedTokenId) {
|
||
|
await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
} else {
|
||
|
expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('with token after previously burnt token transferred and burned', async function () {
|
||
|
const tokenIdToBurn = this.burnedTokenId + 1;
|
||
|
await this.erc721aBurnable
|
||
|
.connect(this.addr1)
|
||
|
.transferFrom(this.addr1.address, this.addr2.address, tokenIdToBurn);
|
||
|
expect(await this.erc721aBurnable.ownerOf(tokenIdToBurn)).to.be.equal(this.addr2.address);
|
||
|
await this.erc721aBurnable.connect(this.addr2).burn(tokenIdToBurn);
|
||
|
for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
|
||
|
if (i == tokenIdToBurn || i == this.burnedTokenId) {
|
||
|
await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
} else {
|
||
|
expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('with first token burned', async function () {
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(0));
|
||
|
for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
|
||
|
if (i == offsetted(0).toNumber() || i == this.burnedTokenId) {
|
||
|
await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
|
||
|
} else {
|
||
|
expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('with last token burned', async function () {
|
||
|
await expect(this.erc721aBurnable.ownerOf(offsetted(this.numTestTokens))).to.be.revertedWith(
|
||
|
'OwnerQueryForNonexistentToken'
|
||
|
);
|
||
|
await this.erc721aBurnable.connect(this.addr1).burn(offsetted(this.numTestTokens - 1));
|
||
|
await expect(this.erc721aBurnable.ownerOf(offsetted(this.numTestTokens - 1))).to.be.revertedWith(
|
||
|
'OwnerQueryForNonexistentToken'
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
describe('ERC721ABurnable', createTestSuite({ contract: 'ERC721ABurnableMock', constructorArgs: ['Azuki', 'AZUKI'] }));
|
||
|
|
||
|
describe(
|
||
|
'ERC721ABurnable override _startTokenId()',
|
||
|
createTestSuite({ contract: 'ERC721ABurnableStartTokenIdMock', constructorArgs: ['Azuki', 'AZUKI', 1] })
|
||
|
);
|