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

1494 lines
48 KiB
TypeScript
Raw Normal View History

import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { BigNumber, constants } from "ethers";
import { ethers } from "hardhat";
import { EquippableTokenMock } from "../typechain-types";
function bn(x: number): BigNumber {
return BigNumber.from(x);
}
const ADDRESS_ZERO = constants.AddressZero;
async function parentChildFixture(): Promise<{
parent: EquippableTokenMock;
child: EquippableTokenMock;
}> {
const factory = await ethers.getContractFactory("EquippableTokenMock");
const parent = await factory.deploy();
await parent.deployed();
const child = await factory.deploy();
await child.deployed();
return { parent, child };
}
describe("NestableToken", function () {
let parent: EquippableTokenMock;
let child: EquippableTokenMock;
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 INestable", 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 INestable", 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 INestable 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: EquippableTokenMock,
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);
}
});