UNPKG

@vechain/vebetterdao-contracts

Version:

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

734 lines 160 kB
import { time } from "@nomicfoundation/hardhat-network-helpers"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { createLocalConfig } from "@repo/config/contracts/envs/local"; import { expect } from "chai"; import { ethers } from "hardhat"; import { beforeEach, describe, it } from "mocha"; import { deployProxy, upgradeProxy } from "../scripts/helpers"; import { bootstrapAndStartEmissions, bootstrapEmissions, catchRevert, createProposal, getEventName, getOrDeployContractInstances, getProposalIdFromTx, getVot3Tokens, mintLegacyNode, NFT_NAME, NFT_SYMBOL, participateInAllocationVoting, payDeposit, startNewAllocationRound, upgradeNFTtoLevel, waitForCurrentRoundToEnd, waitForProposalToBeActive, ZERO_ADDRESS, } from "./helpers"; import { createTestConfig } from "./helpers/config"; import { createNodeHolder, endorseApp } from "./helpers/xnodes"; describe("Galaxy Member - @shard3a", () => { describe("Contract parameters", () => { it("Should have correct parameters set on deployment", async () => { const { galaxyMember, owner } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.name()).to.equal("GalaxyMember"); expect(await galaxyMember.symbol()).to.equal("GM"); expect(await galaxyMember.hasRole(await galaxyMember.DEFAULT_ADMIN_ROLE(), await owner.getAddress())).to.equal(true); expect(await galaxyMember.hasRole(await galaxyMember.PAUSER_ROLE(), await owner.getAddress())).to.equal(true); expect(await galaxyMember.MAX_LEVEL()).to.equal(1); }); it("Admin should be able to set x-allocation voting contract address", async () => { const { galaxyMember, owner, xAllocationVoting, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await galaxyMember.hasRole(await galaxyMember.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.equal(true); const tx = await galaxyMember.connect(owner).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress()); const receipt = await tx.wait(); const name = getEventName(receipt, galaxyMember); expect(name).to.equal("XAllocationsGovernorAddressUpdated"); expect(await galaxyMember.xAllocationsGovernor()).to.equal(await xAllocationVoting.getAddress()); expect(await galaxyMember.hasRole(await galaxyMember.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.equal(false); await expect(galaxyMember.connect(otherAccount).setXAllocationsGovernorAddress(otherAccount.address)).to.be .reverted; // Only admin should be able to set x-allocation voting contract address await expect(galaxyMember.connect(owner).setXAllocationsGovernorAddress(ZERO_ADDRESS)).to.be.reverted; // Cannot set x-allocation voting contract address to zero address }); it("Admin should be able to set B3TR Governor contract address", async () => { const { galaxyMember, owner, xAllocationVoting, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await galaxyMember.hasRole(await galaxyMember.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.equal(true); const tx = await galaxyMember.connect(owner).setB3trGovernorAddress(await xAllocationVoting.getAddress()); const receipt = await tx.wait(); const name = getEventName(receipt, galaxyMember); expect(name).to.equal("B3trGovernorAddressUpdated"); expect(await galaxyMember.b3trGovernor()).to.equal(await xAllocationVoting.getAddress()); expect(await galaxyMember.hasRole(await galaxyMember.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.equal(false); await expect(galaxyMember.connect(otherAccount).setB3trGovernorAddress(await otherAccount.getAddress())).to.be .reverted; // Only admin should be able to set B3TR Governor contract address await expect(galaxyMember.connect(owner).setB3trGovernorAddress(ZERO_ADDRESS)).to.be.reverted; }); it("Only admin should be able to set B3TR Governor contract address", async () => { const { galaxyMember, otherAccount, xAllocationVoting } = await getOrDeployContractInstances({ forceDeploy: true, }); const initialAddress = await galaxyMember.b3trGovernor(); await catchRevert(galaxyMember.connect(otherAccount).setB3trGovernorAddress(await xAllocationVoting.getAddress())); expect(await galaxyMember.b3trGovernor()).to.equal(initialAddress); }); it("Only admin should be able to set x-allocation voting contract address", async () => { const { galaxyMember, otherAccount, xAllocationVoting } = await getOrDeployContractInstances({ forceDeploy: true, }); const initialAddress = await galaxyMember.xAllocationsGovernor(); await catchRevert(galaxyMember.connect(otherAccount).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress())); expect(await galaxyMember.xAllocationsGovernor()).to.equal(initialAddress); }); it("Should have base URI set correctly", async () => { const config = createLocalConfig(); const { galaxyMember, owner } = await getOrDeployContractInstances({ forceDeploy: true, config }); expect(await galaxyMember.baseURI()).to.equal(config.GM_NFT_BASE_URI); const tx = await galaxyMember.connect(owner).setBaseURI("https://newbaseuri.com/"); const receipt = await tx.wait(); const name = getEventName(receipt, galaxyMember); expect(name).to.equal("BaseURIUpdated"); }); it("Only pauser role should be able to pause and unpause the contract", async () => { const { galaxyMember, otherAccount, owner } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.hasRole(await galaxyMember.PAUSER_ROLE(), otherAccount.address)).to.eql(false); expect(await galaxyMember.hasRole(await galaxyMember.PAUSER_ROLE(), owner.address)).to.eql(true); await catchRevert(galaxyMember.connect(otherAccount).pause()); await galaxyMember.connect(owner).pause(); expect(await galaxyMember.paused()).to.equal(true); await galaxyMember.connect(owner).unpause(); expect(await galaxyMember.paused()).to.equal(false); await catchRevert(galaxyMember.connect(otherAccount).unpause()); }); it("Should have b3tr required to upgrade set on deployment", async () => { const { galaxyMember } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.getB3TRtoUpgradeToLevel(2)).to.equal(10000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(3)).to.equal(25000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(4)).to.equal(50000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(5)).to.equal(100000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(6)).to.equal(250000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(7)).to.equal(500000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(8)).to.equal(2500000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(9)).to.equal(5000000000000000000000000n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(10)).to.equal(25000000000000000000000000n); }); it("Should be able to update b3tr required to upgrade if admin", async () => { const { galaxyMember, owner } = await getOrDeployContractInstances({ forceDeploy: false }); const tx = await galaxyMember .connect(owner) .setB3TRtoUpgradeToLevel([ 10000000000000000000001n, 25000000000000000000001n, 50000000000000000000001n, 100000000000000000000001n, 250000000000000000000001n, 500000000000000000000001n, 2500000000000000000000001n, 5000000000000000000000001n, 25000000000000000000000001n, ]); const receipt = await tx.wait(); const name = getEventName(receipt, galaxyMember); expect(name).to.equal("B3TRtoUpgradeToLevelUpdated"); expect(await galaxyMember.getB3TRtoUpgradeToLevel(2)).to.equal(10000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(3)).to.equal(25000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(4)).to.equal(50000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(5)).to.equal(100000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(6)).to.equal(250000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(7)).to.equal(500000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(8)).to.equal(2500000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(9)).to.equal(5000000000000000000000001n); expect(await galaxyMember.getB3TRtoUpgradeToLevel(10)).to.equal(25000000000000000000000001n); }); it("Admin should be able to set new base uri", async () => { const { galaxyMember, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); const newBaseURI = "https://newbaseuri.com/"; await galaxyMember.connect(owner).setBaseURI(newBaseURI); expect(await galaxyMember.baseURI()).to.equal(newBaseURI); await expect(galaxyMember.connect(otherAccount).setBaseURI(newBaseURI + "2")).to.be.reverted; await expect(galaxyMember.connect(owner).setBaseURI("")).to.be.reverted; // base uri cannot be empty }); it("Should have b3tr and treasury addresses set correctly", async () => { const { galaxyMember, b3tr, treasury } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.b3tr()).to.equal(await b3tr.getAddress()); expect(await galaxyMember.treasury()).to.equal(await treasury.getAddress()); }); it("Should support ERC 165 interface", async () => { const { galaxyMember } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.supportsInterface("0x01ffc9a7")).to.equal(true); // ERC165 }); it("Should have Vechain Nodes Manager role correctly set", async () => { const { galaxyMember, owner } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.hasRole(await galaxyMember.NODES_MANAGER_ROLE(), owner.address)).to.eql(true); }); it("Should have correct node to free level mapping", async () => { const { galaxyMember } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await galaxyMember.getNodeToFreeLevel(0)).to.equal(1); // Level 1 Free Upgrade for None expect(await galaxyMember.getNodeToFreeLevel(1)).to.equal(2); // Level 2 Free Upgrade for Strength expect(await galaxyMember.getNodeToFreeLevel(2)).to.equal(4); // Level 4 Free Upgrade for Thunder expect(await galaxyMember.getNodeToFreeLevel(3)).to.equal(6); // Level 6 Free Upgrade for Mjolnir expect(await galaxyMember.getNodeToFreeLevel(4)).to.equal(2); // Level 2 Free Upgrade for VeThorX expect(await galaxyMember.getNodeToFreeLevel(5)).to.equal(4); // Level 4 Free Upgrade for StrengthX expect(await galaxyMember.getNodeToFreeLevel(6)).to.equal(6); // Level 6 Free Upgrade for ThunderX expect(await galaxyMember.getNodeToFreeLevel(7)).to.equal(7); // Level 7 Free Upgrade for MjolnirX expect(await galaxyMember.getNodeToFreeLevel(8)).to.equal(0); // No free upgrade for Dawn expect(await galaxyMember.getNodeToFreeLevel(9)).to.equal(0); // No free upgrade for Lightning expect(await galaxyMember.getNodeToFreeLevel(10)).to.equal(0); // No free upgrade for Flash }); }); describe("ERC721 Compliance", () => { let galaxyMember; let owner; let approved; let operator; let other; let tokenId; beforeEach(async () => { const contracts = await getOrDeployContractInstances({ forceDeploy: true, deployMocks: true, }); galaxyMember = contracts.galaxyMember; owner = contracts.owner; approved = contracts.otherAccount; operator = contracts.otherAccounts[0]; other = contracts.otherAccounts[1]; // Mint a token for testing await participateInAllocationVoting(owner); await galaxyMember.connect(owner).freeMint(); tokenId = 1; }); //After each unstake, so the VET is recovered, otherwise some tests will fail because of insufficient VET afterEach(async () => { const { stargateMock, stargateNftMock } = await getOrDeployContractInstances({}); const nodeId = (await stargateNftMock.connect(owner).idsOwnedBy(owner.address))?.[0]; if (nodeId) { await stargateMock.connect(owner).unstake(nodeId); } }); describe("ERC721 Metadata", () => { it("should implement supportsInterface for ERC721Metadata", async () => { const ERC721MetadataInterfaceId = "0x5b5e139f"; expect(await galaxyMember.supportsInterface(ERC721MetadataInterfaceId)).to.be.true; }); it("should return correct name and symbol", async () => { expect(await galaxyMember.name()).to.equal("GalaxyMember"); expect(await galaxyMember.symbol()).to.equal("GM"); }); }); describe("ERC721 Enumerable", () => { it("should implement supportsInterface for ERC721Enumerable", async () => { const ERC721EnumerableInterfaceId = "0x780e9d63"; expect(await galaxyMember.supportsInterface(ERC721EnumerableInterfaceId)).to.be.true; }); it("should correctly implement totalSupply and tokenByIndex", async () => { expect(await galaxyMember.totalSupply()).to.equal(1); expect(await galaxyMember.tokenByIndex(0)).to.equal(tokenId); }); it("should correctly implement tokenOfOwnerByIndex", async () => { expect(await galaxyMember.tokenOfOwnerByIndex(owner.address, 0)).to.equal(tokenId); }); }); describe("ERC721 Core Functions", () => { it("should implement approve correctly", async () => { await galaxyMember.connect(owner).approve(approved.address, tokenId); expect(await galaxyMember.getApproved(tokenId)).to.equal(approved.address); }); it("should implement setApprovalForAll correctly", async () => { await galaxyMember.connect(owner).setApprovalForAll(operator.address, true); expect(await galaxyMember.isApprovedForAll(owner.address, operator.address)).to.be.true; }); it("should emit Approval event on approve", async () => { await expect(galaxyMember.connect(owner).approve(approved.address, tokenId)) .to.emit(galaxyMember, "Approval") .withArgs(owner.address, approved.address, tokenId); }); it("should emit ApprovalForAll event on setApprovalForAll", async () => { await expect(galaxyMember.connect(owner).setApprovalForAll(operator.address, true)) .to.emit(galaxyMember, "ApprovalForAll") .withArgs(owner.address, operator.address, true); }); }); describe("ERC721 Transfer Mechanics", () => { it("should correctly transfer tokens using transferFrom", async () => { await galaxyMember.connect(owner).approve(approved.address, tokenId); await galaxyMember.connect(approved).transferFrom(owner.address, other.address, tokenId); expect(await galaxyMember.ownerOf(tokenId)).to.equal(other.address); }); it("should correctly transfer tokens using safeTransferFrom", async () => { await galaxyMember .connect(owner)["safeTransferFrom(address,address,uint256)"](owner.address, other.address, tokenId); expect(await galaxyMember.ownerOf(tokenId)).to.equal(other.address); }); it("should clear approvals after transfer", async () => { await galaxyMember.connect(owner).approve(approved.address, tokenId); await galaxyMember.connect(owner).transferFrom(owner.address, other.address, tokenId); expect(await galaxyMember.getApproved(tokenId)).to.equal(ethers.ZeroAddress); }); }); describe("ERC721 Safety Checks", () => { it("should revert when transferring to zero address", async () => { await expect(galaxyMember.connect(owner).transferFrom(owner.address, ethers.ZeroAddress, tokenId)).to.be .reverted; }); it("should revert when caller is not owner or approved", async () => { await expect(galaxyMember.connect(other).transferFrom(owner.address, other.address, tokenId)).to.be.reverted; }); it("should revert when querying non-existent token", async () => { const nonExistentTokenId = 999; await expect(galaxyMember.ownerOf(nonExistentTokenId)).to.be.reverted; }); }); describe("ERC721 Receiver Compliance", () => { let receiverContract; beforeEach(async () => { const MockERC721Receiver = await ethers.getContractFactory("MockERC721Receiver"); receiverContract = await MockERC721Receiver.deploy(); }); it("should transfer to ERC721Receiver implementer", async () => { await galaxyMember .connect(owner)["safeTransferFrom(address,address,uint256)"](owner.address, await receiverContract.getAddress(), tokenId); expect(await galaxyMember.ownerOf(tokenId)).to.equal(await receiverContract.getAddress()); }); it("should revert when transferring to non-receiver contract", async () => { // Try to transfer to the GalaxyMember contract itself (which doesn't implement ERC721Receiver) await expect(galaxyMember .connect(owner)["safeTransferFrom(address,address,uint256)"](owner.address, await galaxyMember.getAddress(), tokenId)).to.be.reverted; }); }); }); describe("Contract upgradeablity", () => { it("Admin should be able to upgrade the contract", async function () { const { galaxyMember, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("GalaxyMember"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); const UPGRADER_ROLE = await galaxyMember.UPGRADER_ROLE(); expect(await galaxyMember.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); await expect(galaxyMember.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); it("Only admin should be able to upgrade the contract", async function () { const { galaxyMember, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("GalaxyMember"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); const UPGRADER_ROLE = await galaxyMember.UPGRADER_ROLE(); expect(await galaxyMember.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await expect(galaxyMember.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.be .reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); expect(newImplAddress.toUpperCase()).to.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.not.eql((await implementation.getAddress()).toUpperCase()); }); it("Admin can change UPGRADER_ROLE", async function () { const { galaxyMember, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("GalaxyMember"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); const UPGRADER_ROLE = await galaxyMember.UPGRADER_ROLE(); expect(await galaxyMember.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await expect(galaxyMember.connect(owner).grantRole(UPGRADER_ROLE, otherAccount.address)).to.not.be.reverted; await expect(galaxyMember.connect(owner).revokeRole(UPGRADER_ROLE, owner.address)).to.not.be.reverted; await expect(galaxyMember.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not .be.reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await galaxyMember.getAddress()); expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); it("Should not be able to deploy contract with max level less than 1", async function () { const { owner, b3tr, treasury } = await getOrDeployContractInstances({ forceDeploy: true, }); const config = createLocalConfig(); await expect(deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 0, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: config.GM_NFT_B3TR_REQUIRED_TO_UPGRADE_TO_LEVEL, b3tr: await b3tr.getAddress(), treasury: await treasury.getAddress(), }, ])).to.be.reverted; }); it("Should not be able to increase max level if b3tr required to upgrade is not set", async function () { const { owner, b3tr, treasury, minterAccount, otherAccount, xAllocationVoting, governor } = await getOrDeployContractInstances({ forceDeploy: true, }); const config = createLocalConfig(); await expect(deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 2, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: [], b3tr: await b3tr.getAddress(), treasury: await treasury.getAddress(), }, ])).to.be.reverted; // Deploy with correct b3tr required to upgrade const galaxyMember = (await deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 2, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: [10000000000000000000000n], b3tr: await b3tr.getAddress(), treasury: await treasury.getAddress(), }, ])); await galaxyMember.waitForDeployment(); await galaxyMember.connect(owner).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress()); await galaxyMember.connect(owner).setB3trGovernorAddress(await governor.getAddress()); // Bootstrap emissions await bootstrapEmissions(); // participation in governance is a requirement for minting await participateInAllocationVoting(otherAccount); await galaxyMember.connect(otherAccount).freeMint(); await galaxyMember.connect(otherAccount).burn(0); await galaxyMember.connect(otherAccount).freeMint(); // Upgrade to level 2 await upgradeNFTtoLevel(1, 2, galaxyMember, b3tr, otherAccount, minterAccount); await expect(upgradeNFTtoLevel(1, 3, galaxyMember, b3tr, otherAccount, minterAccount)).to.be.reverted; // Should not be able to upgrade to level 3 // Set max level to 3 await expect(galaxyMember.connect(owner).setMaxLevel(3)).to.be.reverted; // Should not be able to set max level to 3 as b3tr required to upgrade to level 3 is not set await galaxyMember.setB3TRtoUpgradeToLevel([10000000000000000000000n, 25000000000000000000000n]); // Set b3tr required to upgrade to level 3 too await galaxyMember.connect(owner).setMaxLevel(3); // Should be able to set max level to 3 now }); it("Should not be able to deploy contract if base uri is empty", async function () { const { owner, b3tr, treasury } = await getOrDeployContractInstances({ forceDeploy: true, }); const config = createLocalConfig(); await expect(deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 1, baseTokenURI: "", b3trToUpgradeToLevel: config.GM_NFT_B3TR_REQUIRED_TO_UPGRADE_TO_LEVEL, b3tr: await b3tr.getAddress(), treasury: await treasury.getAddress(), }, ])).to.be.reverted; }); it("Should not be able to deploy contract if b3tr address is not set", async function () { const { owner, treasury } = await getOrDeployContractInstances({ forceDeploy: true, }); const config = createLocalConfig(); await expect(deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 1, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: config.GM_NFT_B3TR_REQUIRED_TO_UPGRADE_TO_LEVEL, b3tr: ZERO_ADDRESS, treasury: await treasury.getAddress(), }, ])).to.be.reverted; }); it("Should not be able to deploy contract if treasury address is not set", async function () { const { owner, b3tr } = await getOrDeployContractInstances({ forceDeploy: true, }); const config = createLocalConfig(); await expect(deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 1, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: config.GM_NFT_B3TR_REQUIRED_TO_UPGRADE_TO_LEVEL, b3tr: await b3tr.getAddress(), treasury: ZERO_ADDRESS, }, ])).to.be.reverted; }); it("Should return correct version of the contract", async () => { const { galaxyMember } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await galaxyMember.version()).to.equal("6"); }); it("Should not have state conflict after upgrading", async () => { const config = createLocalConfig(); const { owner, b3tr, treasury, governor, xAllocationVoting, otherAccount, otherAccounts, minterAccount, vechainNodesMock, nodeManagement, stargateNftMock, } = await getOrDeployContractInstances({ forceDeploy: true, config, deployMocks: true, }); if (!vechainNodesMock) throw new Error("VechainNodesMock not deployed"); // Bootstrap emissions await bootstrapEmissions(); // Should be able to free mint after participating in allocation voting await participateInAllocationVoting(owner, false, otherAccounts[10]); const galaxyMember = (await deployProxy("GalaxyMemberV1", [ { name: NFT_NAME, symbol: NFT_SYMBOL, admin: owner.address, upgrader: owner.address, pauser: owner.address, minter: owner.address, contractsAddressManager: owner.address, maxLevel: 5, baseTokenURI: config.GM_NFT_BASE_URI, b3trToUpgradeToLevel: config.GM_NFT_B3TR_REQUIRED_TO_UPGRADE_TO_LEVEL, b3tr: await b3tr.getAddress(), treasury: await treasury.getAddress(), }, ])); await galaxyMember.waitForDeployment(); expect(await galaxyMember.MAX_LEVEL()).to.equal(5); // Contract setup await galaxyMember.connect(owner).setB3trGovernorAddress(await governor.getAddress()); await galaxyMember.connect(owner).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress()); // Participation in governance is a requirement for minting const participated = await galaxyMember.connect(owner).participatedInGovernance(owner); expect(participated).to.equal(true); // Mint 4 tokens to owner await galaxyMember.connect(owner).freeMint(); // gmId 0 await galaxyMember.connect(owner).burn(0); // gmId 0 await galaxyMember.connect(owner).freeMint(); // gmId 1 await galaxyMember.connect(owner).freeMint(); // gmId 2 await galaxyMember.connect(owner).freeMint(); // gmId 3 await galaxyMember.connect(owner).freeMint(); // gmId 4 expect(await galaxyMember.balanceOf(await owner.getAddress())).to.equal(4); // Transfer GM NFTs to other accounts: otherAccount, otherAccounts[0], otherAccounts[1] await galaxyMember.connect(owner)["safeTransferFrom(address,address,uint256)"](owner, otherAccount, 1); await galaxyMember.connect(owner).transferFrom(owner.address, otherAccounts[0].address, 2); await galaxyMember.connect(owner).transferFrom(owner.address, otherAccounts[1].address, 3); // All 4 accounts hold 1 GM NFT each expect(await galaxyMember.balanceOf(await owner.getAddress())).to.equal(1); expect(await galaxyMember.balanceOf(await otherAccount.getAddress())).to.equal(1); expect(await galaxyMember.balanceOf(await otherAccounts[0].getAddress())).to.equal(1); expect(await galaxyMember.balanceOf(await otherAccounts[1].getAddress())).to.equal(1); expect(await galaxyMember.ownerOf(1)).to.equal(await otherAccount.getAddress()); expect(await galaxyMember.ownerOf(2)).to.equal(await otherAccounts[0].getAddress()); expect(await galaxyMember.ownerOf(3)).to.equal(await otherAccounts[1].getAddress()); expect(await galaxyMember.ownerOf(4)).to.equal(await owner.getAddress()); // All GMs are of level 1 expect(await galaxyMember.levelOf(1)).to.equal(1); // Earth expect(await galaxyMember.levelOf(2)).to.equal(1); expect(await galaxyMember.levelOf(3)).to.equal(1); expect(await galaxyMember.levelOf(4)).to.equal(1); let storageSlots = []; const initialSlot = BigInt("0x7a79e46844ed04411e4579c7bc49d053e59b0854fa4e9a8df3d5a0597ce45200"); // Slot 0 of GalaxyMember for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlots.push(await ethers.provider.getStorage(await galaxyMember.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots const galaxyMemberV2 = (await upgradeProxy("GalaxyMemberV1", "GalaxyMemberV2", await galaxyMember.getAddress(), [owner.address, await nodeManagement.getAddress(), owner.address, config.GM_NFT_NODE_TO_FREE_LEVEL], { version: 2 })); let storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await ethers.provider.getStorage(await galaxyMemberV2.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { // console.log("*** storageSlots v1", storageSlots[i], "vs v2", storageSlotsAfter[i]) expect(storageSlots[i]).to.equal(storageSlotsAfter[i]); } // Contract setup await galaxyMemberV2.setVechainNodes(await vechainNodesMock.getAddress()); expect(await galaxyMemberV2.MAX_LEVEL()).to.equal(5); await galaxyMemberV2.setMaxLevel(10); expect(await galaxyMemberV2.MAX_LEVEL()).to.equal(10); // Check if all GM NFTs are still owned by the original owners expect(await galaxyMemberV2.balanceOf(await owner.getAddress())).to.equal(1); expect(await galaxyMemberV2.balanceOf(await otherAccount.getAddress())).to.equal(1); expect(await galaxyMemberV2.balanceOf(await otherAccounts[0].getAddress())).to.equal(1); expect(await galaxyMemberV2.balanceOf(await otherAccounts[1].getAddress())).to.equal(1); expect(await galaxyMemberV2.ownerOf(1)).to.equal(await otherAccount.getAddress()); expect(await galaxyMemberV2.ownerOf(2)).to.equal(await otherAccounts[0].getAddress()); expect(await galaxyMemberV2.ownerOf(3)).to.equal(await otherAccounts[1].getAddress()); expect(await galaxyMemberV2.ownerOf(4)).to.equal(await owner.getAddress()); // All GMs are still of level 1 expect(await galaxyMemberV2.levelOf(1)).to.equal(1); expect(await galaxyMemberV2.levelOf(2)).to.equal(1); expect(await galaxyMemberV2.levelOf(3)).to.equal(1); expect(await galaxyMemberV2.levelOf(4)).to.equal(1); // Mint a new GM NFT to owner await galaxyMemberV2.connect(owner).freeMint(); expect(await galaxyMemberV2.balanceOf(await owner.getAddress())).to.equal(2); expect(await galaxyMemberV2.ownerOf(5)).to.equal(await owner.getAddress()); expect(await galaxyMemberV2.levelOf(5)).to.equal(1); // Earth // Let's upgrade gmId 1, owned by otherAccount, to level 2 await upgradeNFTtoLevel(1, 2, galaxyMemberV2, b3tr, otherAccount, minterAccount); expect(await galaxyMemberV2.levelOf(1)).to.equal(2); // Moon // Mint Mjolnir X Node to otherAccount await mintLegacyNode(7, otherAccount); const nodeId = 1; //Should be the first minted expect(await vechainNodesMock.idToOwner(nodeId)).to.equal(await otherAccount.getAddress()); // Expect a free upgrade to level 7 when attaching a Mjolnir X Node to a GM NFT of a lower level expect(await galaxyMemberV2.getLevelAfterAttachingNode(1, nodeId)).to.equal(7); // Attach nodeId 2 to gmId 1 await galaxyMemberV2.connect(otherAccount).attachNode(nodeId, 1); expect(await galaxyMemberV2.levelOf(1)).to.equal(7); // Saturn expect(await galaxyMemberV2.tokenURI(1)).to.equal(config.GM_NFT_BASE_URI + "7.json"); storageSlots = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlots.push(await ethers.provider.getStorage(await galaxyMemberV2.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); const galaxyMemberV3 = (await upgradeProxy("GalaxyMemberV2", "GalaxyMemberV3", await galaxyMember.getAddress(), [], { version: 3 })); storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await ethers.provider.getStorage(await galaxyMemberV3.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { // console.log("*** storageSlots v2", storageSlots[i], "vs v3", storageSlotsAfter[i]) expect(storageSlots[i]).to.equal(storageSlotsAfter[i]); } // Check if all GM NFTs are still owned by the original owners expect(await galaxyMemberV3.balanceOf(await owner.getAddress())).to.equal(2); expect(await galaxyMemberV3.balanceOf(await otherAccount.getAddress())).to.equal(1); expect(await galaxyMemberV3.balanceOf(await otherAccounts[0].getAddress())).to.equal(1); expect(await galaxyMemberV3.balanceOf(await otherAccounts[1].getAddress())).to.equal(1); expect(await galaxyMemberV3.ownerOf(1)).to.equal(await otherAccount.getAddress()); expect(await galaxyMemberV3.ownerOf(2)).to.equal(await otherAccounts[0].getAddress()); expect(await galaxyMemberV3.ownerOf(3)).to.equal(await otherAccounts[1].getAddress()); expect(await galaxyMemberV3.ownerOf(4)).to.equal(await owner.getAddress()); expect(await galaxyMemberV3.ownerOf(5)).to.equal(await owner.getAddress()); // Check that existing GM NFTs have the same level expect(await galaxyMemberV3.levelOf(1)).to.equal(7); expect(await galaxyMemberV3.levelOf(2)).to.equal(1); expect(await galaxyMemberV3.levelOf(3)).to.equal(1); expect(await galaxyMemberV3.levelOf(4)).to.equal(1); expect(await galaxyMemberV3.levelOf(5)).to.equal(1); // Mint a new GM NFT to owner await galaxyMemberV3.connect(owner).freeMint(); expect(await galaxyMemberV3.balanceOf(await owner.getAddress())).to.equal(3); expect(await galaxyMemberV3.ownerOf(6)).to.equal(await owner.getAddress()); expect(await galaxyMemberV3.levelOf(6)).to.equal(1); // Earth // Get selected token Id at block number - since you can hold +1 GM NFT, select one to boost rewards expect(await galaxyMemberV3.getSelectedTokenIdAtBlock(owner.address, await ethers.provider.getBlockNumber())).to.equal(0n); // admin selects gmId 4 as part of upgrade await galaxyMemberV3.connect(owner).selectFor(owner.getAddress(), 4); expect(await galaxyMemberV3.getSelectedTokenId(owner.getAddress())).to.equal(4); // Get selected gmId at block number expect(await galaxyMemberV3.getSelectedTokenIdAtBlock(owner.address, await ethers.provider.getBlockNumber())).to.equal(4n); // Transfer gmId 4 to otherAccounts[6] - upon transfer, if the `from` address had that id selected, // and if they own other GM NFTs, the "first" one is selected await galaxyMemberV3.connect(owner).transferFrom(owner.address, otherAccounts[6].address, 4); expect(await galaxyMemberV3.getSelectedTokenId(owner.getAddress())).to.equal(6n); // otherAccounts[6] expect(await galaxyMemberV3.getSelectedTokenIdAtBlock(owner.address, await ethers.provider.getBlockNumber())).to.equal(6n); // Check if the token is transferred expect(await galaxyMemberV3.ownerOf(4)).to.equal(await otherAccounts[6].getAddress()); // Get selected token Id at block number for the transfer recipient expect(await galaxyMemberV3.getSelectedTokenIdAtBlock(otherAccounts[6].address, await ethers.provider.getBlockNumber())).to.equal(4n); storageSlots = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlots.push(await ethers.provider.getStorage(await galaxyMemberV3.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); const galaxyMemberV4 = (await upgradeProxy("GalaxyMemberV3", "GalaxyMemberV4", await galaxyMember.getAddress(), [], { version: 4 })); storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await ethers.provider.getStorage(await galaxyMemberV4.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { // console.log("*** storageSlots v3", storageSlots[i], "vs v4", storageSlotsAfter[i]) expect(storageSlots[i]).to.equal(storageSlotsAfter[i]); } // Check if all GM NFTs are still owned by the original owners expect(await galaxyMemberV4.balanceOf(await owner.getAddress())).to.equal(2); expect(await galaxyMemberV4.balanceOf(await otherAccount.getAddress())).to.equal(1); expect(await galaxyMemberV4.balanceOf(await otherAccounts[0].getAddress())).to.equal(1); expect(await galaxyMemberV4.balanceOf(await otherAccounts[1].getAddress())).to.equal(1); expect(await galaxyMemberV4.balanceOf(await otherAccounts[6].getAddress())).to.equal(1); expect(await galaxyMemberV3.ownerOf(1)).to.equal(await otherAccount.getAddress()); expect(await galaxyMemberV3.ownerOf(2)).to.equal(await otherAccounts[0].getAddress()); expect(await galaxyMemberV3.ownerOf(3)).to.equal(await otherAccounts[1].getAddress()); expect(await galaxyMemberV4.ownerOf(4)).to.equal(await otherAccounts[6].getAddress()); expect(await galaxyMemberV3.ownerOf(5)).to.equal(await owner.getAddress()); expect(await galaxyMemberV3.ownerOf(6)).to.equal(await owner.getAddress()); // Check that existing GM NFTs have the same level expect(await galaxyMemberV3.levelOf(1)).to.equal(7); expect(await galaxyMemberV3.levelOf(2)).to.equal(1); expect(await galaxyMemberV3.levelOf(3)).to.equal(1); expect(await galaxyMemberV3.levelOf(4)).to.equal(1); expect(await galaxyMemberV3.levelOf(5)).to.equal(1); expect(await galaxyMemberV3.levelOf(6)).to.equal(1); // Transfer gmId 6 to otherAccounts[6] await galaxyMemberV4.connect(owner).transferFrom(owner.address, otherAccounts[6].address, 6); // Check if the token is transferred expect(await galaxyMemberV4.ownerOf(6)).to.equal(await otherAccounts[6].getAddress()); // Check token selection expect(await galaxyMemberV4.getSelectedTokenId(owner.getAddress())).to.equal(5n); // defaults to the first token expect(await galaxyMemberV4.getSelectedTokenId(otherAccounts[6].getAddress())).to.equal(4n); // otherAccounts[6] had 4 selected // V5 is about pointing nodeManagement to version 3, and deprecate the usage of tokenAuction contract // Before the upgrade, I can correctly check the level of the vechain node expect(await galaxyMemberV4.getNodeLevelOf(nodeId)).to.equal(7); // The node is still attached to the GM NFT, resulting on the GM NFT having level 7 expect(await galaxyMemberV4.getIdAttachedToNode(nodeId)).to.equal(1); expect(await galaxyMemberV4.levelOf(1)).to.equal(7); storageSlots = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlots.push(await ethers.provider.getStorage(await galaxyMemberV4.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // before the upgrade, we save the mapping _nodeToFreeUpgradeLevel values (to check later) const levels = await stargateNftMock.getLevels(); const freeUpgradeLevels = []; for (const level of levels) { const freeUpgradeLevel = await galaxyMemberV4.getNodeToFreeLevel(level.id); freeUpgradeLevels.push(freeUpgradeLevel); } const galaxyMemberV5 = (await upgradeProxy("GalaxyMemberV4", "GalaxyMemberV5", await galaxyMember.getAddress(), [], { version: 5 })); storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await ethers.provider.getStorage(await galaxyMemberV5.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { // console.log("*** storageSlots v4", storageSlots[i], "vs v5", storageSlotsAfter[i]) expect(storageSlots[i]).to.equal(storageSlotsAfter[i]); } // Check if all GM NFTs are still owned by the original owners expect(await galaxyMemberV5.balanceOf(await owner.getAddress())).to.equal(1); expect(await galaxyMemberV5.balanceOf(await otherAccount.getAddress())).to.equal(1); expect(await galaxyMemberV5.balanceOf(await otherAccounts[0].getAddress())).to.equal(1); expect(await galaxyMemberV5.balanceOf(await otherAccounts[1].getAddress())).to.equal(1); expect(await galaxyMemberV5.balanceOf(await otherAccounts[6