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.

767 lines
33 KiB
JavaScript

const { deployContract, getBlockTimestamp, mineBlockTimestamp, offsettedIndex } = require('./helpers.js');
const { expect } = require('chai');
const { BigNumber } = require('ethers');
const { constants } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const RECEIVER_MAGIC_VALUE = '0x150b7a02';
const GAS_MAGIC_VALUE = 20000;
const createTestSuite = ({ contract, constructorArgs }) =>
function () {
let offsetted;
context(`${contract}`, function () {
beforeEach(async function () {
this.erc721a = await deployContract(contract, constructorArgs);
this.receiver = await deployContract('ERC721ReceiverMock', [RECEIVER_MAGIC_VALUE, this.erc721a.address]);
this.startTokenId = this.erc721a.startTokenId ? (await this.erc721a.startTokenId()).toNumber() : 0;
offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
});
describe('EIP-165 support', async function () {
it('supports ERC165', async function () {
expect(await this.erc721a.supportsInterface('0x01ffc9a7')).to.eq(true);
});
it('supports IERC721', async function () {
expect(await this.erc721a.supportsInterface('0x80ac58cd')).to.eq(true);
});
it('supports ERC721Metadata', async function () {
expect(await this.erc721a.supportsInterface('0x5b5e139f')).to.eq(true);
});
it('does not support ERC721Enumerable', async function () {
expect(await this.erc721a.supportsInterface('0x780e9d63')).to.eq(false);
});
it('does not support random interface', async function () {
expect(await this.erc721a.supportsInterface('0x00000042')).to.eq(false);
});
});
describe('ERC721Metadata support', async function () {
it('name', async function () {
expect(await this.erc721a.name()).to.eq(constructorArgs[0]);
});
it('symbol', async function () {
expect(await this.erc721a.symbol()).to.eq(constructorArgs[1]);
});
describe('baseURI', async function () {
it('sends an empty URI by default', async function () {
expect(await this.erc721a.baseURI()).to.eq('');
});
});
});
context('with no minted tokens', async function () {
it('has 0 totalSupply', async function () {
const supply = await this.erc721a.totalSupply();
expect(supply).to.equal(0);
});
it('has 0 totalMinted', async function () {
const totalMinted = await this.erc721a.totalMinted();
expect(totalMinted).to.equal(0);
});
it('has 0 totalBurned', async function () {
const totalBurned = await this.erc721a.totalBurned();
expect(totalBurned).to.equal(0);
});
it('_nextTokenId must be equal to _startTokenId', async function () {
const nextTokenId = await this.erc721a.nextTokenId();
expect(nextTokenId).to.equal(offsetted(0));
});
});
context('with minted tokens', async function () {
beforeEach(async function () {
const [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
this.addr2 = addr2;
this.addr3 = addr3;
this.addr4 = addr4;
this.expectedMintCount = 6;
this.addr1.expected = {
mintCount: 1,
tokens: [offsetted(0)],
};
this.addr2.expected = {
mintCount: 2,
tokens: offsetted(1, 2),
};
this.addr3.expected = {
mintCount: 3,
tokens: offsetted(3, 4, 5),
};
await this.erc721a['safeMint(address,uint256)'](addr1.address, this.addr1.expected.mintCount);
await this.erc721a['safeMint(address,uint256)'](addr2.address, this.addr2.expected.mintCount);
await this.erc721a['safeMint(address,uint256)'](addr3.address, this.addr3.expected.mintCount);
});
describe('tokenURI (ERC721Metadata)', async function () {
describe('tokenURI', async function () {
it('sends an empty uri by default', async function () {
expect(await this.erc721a.tokenURI(offsetted(0))).to.eq('');
});
it('reverts when tokenId does not exist', async function () {
await expect(this.erc721a.tokenURI(offsetted(this.expectedMintCount))).to.be.revertedWith(
'URIQueryForNonexistentToken'
);
});
});
});
describe('exists', async function () {
it('verifies valid tokens', async function () {
for (let tokenId = offsetted(0); tokenId < offsetted(this.expectedMintCount); tokenId++) {
const exists = await this.erc721a.exists(tokenId);
expect(exists).to.be.true;
}
});
it('verifies invalid tokens', async function () {
expect(await this.erc721a.exists(offsetted(this.expectedMintCount))).to.be.false;
});
});
describe('balanceOf', async function () {
it('returns the amount for a given address', async function () {
expect(await this.erc721a.balanceOf(this.owner.address)).to.equal('0');
expect(await this.erc721a.balanceOf(this.addr1.address)).to.equal(this.addr1.expected.mintCount);
expect(await this.erc721a.balanceOf(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
expect(await this.erc721a.balanceOf(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
});
it('returns correct amount with transferred tokens', async function () {
const tokenIdToTransfer = this.addr2.expected.tokens[0];
await this.erc721a
.connect(this.addr2)
.transferFrom(this.addr2.address, this.addr3.address, tokenIdToTransfer);
// sanity check
expect(await this.erc721a.ownerOf(tokenIdToTransfer)).to.equal(this.addr3.address);
expect(await this.erc721a.balanceOf(this.addr2.address)).to.equal(this.addr2.expected.mintCount - 1);
expect(await this.erc721a.balanceOf(this.addr3.address)).to.equal(this.addr3.expected.mintCount + 1);
});
it('throws an exception for the 0 address', async function () {
await expect(this.erc721a.balanceOf(ZERO_ADDRESS)).to.be.revertedWith('BalanceQueryForZeroAddress');
});
});
describe('_numberMinted', async function () {
it('returns the amount for a given address', async function () {
expect(await this.erc721a.numberMinted(this.owner.address)).to.equal('0');
expect(await this.erc721a.numberMinted(this.addr1.address)).to.equal(this.addr1.expected.mintCount);
expect(await this.erc721a.numberMinted(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
expect(await this.erc721a.numberMinted(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
});
it('returns the same amount with transferred token', async function () {
const tokenIdToTransfer = this.addr2.expected.tokens[0];
await this.erc721a
.connect(this.addr2)
.transferFrom(this.addr2.address, this.addr3.address, tokenIdToTransfer);
// sanity check
expect(await this.erc721a.ownerOf(tokenIdToTransfer)).to.equal(this.addr3.address);
expect(await this.erc721a.numberMinted(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
expect(await this.erc721a.numberMinted(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
});
});
context('_totalMinted', async function () {
it('has correct totalMinted', async function () {
const totalMinted = await this.erc721a.totalMinted();
expect(totalMinted).to.equal(this.expectedMintCount);
});
});
context('_nextTokenId', async function () {
it('has correct nextTokenId', async function () {
const nextTokenId = await this.erc721a.nextTokenId();
expect(nextTokenId).to.equal(offsetted(this.expectedMintCount));
});
});
describe('aux', async function () {
it('get and set works correctly', async function () {
const uint64Max = BigNumber.from(2).pow(64).sub(1).toString();
expect(await this.erc721a.getAux(this.owner.address)).to.equal('0');
await this.erc721a.setAux(this.owner.address, uint64Max);
expect(await this.erc721a.getAux(this.owner.address)).to.equal(uint64Max);
expect(await this.erc721a.getAux(this.addr1.address)).to.equal('0');
await this.erc721a.setAux(this.addr1.address, '1');
expect(await this.erc721a.getAux(this.addr1.address)).to.equal('1');
await this.erc721a.setAux(this.addr3.address, '5');
expect(await this.erc721a.getAux(this.addr3.address)).to.equal('5');
expect(await this.erc721a.getAux(this.addr1.address)).to.equal('1');
});
});
describe('ownerOf', async function () {
it('returns the right owner', async function () {
for (const minter of [this.addr1, this.addr2, this.addr3]) {
for (const tokenId of minter.expected.tokens) {
expect(await this.erc721a.ownerOf(tokenId)).to.equal(minter.address);
}
}
});
it('reverts for an invalid token', async function () {
await expect(this.erc721a.ownerOf(10)).to.be.revertedWith('OwnerQueryForNonexistentToken');
if (this.startTokenId > 0) {
await expect(this.erc721a.ownerOf(0)).to.be.revertedWith('OwnerQueryForNonexistentToken');
}
});
});
describe('approve', async function () {
beforeEach(function () {
this.tokenId = this.addr1.expected.tokens[0];
this.tokenId2 = this.addr2.expected.tokens[0];
});
it('sets approval for the target address', async function () {
await this.erc721a.connect(this.addr1).approve(this.addr2.address, this.tokenId);
const approval = await this.erc721a.getApproved(this.tokenId);
expect(approval).to.equal(this.addr2.address);
});
it('set approval for the target address on behalf of the owner', async function () {
await this.erc721a.connect(this.addr1).setApprovalForAll(this.addr2.address, true);
await this.erc721a.connect(this.addr2).approve(this.addr3.address, this.tokenId);
const approval = await this.erc721a.getApproved(this.tokenId);
expect(approval).to.equal(this.addr3.address);
});
it('rejects an unapproved caller', async function () {
await expect(this.erc721a.approve(this.addr2.address, this.tokenId)).to.be.revertedWith(
'ApprovalCallerNotOwnerNorApproved'
);
});
it('does not get approved for invalid tokens', async function () {
await expect(this.erc721a.getApproved(10)).to.be.revertedWith('ApprovalQueryForNonexistentToken');
});
it('approval allows token transfer', async function () {
await expect(
this.erc721a.connect(this.addr3).transferFrom(this.addr1.address, this.addr3.address, this.tokenId)
).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
await this.erc721a.connect(this.addr1).approve(this.addr3.address, this.tokenId);
await this.erc721a.connect(this.addr3).transferFrom(this.addr1.address, this.addr3.address, this.tokenId);
await expect(
this.erc721a.connect(this.addr1).transferFrom(this.addr3.address, this.addr1.address, this.tokenId)
).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
});
it('token owner can approve self as operator', async function () {
expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
await expect(this.erc721a.connect(this.addr1).approve(this.addr1.address, this.tokenId)
).to.not.be.reverted;
expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
});
it('self-approval is cleared on token transfer', async function () {
await this.erc721a.connect(this.addr1).approve(this.addr1.address, this.tokenId);
expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
await this.erc721a.connect(this.addr1).transferFrom(this.addr1.address, this.addr2.address, this.tokenId);
expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
});
it('direct approve works', async function () {
expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
await this.erc721a.connect(this.addr2).directApprove(this.addr1.address, this.tokenId);
expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
});
});
describe('setApprovalForAll', async function () {
it('sets approval for all properly', async function () {
const approvalTx = await this.erc721a.setApprovalForAll(this.addr1.address, true);
await expect(approvalTx)
.to.emit(this.erc721a, 'ApprovalForAll')
.withArgs(this.owner.address, this.addr1.address, true);
expect(await this.erc721a.isApprovedForAll(this.owner.address, this.addr1.address)).to.be.true;
});
it('caller can approve all with self as operator', async function () {
expect(
await this.erc721a.connect(this.addr1).isApprovedForAll(this.addr1.address, this.addr1.address)
).to.be.false;
await expect(
this.erc721a.connect(this.addr1).setApprovalForAll(this.addr1.address, true)
).to.not.be.reverted;
expect(
await this.erc721a.connect(this.addr1).isApprovedForAll(this.addr1.address, this.addr1.address)
).to.be.true;
});
});
context('test transfer functionality', function () {
const testSuccessfulTransfer = function (transferFn, transferToContract = true) {
beforeEach(async function () {
const sender = this.addr2;
this.tokenId = this.addr2.expected.tokens[0];
this.from = sender.address;
this.to = transferToContract ? this.receiver : this.addr4;
await this.erc721a.connect(sender).approve(this.to.address, this.tokenId);
const ownershipBefore = await this.erc721a.getOwnershipAt(this.tokenId);
this.timestampBefore = parseInt(ownershipBefore.startTimestamp);
this.timestampToMine = (await getBlockTimestamp()) + 12345;
await mineBlockTimestamp(this.timestampToMine);
this.timestampMined = await getBlockTimestamp();
// prettier-ignore
this.transferTx = await this.erc721a
.connect(sender)[transferFn](this.from, this.to.address, this.tokenId);
const ownershipAfter = await this.erc721a.getOwnershipAt(this.tokenId);
this.timestampAfter = parseInt(ownershipAfter.startTimestamp);
});
it('transfers the ownership of the given token ID to the given address', async function () {
expect(await this.erc721a.ownerOf(this.tokenId)).to.be.equal(this.to.address);
});
it('emits a Transfer event', async function () {
await expect(this.transferTx)
.to.emit(this.erc721a, 'Transfer')
.withArgs(this.from, this.to.address, this.tokenId);
});
it('clears the approval for the token ID', async function () {
expect(await this.erc721a.getApproved(this.tokenId)).to.be.equal(ZERO_ADDRESS);
});
it('adjusts owners balances', async function () {
expect(await this.erc721a.balanceOf(this.from)).to.be.equal(1);
});
it('startTimestamp updated correctly', async function () {
expect(this.timestampBefore).to.be.lt(this.timestampToMine);
expect(this.timestampAfter).to.be.gte(this.timestampToMine);
expect(this.timestampAfter).to.be.lt(this.timestampToMine + 10);
expect(this.timestampToMine).to.be.eq(this.timestampMined);
});
};
const testUnsuccessfulTransfer = function (transferFn) {
beforeEach(function () {
this.tokenId = this.addr2.expected.tokens[0];
this.sender = this.addr1;
});
it('rejects unapproved transfer', async function () {
await expect(
this.erc721a.connect(this.sender)[transferFn](this.addr2.address, this.sender.address, this.tokenId)
).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
});
it('rejects transfer from incorrect owner', async function () {
await this.erc721a.connect(this.addr2).setApprovalForAll(this.sender.address, true);
await expect(
this.erc721a.connect(this.sender)[transferFn](this.addr3.address, this.sender.address, this.tokenId)
).to.be.revertedWith('TransferFromIncorrectOwner');
});
it('rejects transfer to zero address', async function () {
await this.erc721a.connect(this.addr2).setApprovalForAll(this.sender.address, true);
await expect(
this.erc721a.connect(this.sender)[transferFn](this.addr2.address, ZERO_ADDRESS, this.tokenId)
).to.be.revertedWith('TransferToZeroAddress');
});
};
context('successful transfers', function () {
context('transferFrom', function () {
describe('to contract', function () {
testSuccessfulTransfer('transferFrom');
});
describe('to EOA', function () {
testSuccessfulTransfer('transferFrom', false);
});
});
context('safeTransferFrom', function () {
describe('to contract', function () {
testSuccessfulTransfer('safeTransferFrom(address,address,uint256)');
it('validates ERC721Received', async function () {
await expect(this.transferTx)
.to.emit(this.receiver, 'Received')
.withArgs(this.addr2.address, this.addr2.address, this.tokenId, '0x', GAS_MAGIC_VALUE);
});
});
describe('to EOA', function () {
testSuccessfulTransfer('safeTransferFrom(address,address,uint256)', false);
});
});
});
context('unsuccessful transfers', function () {
describe('transferFrom', function () {
testUnsuccessfulTransfer('transferFrom');
});
describe('safeTransferFrom', function () {
testUnsuccessfulTransfer('safeTransferFrom(address,address,uint256)');
it('reverts for non-receivers', async function () {
const nonReceiver = this.erc721a;
// prettier-ignore
await expect(
this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256)'](
this.addr1.address,
nonReceiver.address,
offsetted(0)
)
).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
});
it('reverts when the receiver reverted', async function () {
// prettier-ignore
await expect(
this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256,bytes)'](
this.addr1.address,
this.receiver.address,
offsetted(0),
'0x01'
)
).to.be.revertedWith('reverted in the receiver contract!');
});
it('reverts if the receiver returns the wrong value', async function () {
// prettier-ignore
await expect(
this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256,bytes)'](
this.addr1.address,
this.receiver.address,
offsetted(0),
'0x02'
)
).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
});
});
});
});
describe('_burn', async function () {
beforeEach(function () {
this.tokenIdToBurn = offsetted(0);
});
it('can burn if approvalCheck is false', async function () {
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
await this.erc721a.connect(this.addr2)['burn(uint256,bool)'](this.tokenIdToBurn, false);
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
});
it('revert if approvalCheck is true', async function () {
await expect(
this.erc721a.connect(this.addr2)['burn(uint256,bool)'](this.tokenIdToBurn, true)
).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
});
it('can burn without approvalCheck parameter', async function () {
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
await this.erc721a.connect(this.addr2)['burn(uint256)'](this.tokenIdToBurn);
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
});
it('cannot burn a token owned by another if not approved', async function () {
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
await this.erc721a.connect(this.addr2)['burn(uint256)'](this.tokenIdToBurn);
expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
});
});
describe('_initializeOwnershipAt', async function () {
it('successfuly sets ownership of empty slot', async function () {
const lastTokenId = this.addr3.expected.tokens[2];
const ownership1 = await this.erc721a.getOwnershipAt(lastTokenId);
expect(ownership1[0]).to.equal(ZERO_ADDRESS);
await this.erc721a.initializeOwnershipAt(lastTokenId);
const ownership2 = await this.erc721a.getOwnershipAt(lastTokenId);
expect(ownership2[0]).to.equal(this.addr3.address);
});
it("doesn't set ownership if it's already setted", async function () {
const lastTokenId = this.addr3.expected.tokens[2];
expect(await this.erc721a.ownerOf(lastTokenId)).to.be.equal(this.addr3.address);
const tx1 = await this.erc721a.initializeOwnershipAt(lastTokenId);
expect(await this.erc721a.ownerOf(lastTokenId)).to.be.equal(this.addr3.address);
const tx2 = await this.erc721a.initializeOwnershipAt(lastTokenId);
// We assume the 2nd initialization doesn't set again due to less gas used.
const receipt1 = await tx1.wait();
const receipt2 = await tx2.wait();
expect(receipt2.gasUsed.toNumber()).to.be.lessThan(receipt1.gasUsed.toNumber());
});
});
});
context('test mint functionality', function () {
beforeEach(async function () {
const [owner, addr1] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
});
const testSuccessfulMint = function (safe, quantity, mintForContract = true) {
beforeEach(async function () {
this.minter = mintForContract ? this.receiver : this.addr1;
const mintFn = safe ? 'safeMint(address,uint256)' : 'mint(address,uint256)';
this.balanceBefore = (await this.erc721a.balanceOf(this.minter.address)).toNumber();
this.timestampToMine = (await getBlockTimestamp()) + 12345;
await mineBlockTimestamp(this.timestampToMine);
this.timestampMined = await getBlockTimestamp();
this.mintTx = await this.erc721a[mintFn](this.minter.address, quantity);
});
it('changes ownership', async function () {
for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
expect(await this.erc721a.ownerOf(tokenId)).to.equal(this.minter.address);
}
});
it('emits a Transfer event', async function () {
for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
await expect(this.mintTx)
.to.emit(this.erc721a, 'Transfer')
.withArgs(ZERO_ADDRESS, this.minter.address, tokenId);
}
});
it('adjusts owners balances', async function () {
expect(await this.erc721a.balanceOf(this.minter.address)).to.be.equal(this.balanceBefore + quantity);
});
it('adjusts OwnershipAt and OwnershipOf', async function () {
const ownership = await this.erc721a.getOwnershipAt(offsetted(0));
expect(ownership.startTimestamp).to.be.gte(this.timestampToMine);
expect(ownership.startTimestamp).to.be.lt(this.timestampToMine + 10);
expect(ownership.burned).to.be.false;
for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
const ownership = await this.erc721a.getOwnershipOf(tokenId);
expect(ownership.addr).to.equal(this.minter.address);
expect(ownership.startTimestamp).to.be.gte(this.timestampToMine);
expect(ownership.startTimestamp).to.be.lt(this.timestampToMine + 10);
expect(ownership.burned).to.be.false;
}
expect(this.timestampToMine).to.be.eq(this.timestampMined);
});
if (safe && mintForContract) {
it('validates ERC721Received', async function () {
for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
await expect(this.mintTx)
.to.emit(this.minter, 'Received')
.withArgs(this.owner.address, ZERO_ADDRESS, tokenId, '0x', GAS_MAGIC_VALUE);
}
});
}
};
const testUnsuccessfulMint = function (safe) {
beforeEach(async function () {
this.mintFn = safe ? 'safeMint(address,uint256)' : 'mint(address,uint256)';
});
it('rejects mints to the zero address', async function () {
await expect(this.erc721a[this.mintFn](ZERO_ADDRESS, 1)).to.be.revertedWith('MintToZeroAddress');
});
it('requires quantity to be greater than 0', async function () {
await expect(this.erc721a[this.mintFn](this.owner.address, 0)).to.be.revertedWith('MintZeroQuantity');
});
};
context('successful mints', function () {
context('mint', function () {
context('for contract', function () {
describe('single token', function () {
testSuccessfulMint(false, 1);
});
describe('multiple tokens', function () {
testSuccessfulMint(false, 5);
});
it('does not revert for non-receivers', async function () {
const nonReceiver = this.erc721a;
await this.erc721a.mint(nonReceiver.address, 1);
expect(await this.erc721a.ownerOf(offsetted(0))).to.equal(nonReceiver.address);
});
});
context('for EOA', function () {
describe('single token', function () {
testSuccessfulMint(false, 1, false);
});
describe('multiple tokens', function () {
testSuccessfulMint(false, 5, false);
});
});
});
context('safeMint', function () {
context('for contract', function () {
describe('single token', function () {
testSuccessfulMint(true, 1);
});
describe('multiple tokens', function () {
testSuccessfulMint(true, 5);
});
it('validates ERC721Received with custom _data', async function () {
const customData = ethers.utils.formatBytes32String('custom data');
const tx = await this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, customData);
await expect(tx)
.to.emit(this.receiver, 'Received')
.withArgs(this.owner.address, ZERO_ADDRESS, offsetted(0), customData, GAS_MAGIC_VALUE);
});
});
context('for EOA', function () {
describe('single token', function () {
testSuccessfulMint(true, 1, false);
});
describe('multiple tokens', function () {
testSuccessfulMint(true, 5, false);
});
});
});
});
context('unsuccessful mints', function () {
context('mint', function () {
testUnsuccessfulMint(false);
});
context('safeMint', function () {
testUnsuccessfulMint(true);
it('reverts for non-receivers', async function () {
const nonReceiver = this.erc721a;
await expect(this.erc721a['safeMint(address,uint256)'](nonReceiver.address, 1)).to.be.revertedWith(
'TransferToNonERC721ReceiverImplementer'
);
});
it('reverts when the receiver reverted', async function () {
await expect(
this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x01')
).to.be.revertedWith('reverted in the receiver contract!');
});
it('reverts if the receiver returns the wrong value', async function () {
await expect(
this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x02')
).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
});
it('reverts with reentrant call', async function () {
await expect(
this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x03')
).to.be.reverted;
});
});
});
});
context('_toString', async function () {
it('returns correct value', async function () {
expect(await this.erc721a['toString(uint256)']('0')).to.eq('0');
expect(await this.erc721a['toString(uint256)']('1')).to.eq('1');
expect(await this.erc721a['toString(uint256)']('2')).to.eq('2');
const uint256Max = BigNumber.from(2).pow(256).sub(1).toString();
expect(await this.erc721a['toString(uint256)'](uint256Max)).to.eq(uint256Max);
});
});
});
};
describe('ERC721A', createTestSuite({ contract: 'ERC721AMock', constructorArgs: ['Azuki', 'AZUKI'] }));
describe(
'ERC721A override _startTokenId()',
createTestSuite({ contract: 'ERC721AStartTokenIdMock', constructorArgs: ['Azuki', 'AZUKI', 1] })
);
describe('ERC721A with ERC2309', async function () {
beforeEach(async function () {
const [owner, addr1] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
let args;
args = ['Azuki', 'AZUKI', this.owner.address, 1, true];
this.erc721aMint1 = await deployContract('ERC721AWithERC2309Mock', args);
args = ['Azuki', 'AZUKI', this.owner.address, 10, true];
this.erc721aMint10 = await deployContract('ERC721AWithERC2309Mock', args);
});
it('emits a ConsecutiveTransfer event for single mint', async function () {
expect(this.erc721aMint1.deployTransaction)
.to.emit(this.erc721aMint1, 'ConsecutiveTransfer')
.withArgs(0, 0, ZERO_ADDRESS, this.owner.address);
});
it('emits a ConsecutiveTransfer event for a batch mint', async function () {
expect(this.erc721aMint10.deployTransaction)
.to.emit(this.erc721aMint10, 'ConsecutiveTransfer')
.withArgs(0, 9, ZERO_ADDRESS, this.owner.address);
});
it('requires quantity to be below mint limit', async function () {
let args;
const mintLimit = 5000;
args = ['Azuki', 'AZUKI', this.owner.address, mintLimit, true];
await deployContract('ERC721AWithERC2309Mock', args);
args = ['Azuki', 'AZUKI', this.owner.address, mintLimit + 1, true];
await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintERC2309QuantityExceedsLimit');
})
it('rejects mints to the zero address', async function () {
let args = ['Azuki', 'AZUKI', ZERO_ADDRESS, 1, true];
await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintToZeroAddress');
});
it('requires quantity to be greater than 0', async function () {
let args = ['Azuki', 'AZUKI', this.owner.address, 0, true];
await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintZeroQuantity');
});
});