UNPKG

@vechain/vebetterdao-contracts

Version:

Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.

280 lines (279 loc) 19.4 kB
import { describe, it } from "mocha"; import { ZERO_ADDRESS, catchRevert, getOrDeployContractInstances } from "./helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; import { createLocalConfig } from "@repo/config/contracts/envs/local"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { deployProxy, upgradeProxy } from "../scripts/helpers"; describe("X2EarnCreator - @shard11", () => { describe("Contract parameters", () => { it("Should have correct parameters set on deployment", async () => { const { x2EarnCreator, owner } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await x2EarnCreator.name()).to.equal("X2EarnCreator"); expect(await x2EarnCreator.symbol()).to.equal("X2C"); expect(await x2EarnCreator.hasRole(await x2EarnCreator.DEFAULT_ADMIN_ROLE(), await owner.getAddress())).to.equal(true); }); it("Should support ERC 165 interface", async () => { const { x2EarnCreator } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await x2EarnCreator.supportsInterface("0x01ffc9a7")).to.equal(true); // ERC165 }); }); describe("Pausing", () => { it("Only PAUSER_ROLE or DEFAULT_ADMIN_ROLE should be able to pause and unpause the contract", async () => { const { x2EarnCreator, otherAccount, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); const pauser = otherAccounts[0]; await x2EarnCreator.grantRole(await x2EarnCreator.PAUSER_ROLE(), pauser.address); expect(await x2EarnCreator.hasRole(await x2EarnCreator.PAUSER_ROLE(), otherAccount.address)).to.eql(false); expect(await x2EarnCreator.hasRole(await x2EarnCreator.PAUSER_ROLE(), pauser.address)).to.eql(true); expect(await x2EarnCreator.hasRole(await x2EarnCreator.DEFAULT_ADMIN_ROLE(), owner.address)).to.eql(true); await catchRevert(x2EarnCreator.connect(otherAccount).pause()); // DEFAULT_ADMIN_ROLE await expect(x2EarnCreator.connect(owner).pause()).to.emit(x2EarnCreator, "Paused"); expect(await x2EarnCreator.paused()).to.equal(true); await expect(x2EarnCreator.connect(owner).unpause()).to.emit(x2EarnCreator, "Unpaused"); expect(await x2EarnCreator.paused()).to.equal(false); await catchRevert(x2EarnCreator.connect(otherAccount).unpause()); // PAUSER_ROLE await x2EarnCreator.connect(pauser).pause(); expect(await x2EarnCreator.paused()).to.equal(true); await x2EarnCreator.connect(pauser).unpause(); expect(await x2EarnCreator.paused()).to.equal(false); }); it("Should not allow tokens to be minted when paused", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).pause(); await catchRevert(x2EarnCreator.connect(owner).safeMint(otherAccount.address)); }); }); describe("Base URI", () => { it("Should be able to set the base URI", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); // normal user await expect(x2EarnCreator.connect(otherAccount).setBaseURI("ipfs://BASE_URI")).to.be.reverted; // default admin expect(await x2EarnCreator.hasRole(await x2EarnCreator.DEFAULT_ADMIN_ROLE(), owner.address)).to.equal(true); await expect(x2EarnCreator.connect(owner).setBaseURI("ipfs://BASE_URI2")).to.not.be.reverted; expect(await x2EarnCreator.baseURI()).to.equal("ipfs://BASE_URI2"); }); }); describe("Upgrading", () => { it("Admin should be able to upgrade the contract", async function () { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("X2EarnCreator"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await x2EarnCreator.getAddress()); await x2EarnCreator.grantRole(await x2EarnCreator.UPGRADER_ROLE(), owner.address); const UPGRADER_ROLE = await x2EarnCreator.UPGRADER_ROLE(); expect(await x2EarnCreator.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); // If non-admin tries to upgrade, it should fail await expect(x2EarnCreator.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.be .reverted; await expect(x2EarnCreator.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await x2EarnCreator.getAddress()); expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); it("should return the correct version", async function () { const { x2EarnCreator } = await getOrDeployContractInstances({ forceDeploy: true, }); const version = await x2EarnCreator.version(); expect(version).to.equal("2"); }); it("Should not be able to initialize the contract after it has already been initialized", async function () { const config = createLocalConfig(); const { x2EarnCreator, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await expect(x2EarnCreator.initialize(config.CREATOR_NFT_URI, owner.address)).to.be.reverted; // already initialized }); it("V1 to V2 upgrade should preserve storage and enable selfMint", async function () { const config = createLocalConfig(); const [owner, user] = await ethers.getSigners(); const v1 = (await deployProxy("X2EarnCreatorV1", [config.CREATOR_NFT_URI, owner.address])); await v1.connect(owner).safeMint(user.address); expect(await v1.balanceOf(user.address)).to.equal(1); expect(await v1.version()).to.equal("1"); const v2 = (await upgradeProxy("X2EarnCreatorV1", "X2EarnCreator", await v1.getAddress(), [true], { version: 2, })); expect(await v2.version()).to.equal("2"); expect(await v2.balanceOf(user.address)).to.equal(1); expect(await v2.baseURI()).to.equal(config.CREATOR_NFT_URI); expect(await v2.selfMintEnabled()).to.equal(true); const [, , newUser] = await ethers.getSigners(); await expect(v2.connect(newUser).selfMint()).to.emit(v2, "Transfer"); expect(await v2.balanceOf(newUser.address)).to.equal(1); }); }); describe("Minting", () => { it("Should mint a token to the caller", async () => { const { x2EarnCreator, owner, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); await expect(x2EarnCreator.connect(owner).safeMint(otherAccounts[14].address)).to.emit(x2EarnCreator, "Transfer"); expect(await x2EarnCreator.ownerOf(10)).to.equal(otherAccounts[14].address); expect(await x2EarnCreator.tokenURI(10)).to.equal("ipfs://bafybeie2onvzl3xsod5becuswpdmi63gtq7wgjqhqjecehytt7wdeg4py4/metadata/1.json"); }); it("Should not allow minting when paused", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).pause(); await catchRevert(x2EarnCreator.connect(owner).safeMint(otherAccount.address)); }); it("Should not allow minting to the zero address", async () => { const { x2EarnCreator, owner } = await getOrDeployContractInstances({ forceDeploy: true }); await catchRevert(x2EarnCreator.connect(owner).safeMint(ZERO_ADDRESS)); }); it("Only user with MINTER_ROLE or DEFAULT_ADMIN_ROLE should be able to mint tokens", async () => { const { x2EarnCreator, owner, otherAccount, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, }); const minter = otherAccounts[0]; await x2EarnCreator.grantRole(await x2EarnCreator.MINTER_ROLE(), minter.address); expect(await x2EarnCreator.hasRole(await x2EarnCreator.MINTER_ROLE(), otherAccount.address)).to.eql(false); expect(await x2EarnCreator.hasRole(await x2EarnCreator.MINTER_ROLE(), minter.address)).to.eql(true); expect(await x2EarnCreator.hasRole(await x2EarnCreator.DEFAULT_ADMIN_ROLE(), owner.address)).to.eql(true); await catchRevert(x2EarnCreator.connect(otherAccount).safeMint(otherAccount.address)); // DEFAULT_ADMIN_ROLE await expect(x2EarnCreator.connect(owner).safeMint(otherAccount.address)).to.not.be.reverted; // MINTER_ROLE await expect(x2EarnCreator.connect(minter).safeMint(otherAccounts[10].address)).to.not.be.reverted; }); it("Should not be able to get the token URI if token not minted", async () => { const { x2EarnCreator } = await getOrDeployContractInstances({ forceDeploy: true }); // x2earnApp v5: Token [2...5] are reserved for the creator NFTs await expect(x2EarnCreator.tokenURI(10)).to.be.reverted; }); it("Should not be able to mint token to a user that already has a token", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await expect(x2EarnCreator.connect(owner).safeMint(otherAccount.address)).to.be.revertedWithCustomError(x2EarnCreator, "AlreadyOwnsNFT"); }); }); describe("Transferring", () => { it("Should not be able to tranfet a token using transferFrom", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await expect(x2EarnCreator.connect(owner).transferFrom(owner.address, otherAccount.address, 0)).to.be.revertedWithCustomError(x2EarnCreator, "TransfersDisabled"); }); it("Should not be able to transfer a token using safeTransferFrom without data", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); // Testing the first overload without data parameter await expect(x2EarnCreator["safeTransferFrom(address,address,uint256)"](owner.address, otherAccount.address, 1)).to.be.revertedWithCustomError(x2EarnCreator, "TransfersDisabled"); }); it("Should not be able to transfer a token using safeTransferFrom with data", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); // Testing the second overload with the data parameter await expect(x2EarnCreator["safeTransferFrom(address,address,uint256,bytes)"](owner.address, otherAccount.address, 1, "0x")).to.be.revertedWithCustomError(x2EarnCreator, "TransfersDisabled"); }); it("Should not be able to approve a token for transfer", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await expect(x2EarnCreator.connect(owner).approve(otherAccount.address, 0)).to.be.revertedWithCustomError(x2EarnCreator, "TransfersDisabled"); }); it("Should not be able to setApprovalForAll", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await expect(x2EarnCreator.connect(owner).setApprovalForAll(otherAccount.address, true)).to.be.revertedWithCustomError(x2EarnCreator, "TransfersDisabled"); }); }); describe("Burning", () => { it("Only user with BURNER_ROLE or DEFAULT_ADMIN_ROLE should be able to burn NFT", async () => { const { x2EarnCreator, owner, otherAccount, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, }); const burner = otherAccounts[0]; await x2EarnCreator.grantRole(await x2EarnCreator.BURNER_ROLE(), burner.address); expect(await x2EarnCreator.hasRole(await x2EarnCreator.BURNER_ROLE(), otherAccount.address)).to.eql(false); expect(await x2EarnCreator.hasRole(await x2EarnCreator.BURNER_ROLE(), burner.address)).to.eql(true); expect(await x2EarnCreator.hasRole(await x2EarnCreator.DEFAULT_ADMIN_ROLE(), owner.address)).to.eql(true); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); // normal user await expect(x2EarnCreator.connect(otherAccount).burn(1)).to.be.reverted; // default admin await expect(x2EarnCreator.connect(owner).burn(1)).to.emit(x2EarnCreator, "Transfer"); // burner await expect(x2EarnCreator.connect(burner).burn(2)).to.emit(x2EarnCreator, "Transfer"); }); it("Should not be able to burn a token when paused", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await x2EarnCreator.connect(owner).pause(); await catchRevert(x2EarnCreator.connect(owner).burn(0)); }); it("Should not be able to burn a token that does not exist", async () => { const { x2EarnCreator, owner } = await getOrDeployContractInstances({ forceDeploy: true }); await catchRevert(x2EarnCreator.connect(owner).burn(10)); }); it("Should not be able to get the token URI after burning", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); await x2EarnCreator.connect(owner).burn(1); await expect(x2EarnCreator.tokenURI(1)).to.be.reverted; }); }); describe("Token Info", () => { it("Should return the correct token URI", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); expect(await x2EarnCreator.tokenURI(1)).to.equal("ipfs://bafybeie2onvzl3xsod5becuswpdmi63gtq7wgjqhqjecehytt7wdeg4py4/metadata/1.json"); }); it("Should return the correct token owner", async () => { const { x2EarnCreator, owner, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); let creator10 = otherAccounts[10]; await x2EarnCreator.connect(owner).safeMint(creator10.address); expect(await x2EarnCreator.ownerOf(1)).to.equal(owner.address); expect(await x2EarnCreator.ownerOf(10)).to.equal(creator10.address); }); it("Should return the correct token balance", async () => { const { x2EarnCreator, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccount.address); expect(await x2EarnCreator.balanceOf(owner.address)).to.equal(1); expect(await x2EarnCreator.balanceOf(otherAccount.address)).to.equal(1); }); it("Should return the correct token total supply", async () => { const { x2EarnCreator, owner, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); // Already minted 9 tokens to otherAccounts[0..7] and owner -> see deploy.ts await x2EarnCreator.connect(owner).safeMint(otherAccounts[10].address); await x2EarnCreator.connect(owner).safeMint(otherAccounts[11].address); await x2EarnCreator.connect(owner).burn(1); expect(await x2EarnCreator.totalSupply()).to.equal(10); }); it("Should return the correct token owner by index", async () => { const { x2EarnCreator, owner, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnCreator.connect(owner).safeMint(otherAccounts[10].address); await x2EarnCreator.connect(owner).safeMint(otherAccounts[11].address); expect(await x2EarnCreator.tokenOfOwnerByIndex(owner.address, 0)).to.equal(1); expect(await x2EarnCreator.tokenOfOwnerByIndex(otherAccounts[10].address, 0)).to.equal(10); expect(await x2EarnCreator.tokenOfOwnerByIndex(otherAccounts[11].address, 0)).to.equal(11); }); }); describe("Self Minting", () => { it("Should allow self-minting after initializeV2", async () => { const { x2EarnCreator, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const user = otherAccounts[15]; expect(await x2EarnCreator.selfMintEnabled()).to.equal(true); await expect(x2EarnCreator.connect(user).selfMint()).to.emit(x2EarnCreator, "Transfer"); expect(await x2EarnCreator.balanceOf(user.address)).to.equal(1); }); it("Should revert selfMint when user already owns NFT", async () => { const { x2EarnCreator, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const user = otherAccounts[15]; await x2EarnCreator.connect(user).selfMint(); await expect(x2EarnCreator.connect(user).selfMint()).to.be.revertedWithCustomError(x2EarnCreator, "AlreadyOwnsNFT"); }); it("Should revert selfMint when paused", async () => { const { x2EarnCreator, owner, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const user = otherAccounts[15]; await x2EarnCreator.connect(owner).pause(); await catchRevert(x2EarnCreator.connect(user).selfMint()); }); }); });