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.

311 lines
13 KiB
JavaScript

const { deployContract, offsettedIndex } = require('../helpers.js');
const { expect } = require('chai');
const { BigNumber } = require('ethers');
const { constants } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const createTestSuite = ({ contract, constructorArgs }) =>
function () {
let offsetted;
context(`${contract}`, function () {
beforeEach(async function () {
this.erc721aQueryable = await deployContract(contract, constructorArgs);
this.startTokenId = this.erc721aQueryable.startTokenId
? (await this.erc721aQueryable.startTokenId()).toNumber()
: 0;
offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
});
const expectExplicitOwnershipBurned = function (explicitOwnership, address) {
expect(explicitOwnership.burned).to.eql(true);
expect(explicitOwnership.addr).to.eql(address);
expect(explicitOwnership.startTimestamp).to.not.eql(BigNumber.from(0));
expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
};
const expectExplicitOwnershipNotExists = function (explicitOwnership) {
expect(explicitOwnership.burned).to.eql(false);
expect(explicitOwnership.addr).to.eql(ZERO_ADDRESS);
expect(explicitOwnership.startTimestamp).to.eql(BigNumber.from(0));
expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
};
const expectExplicitOwnershipExists = function (explicitOwnership, address) {
expect(explicitOwnership.burned).to.eql(false);
expect(explicitOwnership.addr).to.eql(address);
expect(explicitOwnership.startTimestamp).to.not.eql(BigNumber.from(0));
expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
};
context('with no minted tokens', async function () {
beforeEach(async function () {
const [owner, addr1] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
});
describe('tokensOfOwner', async function () {
it('returns empty array', async function () {
expect(await this.erc721aQueryable.tokensOfOwner(this.owner.address)).to.eql([]);
expect(await this.erc721aQueryable.tokensOfOwner(this.addr1.address)).to.eql([]);
});
});
describe('tokensOfOwnerIn', async function () {
it('returns empty array', async function () {
expect(await this.erc721aQueryable.tokensOfOwnerIn(this.owner.address, 0, 9)).to.eql([]);
expect(await this.erc721aQueryable.tokensOfOwnerIn(this.addr1.address, 0, 9)).to.eql([]);
});
});
describe('explicitOwnershipOf', async function () {
it('returns empty struct', async function () {
expectExplicitOwnershipNotExists(await this.erc721aQueryable.explicitOwnershipOf(0));
expectExplicitOwnershipNotExists(await this.erc721aQueryable.explicitOwnershipOf(1));
});
});
describe('explicitOwnershipsOf', async function () {
it('returns empty structs', async function () {
const tokenIds = [0, 1, 2, 3];
const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
for (let i = 0; i < explicitOwnerships.length; ++i) {
expectExplicitOwnershipNotExists(explicitOwnerships[i]);
}
});
});
});
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.addr1.expected = {
balance: 1,
tokens: [offsetted(0)],
};
this.addr2.expected = {
balance: 2,
tokens: offsetted(1, 2),
};
this.addr3.expected = {
balance: 3,
tokens: offsetted(3, 4, 5),
};
this.addr4.expected = {
balance: 0,
tokens: [],
};
this.owner.expected = {
balance: 3,
tokens: offsetted(6, 7, 8),
};
this.lastTokenId = offsetted(8);
this.currentIndex = this.lastTokenId.add(1);
this.mintOrder = [this.addr1, this.addr2, this.addr3, this.addr4, owner];
for (const minter of this.mintOrder) {
const balance = minter.expected.balance;
if (balance > 0) {
await this.erc721aQueryable['safeMint(address,uint256)'](minter.address, balance);
}
// sanity check
expect(await this.erc721aQueryable.balanceOf(minter.address)).to.equal(minter.expected.balance);
}
});
describe('tokensOfOwner', async function () {
it('initial', async function () {
for (const minter of this.mintOrder) {
const tokens = await this.erc721aQueryable.tokensOfOwner(minter.address);
expect(tokens).to.eql(minter.expected.tokens);
}
});
it('after a transfer', async function () {
// Break sequential order by transfering 7th token from owner to addr4
const tokenIdToTransfer = [offsetted(7)];
await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenIdToTransfer[0]);
// Load balances
const ownerTokens = await this.erc721aQueryable.tokensOfOwner(this.owner.address);
const addr4Tokens = await this.erc721aQueryable.tokensOfOwner(this.addr4.address);
// Verify the function can still read the correct token ids
expect(ownerTokens).to.eql(offsetted(6, 8));
expect(addr4Tokens).to.eql(tokenIdToTransfer);
});
it('after a burn', async function () {
// Burn tokens
const tokenIdToBurn = [offsetted(7)];
await this.erc721aQueryable.burn(tokenIdToBurn[0]);
// Load balances
const ownerTokens = await this.erc721aQueryable.tokensOfOwner(this.owner.address);
// Verify the function can still read the correct token ids
expect(ownerTokens).to.eql(offsetted(6, 8));
});
});
describe('tokensOfOwnerIn', async function () {
const expectCorrect = async function (addr, start, stop) {
if (BigNumber.from(start).gte(BigNumber.from(stop))) {
await expect(this.erc721aQueryable.tokensOfOwnerIn(addr, start, stop)).to.be.revertedWith(
'InvalidQueryRange'
);
} else {
const expectedTokens = (await this.erc721aQueryable.tokensOfOwner(addr)).filter(
(x) => BigNumber.from(start).lte(x) && BigNumber.from(stop).gt(x)
);
const tokens = await this.erc721aQueryable.tokensOfOwnerIn(addr, start, stop);
expect(tokens).to.eql(expectedTokens);
}
};
const subTests = function (description, beforeEachFunction) {
describe(description, async function () {
it('all token ids', async function () {
await beforeEachFunction.call(this);
await expectCorrect.call(this, this.owner.address, offsetted(0), this.currentIndex);
await expectCorrect.call(this, this.owner.address, offsetted(0), this.currentIndex.add(1));
});
it('partial token ids', async function () {
await beforeEachFunction.call(this);
const ownerTokens = this.owner.expected.tokens;
const start = ownerTokens[0];
const stop = ownerTokens[ownerTokens.length - 1] + 1;
for (let o = 1; o <= ownerTokens.length; ++o) {
// Start truncated.
await expectCorrect.call(this, this.owner.address, start + o, stop);
// End truncated.
await expectCorrect.call(this, this.owner.address, start, stop - o);
// Start and end truncated. This also tests for start + o >= stop - o.
await expectCorrect.call(this, this.owner.address, start + o, stop - o);
}
for (let l = 0; l < ownerTokens.length; ++l) {
for (let o = 0, n = parseInt(this.currentIndex) + 1; o <= n; ++o) {
// Sliding window.
await expectCorrect.call(this, this.owner.address, o, o + l);
}
}
});
});
};
subTests('initial', async function () {});
subTests('after a token tranfer', async function () {
await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, offsetted(7));
});
subTests('after a token burn', async function () {
await this.erc721aQueryable.burn(offsetted(7));
});
});
describe('explicitOwnershipOf', async function () {
it('token exists', async function () {
const tokenId = this.owner.expected.tokens[0];
const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
expectExplicitOwnershipExists(explicitOwnership, this.owner.address);
});
it('after a token burn', async function () {
const tokenId = this.owner.expected.tokens[0];
await this.erc721aQueryable.burn(tokenId);
const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
expectExplicitOwnershipBurned(explicitOwnership, this.owner.address);
});
it('after a token transfer', async function () {
const tokenId = this.owner.expected.tokens[0];
await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenId);
const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
expectExplicitOwnershipExists(explicitOwnership, this.addr4.address);
});
it('out of bounds', async function () {
const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(this.currentIndex);
expectExplicitOwnershipNotExists(explicitOwnership);
});
});
describe('explicitOwnershipsOf', async function () {
it('tokens exist', async function () {
const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
for (let i = 0; i < tokenIds.length; ++i) {
const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
expectExplicitOwnershipExists(explicitOwnerships[i], owner);
}
});
it('after a token burn', async function () {
const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
await this.erc721aQueryable.burn(tokenIds[0]);
const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
expectExplicitOwnershipBurned(explicitOwnerships[0], this.owner.address);
for (let i = 1; i < tokenIds.length; ++i) {
const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
expectExplicitOwnershipExists(explicitOwnerships[i], owner);
}
});
it('after a token transfer', async function () {
const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenIds[0]);
const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
expectExplicitOwnershipExists(explicitOwnerships[0], this.addr4.address);
for (let i = 1; i < tokenIds.length; ++i) {
const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
expectExplicitOwnershipExists(explicitOwnerships[i], owner);
}
});
it('out of bounds', async function () {
const tokenIds = [].concat([this.currentIndex], this.addr3.expected.tokens);
const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
expectExplicitOwnershipNotExists(explicitOwnerships[0]);
for (let i = 1; i < tokenIds.length; ++i) {
const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
expectExplicitOwnershipExists(explicitOwnerships[i], owner);
}
});
});
});
});
};
describe(
'ERC721AQueryable',
createTestSuite({
contract: 'ERC721AQueryableMock',
constructorArgs: ['Azuki', 'AZUKI'],
})
);
describe(
'ERC721AQueryable override _startTokenId()',
createTestSuite({
contract: 'ERC721AQueryableStartTokenIdMock',
constructorArgs: ['Azuki', 'AZUKI', 1],
})
);