DCIPs/assets/eip-6059/test/nestable.ts

1159 lines
44 KiB
TypeScript

import { expect } from 'chai';
import { ethers } from 'hardhat';
import { BigNumber, constants } from 'ethers';
import { NestableTokenMock } from '../typechain-types';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
function bn(x: number): BigNumber {
return BigNumber.from(x);
}
const ADDRESS_ZERO = constants.AddressZero;
async function parentChildFixture(): Promise<{
parent: NestableTokenMock;
child: NestableTokenMock;
}> {
const factory = await ethers.getContractFactory('NestableTokenMock');
const parent = await factory.deploy();
await parent.deployed();
const child = await factory.deploy();
await child.deployed();
return { parent, child };
}
describe('NestableToken', function () {
let parent: NestableTokenMock;
let child: NestableTokenMock;
let owner: SignerWithAddress;
let tokenOwner: SignerWithAddress;
let addrs: SignerWithAddress[];
beforeEach(async function () {
[owner, tokenOwner, ...addrs] = await ethers.getSigners();
({ parent, child } = await loadFixture(parentChildFixture));
});
describe('Minting', async function () {
it('cannot mint id 0', async function () {
const tokenId1 = 0;
await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError(
child,
'IdZeroForbidden',
);
});
it('cannot nest mint id 0', async function () {
const parentId = 1;
await child.mint(owner.address, parentId);
const childId1 = 0;
await expect(
child.nestMint(parent.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'IdZeroForbidden');
});
it('cannot mint already minted token', async function () {
const tokenId1 = 1;
await child.mint(owner.address, tokenId1);
await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError(
child,
'ERC721TokenAlreadyMinted',
);
});
it('cannot nest mint already minted token', async function () {
const parentId = 1;
const childId1 = 99;
await parent.mint(owner.address, parentId);
await child.nestMint(parent.address, childId1, parentId);
await expect(
child.nestMint(parent.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted');
});
it('cannot nest mint already minted token', async function () {
const parentId = 1;
const childId1 = 99;
await parent.mint(owner.address, parentId);
await child.nestMint(parent.address, childId1, parentId);
await expect(
child.nestMint(parent.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted');
});
it('can mint with no destination', async function () {
const tokenId1 = 1;
await child.mint(tokenOwner.address, tokenId1);
expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address);
expect(await child.directOwnerOf(tokenId1)).to.eql([tokenOwner.address, bn(0), false]);
});
it('has right owners', async function () {
const otherOwner = addrs[2];
const tokenId1 = 1;
await parent.mint(tokenOwner.address, tokenId1);
const tokenId2 = 2;
await parent.mint(otherOwner.address, tokenId2);
const tokenId3 = 3;
await parent.mint(otherOwner.address, tokenId3);
expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address);
expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address);
expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
expect(await parent.balanceOf(otherOwner.address)).to.equal(2);
await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
});
it('cannot mint to zero address', async function () {
await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError(
child,
'ERC721MintToTheZeroAddress',
);
});
it('cannot nest mint to a non-contract destination', async function () {
await expect(child.nestMint(tokenOwner.address, 1, 1)).to.be.revertedWithCustomError(
child,
'IsNotContract',
);
});
it('cannot nest mint to non nestable receiver', async function () {
const ERC721 = await ethers.getContractFactory('ERC721Mock');
const nonReceiver = await ERC721.deploy('Non receiver', 'NR');
await nonReceiver.deployed();
await expect(child.nestMint(nonReceiver.address, 1, 1)).to.be.revertedWithCustomError(
child,
'MintToNonNestableImplementer',
);
});
it('cannot nest mint to a non-existent token', async function () {
await expect(child.nestMint(parent.address, 1, 1)).to.be.revertedWithCustomError(
child,
'ERC721InvalidTokenId',
);
});
it('cannot nest mint to zero address', async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
await expect(child.nestMint(ADDRESS_ZERO, parentId, 1)).to.be.revertedWithCustomError(
child,
'IsNotContract',
);
});
it('can mint to contract and owners are ok', async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
// owner is the same adress
expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address);
expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
expect(await child.balanceOf(parent.address)).to.equal(1);
});
it('can mint to contract and direct owners are ok', async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
// Direct owner is an address for the parent
expect(await parent.directOwnerOf(parentId)).to.eql([tokenOwner.address, bn(0), false]);
// Direct owner is a contract for the child
expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]);
});
it("can mint to contract and parent's children are ok", async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
const children = await parent.childrenOf(parentId);
expect(children).to.eql([]);
const pendingChildren = await parent.pendingChildrenOf(parentId);
expect(pendingChildren).to.eql([[bn(childId1), child.address]]);
expect(await parent.pendingChildOf(parentId, 0)).to.eql([bn(childId1), child.address]);
});
it('cannot get child out of index', async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError(
parent,
'ChildIndexOutOfRange',
);
});
it('cannot get pending child out of index', async function () {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
await expect(parent.pendingChildOf(parentId, 0)).to.be.revertedWithCustomError(
parent,
'PendingChildIndexOutOfRange',
);
});
it('can mint multiple children', async function () {
const parentId = 1;
const childId1 = 99;
const childId2 = 100;
await parent.mint(tokenOwner.address, parentId);
await child.nestMint(parent.address, childId1, parentId);
await child.nestMint(parent.address, childId2, parentId);
expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address);
expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address);
expect(await child.balanceOf(parent.address)).to.equal(2);
const pendingChildren = await parent.pendingChildrenOf(parentId);
expect(pendingChildren).to.eql([
[bn(childId1), child.address],
[bn(childId2), child.address],
]);
});
it('can mint child into child', async function () {
const parentId = 1;
const childId1 = 99;
const granchildId = 999;
await parent.mint(tokenOwner.address, parentId);
await child.nestMint(parent.address, childId1, parentId);
await child.nestMint(child.address, granchildId, childId1);
// Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself
// and this is a little counterintuitive, but the root owner is the EOA.
expect(await child.balanceOf(parent.address)).to.equal(1);
expect(await child.balanceOf(child.address)).to.equal(1);
const pendingChildrenOfChunky10 = await parent.pendingChildrenOf(parentId);
const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1);
expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]);
expect(pendingChildrenOfMonkey1).to.eql([[bn(granchildId), child.address]]);
expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]);
expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address);
});
it('cannot have too many pending children', async () => {
const parentId = 1;
await parent.mint(tokenOwner.address, parentId);
// First 128 should be fine.
for (let i = 1; i <= 128; i++) {
await child.nestMint(parent.address, i, parentId);
}
await expect(child.nestMint(parent.address, 129, parentId)).to.be.revertedWithCustomError(
child,
'MaxPendingChildrenReached',
);
});
});
describe('Interface support', async function () {
it('can support IERC165', async function () {
expect(await parent.supportsInterface('0x01ffc9a7')).to.equal(true);
});
it('can support IERC721', async function () {
expect(await parent.supportsInterface('0x80ac58cd')).to.equal(true);
});
it('can support IERC6059', async function () {
expect(await parent.supportsInterface('0x42b0e56f')).to.equal(true);
});
it('cannot support other interfaceId', async function () {
expect(await parent.supportsInterface('0xffffffff')).to.equal(false);
});
});
describe('Adding child', async function () {
it('cannot add child from user address', async function () {
const tokenOwner1 = addrs[0];
const tokenOwner2 = addrs[1];
const parentId = 1;
await parent.mint(tokenOwner1.address, parentId);
const childId1 = 99;
await child.mint(tokenOwner2.address, childId1);
await expect(parent.addChild(parentId, childId1, '0x')).to.be.revertedWithCustomError(
parent,
'IsNotContract',
);
});
});
describe('Accept child', async function () {
let parentId: number;
let childId1: number;
beforeEach(async function () {
parentId = 1;
await parent.mint(tokenOwner.address, parentId);
childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
});
it('can accept child', async function () {
await expect(parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1))
.to.emit(parent, 'ChildAccepted')
.withArgs(parentId, 0, child.address, childId1);
await checkChildWasAccepted();
});
it('can accept child if approved', async function () {
const approved = addrs[1];
await parent.connect(tokenOwner).approve(approved.address, parentId);
await parent.connect(approved).acceptChild(parentId, 0, child.address, childId1);
await checkChildWasAccepted();
});
it('can accept child if approved for all', async function () {
const operator = addrs[2];
await parent.connect(tokenOwner).setApprovalForAll(operator.address, true);
await parent.connect(operator).acceptChild(parentId, 0, child.address, childId1);
await checkChildWasAccepted();
});
it('cannot accept not owned child', async function () {
const notOwner = addrs[3];
await expect(
parent.connect(notOwner).acceptChild(parentId, 0, child.address, childId1),
).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner');
});
it('cannot accept child if address or id do not match', async function () {
const otherAddress = addrs[1].address;
const otherChildId = 9999;
await expect(
parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, otherChildId),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
await expect(
parent.connect(tokenOwner).acceptChild(parentId, 0, otherAddress, childId1),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
});
it('cannot accept children for non existing index', async () => {
await expect(
parent.connect(tokenOwner).acceptChild(parentId, 1, child.address, childId1),
).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange');
});
async function checkChildWasAccepted() {
expect(await parent.pendingChildrenOf(parentId)).to.eql([]);
expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]);
}
});
describe('Rejecting children', async function () {
let parentId: number;
beforeEach(async function () {
parentId = 1;
await parent.mint(tokenOwner.address, parentId);
await child.nestMint(parent.address, 99, parentId);
});
it('can reject all pending children', async function () {
// Mint a couple of more children
await child.nestMint(parent.address, 100, parentId);
await child.nestMint(parent.address, 101, parentId);
await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3))
.to.emit(parent, 'AllChildrenRejected')
.withArgs(parentId);
await checkNoChildrenNorPending(parentId);
// They are still on the child
expect(await child.balanceOf(parent.address)).to.equal(3);
});
it('cannot reject all pending children if there are more than expected', async function () {
// Mint a couple of more children
await child.nestMint(parent.address, 100, parentId);
await child.nestMint(parent.address, 101, parentId);
await expect(
parent.connect(tokenOwner).rejectAllChildren(parentId, 1),
).to.be.revertedWithCustomError(parent, 'UnexpectedNumberOfChildren');
});
it('can reject all pending children if approved', async function () {
// Mint a couple of more children
await child.nestMint(parent.address, 100, parentId);
await child.nestMint(parent.address, 101, parentId);
const rejecter = addrs[1];
await parent.connect(tokenOwner).approve(rejecter.address, parentId);
await parent.connect(rejecter).rejectAllChildren(parentId, 3);
await checkNoChildrenNorPending(parentId);
});
it('can reject all pending children if approved for all', async function () {
// Mint a couple of more children
await child.nestMint(parent.address, 100, parentId);
await child.nestMint(parent.address, 101, parentId);
const operator = addrs[2];
await parent.connect(tokenOwner).setApprovalForAll(operator.address, true);
await parent.connect(operator).rejectAllChildren(parentId, 3);
await checkNoChildrenNorPending(parentId);
});
it('cannot reject all pending children for not owned pending child', async function () {
const notOwner = addrs[3];
await expect(
parent.connect(notOwner).rejectAllChildren(parentId, 2),
).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner');
});
});
describe('Burning', async function () {
let parentId: number;
beforeEach(async function () {
parentId = 1;
await parent.mint(tokenOwner.address, parentId);
});
it('can burn token', async function () {
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
await parent.connect(tokenOwner)['burn(uint256)'](parentId);
await checkBurntParent();
});
it('can burn token if approved', async function () {
const approved = addrs[1];
await parent.connect(tokenOwner).approve(approved.address, parentId);
await parent.connect(approved)['burn(uint256)'](parentId);
await checkBurntParent();
});
it('can burn token if approved for all', async function () {
const operator = addrs[2];
await parent.connect(tokenOwner).setApprovalForAll(operator.address, true);
await parent.connect(operator)['burn(uint256)'](parentId);
await checkBurntParent();
});
it('can recursively burn nested token', async function () {
const childId1 = 99;
const granchildId = 999;
await child.nestMint(parent.address, childId1, parentId);
await child.nestMint(child.address, granchildId, childId1);
await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1);
await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, granchildId);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
expect(await child.balanceOf(parent.address)).to.equal(1);
expect(await child.balanceOf(child.address)).to.equal(1);
expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]);
expect(await child.childrenOf(childId1)).to.eql([[bn(granchildId), child.address]]);
expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]);
// Sets recursive burns to 2
await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(0);
expect(await child.balanceOf(parent.address)).to.equal(0);
expect(await child.balanceOf(child.address)).to.equal(0);
await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
await expect(parent.directOwnerOf(parentId)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError(
child,
'ERC721InvalidTokenId',
);
await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError(
child,
'ERC721InvalidTokenId',
);
await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
await expect(parent.directOwnerOf(granchildId)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
});
it('can recursively burn nested token with the right number of recursive burns', async function () {
// Parent
// -> Child1
// -> GrandChild1
// -> GrandChild2
// -> GreatGrandChild1
// -> Child2
// Total tree 5 (4 recursive burns)
const childId1 = 99;
const childId2 = 100;
const grandChild1 = 999;
const grandChild2 = 1000;
const greatGrandChild1 = 9999;
await child.nestMint(parent.address, childId1, parentId);
await child.nestMint(parent.address, childId2, parentId);
await child.nestMint(child.address, grandChild1, childId1);
await child.nestMint(child.address, grandChild2, childId1);
await child.nestMint(child.address, greatGrandChild1, grandChild2);
await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1);
await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId2);
await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild1);
await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild2);
await child.connect(tokenOwner).acceptChild(grandChild2, 0, child.address, greatGrandChild1);
// 0 is not enough
await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 0))
.to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached')
.withArgs(child.address, childId1);
// 1 is not enough
await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 1))
.to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached')
.withArgs(child.address, grandChild1);
// 2 is not enough
await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2))
.to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached')
.withArgs(child.address, grandChild2);
// 3 is not enough
await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 3))
.to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached')
.withArgs(child.address, greatGrandChild1);
// 4 is not enough
await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 4))
.to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached')
.withArgs(child.address, childId2);
// 5 is just enough
await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 5);
});
async function checkBurntParent() {
expect(await parent.balanceOf(addrs[1].address)).to.equal(0);
await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError(
parent,
'ERC721InvalidTokenId',
);
}
});
describe('Transferring Active Children', async function () {
let parentId: number;
let childId1: number;
beforeEach(async function () {
parentId = 1;
childId1 = 99;
await parent.mint(tokenOwner.address, parentId);
await child.nestMint(parent.address, childId1, parentId);
await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1);
});
it('can transfer child with to as root owner', async function () {
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, false, '0x'),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, false);
await checkChildMovedToRootOwner();
});
it('can transfer child to another address', async function () {
const toOwnerAddress = addrs[2].address;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, false, '0x'),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, false);
await checkChildMovedToRootOwner(toOwnerAddress);
});
it('can transfer child to another NFT', async function () {
const newOwnerAddress = addrs[2].address;
const newParentId = 2;
await parent.mint(newOwnerAddress, newParentId);
await expect(
parent
.connect(tokenOwner)
.transferChild(
parentId,
parent.address,
newParentId,
0,
child.address,
childId1,
false,
'0x',
),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, false);
expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress);
expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]);
expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]);
});
it('cannot transfer child out of index', async function () {
const toOwnerAddress = addrs[2].address;
const badIndex = 2;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, false, '0x'),
).to.be.revertedWithCustomError(parent, 'ChildIndexOutOfRange');
});
it('cannot transfer child if address or id do not match', async function () {
const otherAddress = addrs[1].address;
const otherChildId = 9999;
const toOwnerAddress = addrs[2].address;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, false, '0x'),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, false, '0x'),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
});
it('can transfer child if approved', async function () {
const transferer = addrs[1];
const toOwner = tokenOwner.address;
await parent.connect(tokenOwner).approve(transferer.address, parentId);
await parent
.connect(transferer)
.transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x');
await checkChildMovedToRootOwner();
});
it('can transfer child if approved for all', async function () {
const operator = addrs[2];
const toOwner = tokenOwner.address;
await parent.connect(tokenOwner).setApprovalForAll(operator.address, true);
await parent
.connect(operator)
.transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x');
await checkChildMovedToRootOwner();
});
it('can transfer child with grandchild and children are ok', async function () {
const toOwner = tokenOwner.address;
const grandchildId = 999;
await child.nestMint(child.address, grandchildId, childId1);
// Transfer child from parent.
await parent
.connect(tokenOwner)
.transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x');
// New owner of child
expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address);
expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]);
// Grandchild is still owned by child
expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address);
expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]);
});
it('cannot transfer child if not child root owner', async function () {
const toOwner = tokenOwner.address;
const notOwner = addrs[3];
await expect(
parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'),
).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner');
});
it('cannot transfer child from not existing parent', async function () {
const badChildId = 99;
const toOwner = tokenOwner.address;
await expect(
parent
.connect(tokenOwner)
.transferChild(badChildId, toOwner, 0, 0, child.address, childId1, false, '0x'),
).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId');
});
async function checkChildMovedToRootOwner(rootOwnerAddress?: string) {
if (rootOwnerAddress === undefined) {
rootOwnerAddress = tokenOwner.address;
}
expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress);
expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]);
// Transferring updates balances downstream
expect(await child.balanceOf(rootOwnerAddress)).to.equal(1);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
}
});
describe('Transferring Pending Children', async function () {
let parentId: number;
let childId1: number;
beforeEach(async function () {
parentId = 1;
await parent.mint(tokenOwner.address, parentId);
childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
});
it('can transfer child with to as root owner', async function () {
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, true, '0x'),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, true);
await checkChildMovedToRootOwner();
});
it('can transfer child to another address', async function () {
const toOwnerAddress = addrs[2].address;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, true, '0x'),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, true);
await checkChildMovedToRootOwner(toOwnerAddress);
});
it('can transfer child to another NFT', async function () {
const newOwnerAddress = addrs[2].address;
const newParentId = 2;
await parent.mint(newOwnerAddress, newParentId);
await expect(
parent
.connect(tokenOwner)
.transferChild(
parentId,
parent.address,
newParentId,
0,
child.address,
childId1,
true,
'0x',
),
)
.to.emit(parent, 'ChildTransferred')
.withArgs(parentId, 0, child.address, childId1, true);
expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress);
expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]);
expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]);
});
it('cannot transfer child out of index', async function () {
const toOwnerAddress = addrs[2].address;
const badIndex = 2;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, true, '0x'),
).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange');
});
it('cannot transfer child if address or id do not match', async function () {
const otherAddress = addrs[1].address;
const otherChildId = 9999;
const toOwnerAddress = addrs[2].address;
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, true, '0x'),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
await expect(
parent
.connect(tokenOwner)
.transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, true, '0x'),
).to.be.revertedWithCustomError(parent, 'UnexpectedChildId');
});
it('can transfer child if approved', async function () {
const transferer = addrs[1];
const toOwner = tokenOwner.address;
await parent.connect(tokenOwner).approve(transferer.address, parentId);
await parent
.connect(transferer)
.transferChild(parentId, toOwner, 0, 0,child.address, childId1, true, '0x');
await checkChildMovedToRootOwner();
});
it('can transfer child if approved for all', async function () {
const operator = addrs[2];
const toOwner = tokenOwner.address;
await parent.connect(tokenOwner).setApprovalForAll(operator.address, true);
await parent
.connect(operator)
.transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x');
await checkChildMovedToRootOwner();
});
it('can transfer child with grandchild and children are ok', async function () {
const toOwner = tokenOwner.address;
const grandchildId = 999;
await child.nestMint(child.address, grandchildId, childId1);
// Transfer child from parent.
await parent
.connect(tokenOwner)
.transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x');
// New owner of child
expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address);
expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]);
// Grandchild is still owned by child
expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address);
expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]);
});
it('cannot transfer child if not child root owner', async function () {
const toOwner = tokenOwner.address;
const notOwner = addrs[3];
await expect(
parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'),
).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner');
});
it('cannot transfer child from not existing parent', async function () {
const badChildId = 99;
const toOwner = tokenOwner.address;
await expect(
parent
.connect(tokenOwner)
.transferChild(badChildId, toOwner, 0, 0, child.address, childId1, true, '0x'),
).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId');
});
async function checkChildMovedToRootOwner(rootOwnerAddress?: string) {
if (rootOwnerAddress === undefined) {
rootOwnerAddress = tokenOwner.address;
}
expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress);
expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]);
// Transferring updates balances downstream
expect(await child.balanceOf(rootOwnerAddress)).to.equal(1);
expect(await parent.balanceOf(tokenOwner.address)).to.equal(1);
}
});
describe('Transfer', async function () {
it('can transfer token', async function () {
const firstOwner = addrs[1];
const newOwner = addrs[2];
const tokenId1 = 1;
await parent.mint(firstOwner.address, tokenId1);
await parent.connect(firstOwner).transfer(newOwner.address, tokenId1);
// Balances and ownership are updated
expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address);
expect(await parent.balanceOf(firstOwner.address)).to.equal(0);
expect(await parent.balanceOf(newOwner.address)).to.equal(1);
});
it('cannot transfer not owned token', async function () {
const firstOwner = addrs[1];
const newOwner = addrs[2];
const tokenId1 = 1;
await parent.mint(firstOwner.address, tokenId1);
await expect(
parent.connect(newOwner).transfer(newOwner.address, tokenId1),
).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner');
});
it('cannot transfer to address zero', async function () {
const firstOwner = addrs[1];
const tokenId1 = 1;
await parent.mint(firstOwner.address, tokenId1);
await expect(
parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1),
).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress');
});
it('can transfer token from approved address (not owner)', async function () {
const firstOwner = addrs[1];
const approved = addrs[2];
const newOwner = addrs[3];
const tokenId1 = 1;
await parent.mint(firstOwner.address, tokenId1);
await parent.connect(firstOwner).approve(approved.address, tokenId1);
await parent.connect(firstOwner).transfer(newOwner.address, tokenId1);
expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address);
});
it('can transfer not nested token with child to address and owners/children are ok', async function () {
const firstOwner = addrs[1];
const newOwner = addrs[2];
const parentId = 1;
await parent.mint(firstOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
await parent.connect(firstOwner).transfer(newOwner.address, parentId);
// Balances and ownership are updated
expect(await parent.balanceOf(firstOwner.address)).to.equal(0);
expect(await parent.balanceOf(newOwner.address)).to.equal(1);
expect(await parent.ownerOf(parentId)).to.eql(newOwner.address);
expect(await parent.directOwnerOf(parentId)).to.eql([newOwner.address, bn(0), false]);
// New owner of child
expect(await child.ownerOf(childId1)).to.eql(newOwner.address);
expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]);
// Parent still has its children
expect(await parent.pendingChildrenOf(parentId)).to.eql([[bn(childId1), child.address]]);
});
it('cannot directly transfer nested child', async function () {
const firstOwner = addrs[1];
const newOwner = addrs[2];
const parentId = 1;
await parent.mint(firstOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
await expect(
child.connect(firstOwner).transfer(newOwner.address, childId1),
).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner');
});
it('can transfer parent token to token with same owner, family tree is ok', async function () {
const firstOwner = addrs[1];
const grandParentId = 999;
await parent.mint(firstOwner.address, grandParentId);
const parentId = 1;
await parent.mint(firstOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
// Check balances
expect(await parent.balanceOf(firstOwner.address)).to.equal(2);
expect(await child.balanceOf(parent.address)).to.equal(1);
// Transfers token parentId to (parent.address, token grandParentId)
await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId);
// Balances unchanged since root owner is the same
expect(await parent.balanceOf(firstOwner.address)).to.equal(1);
expect(await child.balanceOf(parent.address)).to.equal(1);
expect(await parent.balanceOf(parent.address)).to.equal(1);
// Parent is still owner of child
let expected = [bn(childId1), child.address];
checkAcceptedAndPendingChildren(parent, parentId, [expected], []);
// Ownership: firstOwner > newGrandparent > parent > child
expected = [bn(parentId), parent.address];
checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]);
});
it('can transfer parent token to token with different owner, family tree is ok', async function () {
const firstOwner = addrs[1];
const otherOwner = addrs[2];
const grandParentId = 999;
await parent.mint(otherOwner.address, grandParentId);
const parentId = 1;
await parent.mint(firstOwner.address, parentId);
const childId1 = 99;
await child.nestMint(parent.address, childId1, parentId);
// Check balances
expect(await parent.balanceOf(otherOwner.address)).to.equal(1);
expect(await parent.balanceOf(firstOwner.address)).to.equal(1);
expect(await child.balanceOf(parent.address)).to.equal(1);
// firstOwner calls parent to transfer parent token parent
await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId);
// Balances update
expect(await parent.balanceOf(firstOwner.address)).to.equal(0);
expect(await parent.balanceOf(parent.address)).to.equal(1);
expect(await parent.balanceOf(otherOwner.address)).to.equal(1);
expect(await child.balanceOf(parent.address)).to.equal(1);
// Parent is still owner of child
let expected = [bn(childId1), child.address];
checkAcceptedAndPendingChildren(parent, parentId, [expected], []);
// Ownership: firstOwner > newGrandparent > parent > child
expected = [bn(parentId), parent.address];
checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]);
});
});
describe('Nest Transfer', async function () {
let firstOwner: SignerWithAddress;
let parentId: number;
let childId1: number;
beforeEach(async function () {
firstOwner = addrs[1];
parentId = 1;
childId1 = 99;
await parent.mint(firstOwner.address, parentId);
await child.mint(firstOwner.address, childId1);
});
it('cannot nest tranfer from non immediate owner (owner of parent)', async function () {
const otherParentId = 2;
await parent.mint(firstOwner.address, otherParentId);
// We send it to the parent first
await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId);
// We can no longer nest transfer it, even if we are the root owner:
await expect(
child.connect(firstOwner).nestTransfer(parent.address, childId1, otherParentId),
).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner');
});
it('cannot nest tranfer to same NFT', async function () {
// We can no longer nest transfer it, even if we are the root owner:
await expect(
child.connect(firstOwner).nestTransfer(child.address, childId1, childId1),
).to.be.revertedWithCustomError(child, 'NestableTransferToSelf');
});
it('cannot nest tranfer a descendant same NFT', async function () {
// We can no longer nest transfer it, even if we are the root owner:
await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId);
const grandChildId = 999;
await child.nestMint(child.address, grandChildId, childId1);
// Ownership is now parent->child->granChild
// Cannot send parent to grandChild
await expect(
parent.connect(firstOwner).nestTransfer(child.address, parentId, grandChildId),
).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant');
// Cannot send parent to child
await expect(
parent.connect(firstOwner).nestTransfer(child.address, parentId, childId1),
).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant');
});
it('cannot nest tranfer if ancestors tree is too deep', async function () {
let lastId = childId1;
for (let i = 101; i <= 200; i++) {
await child.nestMint(child.address, i, lastId);
lastId = i;
}
// Ownership is now parent->child->child->child->child...->lastChild
// Cannot send parent to lastChild
await expect(
parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId),
).to.be.revertedWithCustomError(child, 'NestableTooDeep');
});
it('cannot nest tranfer if not owner', async function () {
const notOwner = addrs[3];
await expect(
child.connect(notOwner).nestTransfer(parent.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner');
});
it('cannot nest tranfer to address 0', async function () {
await expect(
child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId),
).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress');
});
it('cannot nest tranfer to a non contract', async function () {
const newOwner = addrs[2];
await expect(
child.connect(firstOwner).nestTransfer(newOwner.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'IsNotContract');
});
it('cannot nest tranfer to contract if it does implement IERC6059', async function () {
const ERC721 = await ethers.getContractFactory('ERC721Mock');
const nonNestable = await ERC721.deploy('Non receiver', 'NR');
await nonNestable.deployed();
await expect(
child.connect(firstOwner).nestTransfer(nonNestable.address, childId1, parentId),
).to.be.revertedWithCustomError(child, 'NestableTransferToNonNestableImplementer');
});
it('can nest tranfer to IERC6059 contract', async function () {
await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId);
expect(await child.ownerOf(childId1)).to.eql(firstOwner.address);
expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]);
});
it('cannot nest tranfer to non existing parent token', async function () {
const notExistingParentId = 9999;
await expect(
child.connect(firstOwner).nestTransfer(parent.address, childId1, notExistingParentId),
).to.be.revertedWithCustomError(parent, 'ERC721InvalidTokenId');
});
});
async function checkNoChildrenNorPending(parentId: number): Promise<void> {
expect(await parent.pendingChildrenOf(parentId)).to.eql([]);
expect(await parent.childrenOf(parentId)).to.eql([]);
}
async function checkAcceptedAndPendingChildren(
contract: NestableTokenMock,
tokenId1: number,
expectedAccepted: any[],
expectedPending: any[],
) {
const accepted = await contract.childrenOf(tokenId1);
expect(accepted).to.eql(expectedAccepted);
const pending = await contract.pendingChildrenOf(tokenId1);
expect(pending).to.eql(expectedPending);
}
});