UNPKG

@vechain/vebetterdao-contracts

Version:

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

710 lines 54.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const local_1 = require("@repo/config/contracts/envs/local"); const chai_1 = require("chai"); const hardhat_1 = require("hardhat"); const mocha_1 = require("mocha"); const helpers_1 = require("../scripts/helpers"); const helpers_2 = require("./helpers"); const xnodes_1 = require("./helpers/xnodes"); (0, mocha_1.describe)("X-Apps - Core Features - @shard15a", function () { // We prepare the environment for 4 creators let creator1; let creator2; let creator3; let creator4; (0, mocha_1.before)(async function () { const { creators } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); creator1 = creators[0]; creator2 = creators[1]; creator3 = creators[2]; creator4 = creators[3]; }); (0, mocha_1.describe)("Deployment", function () { (0, mocha_1.it)("Clock mode is set correctly", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await x2EarnApps.CLOCK_MODE()).to.eql("mode=blocknumber&from=default"); }); (0, mocha_1.it)("Node level to endorsement score mapping is correct", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(0)).to.eql(0n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(1)).to.eql(2n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(2)).to.eql(13n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(3)).to.eql(50n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(4)).to.eql(3n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(5)).to.eql(9n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(6)).to.eql(35n); (0, chai_1.expect)(await x2EarnApps.nodeLevelEndorsementScore(7)).to.eql(100n); }); (0, mocha_1.it)("Version returns a string", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const version = await x2EarnApps.version(); (0, chai_1.expect)(typeof version).to.eql("string"); (0, chai_1.expect)(version.length).to.be.greaterThan(0); }); (0, mocha_1.it)("Cooldown period is set correctly", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const config = (0, local_1.createLocalConfig)(); (0, chai_1.expect)(await x2EarnApps.cooldownPeriod()).to.eql(BigInt(config.X2EARN_NODE_COOLDOWN_PERIOD)); }); (0, mocha_1.it)("hashAppName returns a bytes32 hash", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const hash = await x2EarnApps.hashAppName("TestApp"); (0, chai_1.expect)(hash).to.match(/^0x[a-fA-F0-9]{64}$/); // Verify deterministic hashing const hash2 = await x2EarnApps.hashAppName("TestApp"); (0, chai_1.expect)(hash).to.eql(hash2); // Different input produces different hash const hash3 = await x2EarnApps.hashAppName("DifferentApp"); (0, chai_1.expect)(hash).to.not.eql(hash3); }); }); (0, mocha_1.describe)("Settings", function () { (0, mocha_1.it)("Admin can set baseURI for apps", async function () { const { owner, x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const initialURI = await x2EarnApps.baseURI(); await x2EarnApps.connect(owner).setBaseURI("ipfs2://"); const updatedURI = await x2EarnApps.baseURI(); (0, chai_1.expect)(updatedURI).to.eql("ipfs2://"); (0, chai_1.expect)(updatedURI).to.not.eql(initialURI); }); (0, mocha_1.it)("Limit of 100 moderators and distributors is set", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await x2EarnApps.MAX_MODERATORS()).to.eql(100n); (0, chai_1.expect)(await x2EarnApps.MAX_REWARD_DISTRIBUTORS()).to.eql(100n); }); }); (0, mocha_1.describe)("Add apps", function () { (0, mocha_1.it)("Should be able to register an app successfully", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[0].address)); let tx = await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); let receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let appAdded = (0, helpers_2.filterEventsByName)(receipt.logs, "AppAdded"); (0, chai_1.expect)(appAdded).not.to.eql([]); let { id, address } = await (0, helpers_2.parseAppAddedEvent)(appAdded[0]); (0, chai_1.expect)(id).to.eql(app1Id); (0, chai_1.expect)(address).to.eql(otherAccounts[0].address); }); (0, mocha_1.it)("Should not be able to register an app if it is already registered", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, helpers_2.catchRevert)(x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI")); }); (0, mocha_1.it)("Should be able to fetch app team wallet address", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); //Add apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[2].address, otherAccounts[2].address, "My app", "metadataURI"); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[3].address, otherAccounts[3].address, "My app #2", "metadataURI"); const app1ReceiverAddress = await x2EarnApps.teamWalletAddress(app1Id); const app2ReceiverAddress = await x2EarnApps.teamWalletAddress(app2Id); (0, chai_1.expect)(app1ReceiverAddress).to.eql(otherAccounts[2].address); (0, chai_1.expect)(app2ReceiverAddress).to.eql(otherAccounts[3].address); }); (0, mocha_1.it)("Cannot register an app that has ZERO address as the team wallet address", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, helpers_2.catchRevert)(x2EarnApps.connect(owner).submitApp(helpers_2.ZERO_ADDRESS, otherAccounts[2].address, "My app", "metadataURI")); }); (0, mocha_1.it)("Cannot register an app that has ZERO address as the admin", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, helpers_2.catchRevert)(x2EarnApps.connect(owner).submitApp(otherAccounts[2].address, helpers_2.ZERO_ADDRESS, "My app", "metadataURI")); }); (0, mocha_1.it)("Only users with the XAPP creator nft can register an app", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)(x2EarnApps .connect(otherAccounts[11]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, "My app", "metadataURI")).to.be.revertedWithCustomError(x2EarnApps, "X2EarnUnverifiedCreator"); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[2].address, otherAccounts[2].address, "My app", "metadataURI"); }); (0, mocha_1.it)("Should enable rewards pool for new app when registering an app", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await (0, chai_1.expect)(x2EarnApps.enableRewardsPoolForNewApp(app1Id)).to.be.revertedWith("X2EarnRewardsPool: rewards pool already enabled"); }); (0, mocha_1.it)("Rewards pool should be enabled for new apps and disabled for older apps", async function () { const { x2EarnApps, otherAccounts, owner, x2EarnRewardsPool, timeLock, nodeManagement, veBetterPassport, x2EarnCreator, administrationUtilsV2, endorsementUtilsV2, voteEligibilityUtilsV2, administrationUtilsV3, endorsementUtilsV3, voteEligibilityUtilsV3, } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); const config = (0, local_1.createLocalConfig)(); config.EMISSIONS_CYCLE_DURATION = 24; config.X2EARN_NODE_COOLDOWN_PERIOD = 1; const xAllocationGovernor = otherAccounts[1].address; const veBetterPassportContractAddress = await veBetterPassport.getAddress(); const x2EarnAppsV3 = (await (0, helpers_1.deployAndUpgrade)(["X2EarnAppsV1", "X2EarnAppsV2", "X2EarnAppsV3"], [ ["ipfs://", [await timeLock.getAddress(), owner.address], owner.address, owner.address], [ config.XAPP_GRACE_PERIOD, await nodeManagement.getAddress(), veBetterPassportContractAddress, await x2EarnCreator.getAddress(), ], [config.X2EARN_NODE_COOLDOWN_PERIOD, xAllocationGovernor], ], { versions: [undefined, 2, 3], libraries: [ undefined, { AdministrationUtilsV2: await administrationUtilsV2.getAddress(), EndorsementUtilsV2: await endorsementUtilsV2.getAddress(), VoteEligibilityUtilsV2: await voteEligibilityUtilsV2.getAddress(), }, { AdministrationUtilsV3: await administrationUtilsV3.getAddress(), EndorsementUtilsV3: await endorsementUtilsV3.getAddress(), VoteEligibilityUtilsV3: await voteEligibilityUtilsV3.getAddress(), }, ], })); // The app was prev deployed with version 3 of x2earn app const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await x2EarnAppsV3 .connect(owner) .submitApp(otherAccounts[2].address, otherAccounts[2].address, "My app", "metadataURI"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[4].address, otherAccounts[4].address, "My app #2", "metadataURI"); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); // check that the rewards pool is not enabled by default for the older app (0, chai_1.expect)(await x2EarnRewardsPool.isRewardsPoolEnabled(app1Id)).to.be.equal(false); // check that the rewards pool is enabled by default for the new app (0, chai_1.expect)(await x2EarnRewardsPool.isRewardsPoolEnabled(app2Id)).to.be.equal(true); }); }); (0, mocha_1.describe)("Fetch apps", function () { (0, mocha_1.it)("Can get unendorsed app ids", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.unendorsedAppIds(); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(creator2) .submitApp(otherAccounts[1].address, otherAccounts[1].address, "My app #2", "metadataURI"); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); // unendorsed apps const appIds = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIds).to.eql([app1Id, app2Id]); }); (0, mocha_1.it)("Can retrieve app by id", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const app = await x2EarnApps.app(app1Id); (0, chai_1.expect)(app.id).to.eql(app1Id); (0, chai_1.expect)(app.teamWalletAddress).to.eql(otherAccounts[0].address); (0, chai_1.expect)(app.name).to.eql("My app"); (0, chai_1.expect)(app.metadataURI).to.eql("metadataURI"); }); (0, mocha_1.it)("Can index endorsed apps", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[1].address, otherAccounts[1].address, "My app #2", "metadataURI"); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[1]); const apps = await x2EarnApps.apps(); (0, chai_1.expect)(apps.length).to.eql(2); }); (0, mocha_1.it)("Can index unendorsed apps", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps .connect(creator2) .submitApp(otherAccounts[1].address, otherAccounts[1].address, "My app #2", "metadataURI"); const apps = await x2EarnApps.unendorsedApps(); (0, chai_1.expect)(apps.length).to.eql(2); }); (0, mocha_1.it)("Can get number of apps", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[1].address, otherAccounts[1].address, "My app #2", "metadataURI"); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[1]); await x2EarnApps .connect(creator2) .submitApp(otherAccounts[2].address, otherAccounts[2].address, "My app #3", "metadataURI"); const app3Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #3")); await (0, xnodes_1.endorseApp)(app3Id, otherAccounts[2]); await x2EarnApps .connect(creator3) .submitApp(otherAccounts[3].address, otherAccounts[3].address, "My app #4", "metadataURI"); const app4Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #4")); await (0, xnodes_1.endorseApp)(app4Id, otherAccounts[3]); const apps = await x2EarnApps.apps(); (0, chai_1.expect)(apps.length).to.eql(4); }); (0, mocha_1.it)("Can fetch up to 1000 apps without pagination", async function () { console.log("Test disabled"); // const { x2EarnApps, otherAccounts, owner, xAllocationVoting } = await getOrDeployContractInstances({ // forceDeploy: true, // }) // const limit = 1000 // let registerAppsPromises = [] // for (let i = 1; i <= limit; i++) { // registerAppsPromises.push( // x2EarnApps // .connect(owner) // .submitApp(otherAccounts[1].address, otherAccounts[1].address, "My app" + i, "metadataURI"), // ) // const appId = ethers.keccak256(ethers.toUtf8Bytes("My app" + i)) // await endorseApp(appId, otherAccounts[i]) // } // await Promise.all(registerAppsPromises) // const apps = await x2EarnApps.apps() // expect(apps.length).to.eql(limit) // // check that can correctly fetch apps in round // await startNewAllocationRound() // const appsInRound = await xAllocationVoting.getAppsOfRound(1) // expect(appsInRound.length).to.eql(limit) }); }); (0, mocha_1.describe)("App availability for allocation voting", function () { (0, mocha_1.it)("Should be possible to endorse an app and make it available for allocation voting", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.hasRole(await x2EarnApps.GOVERNANCE_ROLE(), owner.address)).to.eql(true); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); let roundId = await (0, helpers_2.startNewAllocationRound)(); const isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, roundId); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("Admin can make an app unavailable for allocation voting starting from next round", async function () { const { xAllocationVoting, x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.hasRole(await x2EarnApps.GOVERNANCE_ROLE(), owner.address)).to.eql(true); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); let round1 = await (0, helpers_2.startNewAllocationRound)(); await x2EarnApps.connect(owner).setVotingEligibility(app1Id, false); // app should still be eligible for the current round let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); let appsVotedInSpecificRound = await xAllocationVoting.getAppIdsOfRound(round1); (0, chai_1.expect)(appsVotedInSpecificRound.length).to.equal(1n); await (0, helpers_2.waitForRoundToEnd)(round1); let round2 = await (0, helpers_2.startNewAllocationRound)(); // app should not be eligible from this round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(false); appsVotedInSpecificRound = await xAllocationVoting.getAppIdsOfRound(round2); (0, chai_1.expect)(appsVotedInSpecificRound.length).to.equal(0); // if checking for the previous round, it should still be eligible isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("Admin with governance role can make an unavailable app available again starting from next round", async function () { const { xAllocationVoting, x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.hasRole(await x2EarnApps.GOVERNANCE_ROLE(), owner.address)).to.eql(true); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(true); await x2EarnApps.connect(owner).setVotingEligibility(app1Id, false); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(false); let round1 = await (0, helpers_2.startNewAllocationRound)(); // app should still be eligible for the current round let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(false); await x2EarnApps.connect(owner).setVotingEligibility(app1Id, true); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(true); (0, chai_1.expect)(await x2EarnApps.isEligible(app1Id, await xAllocationVoting.roundSnapshot(round1))).to.eql(false); // app still should not be eligible from this round (0, chai_1.expect)(await xAllocationVoting.isEligibleForVote(app1Id, round1)).to.eql(false); await (0, helpers_2.waitForRoundToEnd)(round1); let round2 = await (0, helpers_2.startNewAllocationRound)(); // app should be eligible from this round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("Non existing app is not eligible", async function () { const { xAllocationVoting, x2EarnApps, owner, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appId = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[0].address)); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(appId)).to.eql(false); (0, chai_1.expect)(await x2EarnApps.isEligible(appId, (await xAllocationVoting.clock()) - 1n)).to.eql(false); }); (0, mocha_1.it)("Non endorsed app is not eligible", async function () { const { xAllocationVoting, x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName(helpers_2.ZERO_ADDRESS); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(false); (0, chai_1.expect)(await x2EarnApps.isEligible(app1Id, (await xAllocationVoting.clock()) - 1n)).to.eql(false); }); (0, mocha_1.it)("Cannot get eligilibity in the future", async function () { const { xAllocationVoting, x2EarnApps, otherAccounts, owner } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); await (0, chai_1.expect)(x2EarnApps.isEligible(app1Id, (await xAllocationVoting.clock()) + 1n)).to.be.reverted; }); (0, mocha_1.it)("DAO can make an app unavailable for allocation voting starting from next round", async function () { const { otherAccounts, x2EarnApps, xAllocationVoting, emissions, timeLock, owner, endorsementUtils, administrationUtils, voteEligibilityUtils, appStorageUtils, } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, helpers_2.bootstrapAndStartEmissions)(); const app1Id = await x2EarnApps.hashAppName("Bike 4 Life"); const proposer = otherAccounts[0]; const voter1 = otherAccounts[1]; // check that app does not exists await (0, chai_1.expect)(x2EarnApps.app(app1Id)).to.be.reverted; // granting role to the timelock await x2EarnApps.grantRole(await x2EarnApps.GOVERNANCE_ROLE(), await timeLock.getAddress()); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "Bike 4 Life", "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); await (0, helpers_2.waitForCurrentRoundToEnd)(); // start new round await emissions.distribute(); let round1 = await xAllocationVoting.currentRoundId(); let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); await (0, helpers_2.waitForCurrentRoundToEnd)(); await (0, helpers_2.createProposalAndExecuteIt)(proposer, voter1, x2EarnApps, await hardhat_1.ethers.getContractFactory("X2EarnApps", { libraries: { AdministrationUtils: await administrationUtils.getAddress(), EndorsementUtils: await endorsementUtils.getAddress(), VoteEligibilityUtils: await voteEligibilityUtils.getAddress(), AppStorageUtils: await appStorageUtils.getAddress(), }, }), "Exclude app from the allocation voting rounds", "setVotingEligibility", [app1Id, false]); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); await (0, helpers_2.waitForCurrentRoundToEnd)(); await emissions.distribute(); let round2 = await xAllocationVoting.currentRoundId(); // app should not be eligible from this round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(false); }); (0, mocha_1.it)("Non-admin address cannot make an app available or unavailable for allocation voting", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: false }); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); (0, chai_1.expect)(await x2EarnApps.hasRole(await x2EarnApps.GOVERNANCE_ROLE(), otherAccounts[0].address)).to.eql(false); await (0, helpers_2.catchRevert)(x2EarnApps.connect(otherAccounts[0]).setVotingEligibility(app1Id, true)); }); (0, mocha_1.it)("App needs to wait next round if added during an ongoing round", async function () { const { otherAccounts, x2EarnApps, owner, xAllocationVoting, veBetterPassport } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); // Bootstrap emissions await (0, helpers_2.bootstrapEmissions)(); const voter = otherAccounts[0]; await (0, helpers_2.getVot3Tokens)(voter, "30000"); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); let round1 = await (0, helpers_2.startNewAllocationRound)(); await veBetterPassport.whitelist(voter.address); await veBetterPassport.toggleCheck(1); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[0]); let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(false); //check that I cannot vote for this app in current round await (0, helpers_2.catchRevert)(xAllocationVoting.connect(voter).castVote(round1, [app1Id], [hardhat_1.ethers.parseEther("1")])); let appVotes = await xAllocationVoting.getAppVotes(round1, app1Id); (0, chai_1.expect)(appVotes).to.equal(0n); let appsVotedInSpecificRound = await xAllocationVoting.getAppIdsOfRound(round1); (0, chai_1.expect)(appsVotedInSpecificRound.length).to.equal(0); await (0, helpers_2.waitForRoundToEnd)(round1); let round2 = await (0, helpers_2.startNewAllocationRound)(); // app should be eligible from this round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check that I can vote for this app (0, chai_1.expect)(await xAllocationVoting.connect(voter).castVote(round2, [app1Id], [hardhat_1.ethers.parseEther("1")])).to.not.be .reverted; appVotes = await xAllocationVoting.getAppVotes(round2, app1Id); (0, chai_1.expect)(appVotes).to.equal(hardhat_1.ethers.parseEther("1")); }); (0, mocha_1.it)("Cannot set Eligibility for non existing app", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await (0, helpers_2.catchRevert)(x2EarnApps.setVotingEligibility(app1Id, true)); }); }); (0, mocha_1.describe)("Creator NFT", function () { (0, mocha_1.it)("Users with the XAPP creator nft can register an app sucesfully", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps.connect(creator1).submitApp(creator2.address, creator2.address, creator2.address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(creator2.address)); // App should be registered successfully (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // User should be listed as one of the apps creators (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([creator1.address]); }); (0, mocha_1.it)("App admin can't add more than 3 creator for the app", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps.connect(creator1).submitApp(creator2.address, creator2.address, creator2.address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(creator2.address)); // App should be registered successfully (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // User should be listed as one of the apps creators (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([creator1.address]); // App admin can add more creators for the app await (0, chai_1.expect)(x2EarnApps.connect(creator2).addCreator(app1Id, creator2.address)).to.emit(x2EarnApps, "CreatorAddedToApp"); // App admin can add more creators for the app await (0, chai_1.expect)(x2EarnApps.connect(creator2).addCreator(app1Id, creator3.address)).to.emit(x2EarnApps, "CreatorAddedToApp"); // App admin can add more creators for the app await (0, chai_1.expect)(x2EarnApps.connect(creator2).addCreator(app1Id, creator4.address)).to.revertedWithCustomError(x2EarnApps, "X2EarnMaxCreatorsReached"); // New creator should be added to the app (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([creator1.address, creator2.address, creator3.address]); }); (0, mocha_1.it)("Added creator can't submit another app unless removed from the app as a creator", async function () { const { x2EarnApps, x2EarnCreator } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps.connect(creator1).submitApp(creator2.address, creator2.address, creator2.address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(creator2.address)); // App should be registered successfully (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // User should be listed as one of the apps creators (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([creator1.address]); // Adding creator2 to the app await (0, chai_1.expect)(x2EarnApps.connect(creator2).addCreator(app1Id, creator2.address)).to.emit(x2EarnApps, "CreatorAddedToApp"); (0, chai_1.expect)(await x2EarnApps.isCreatorOfAnyApp(creator2.address)).to.eql(true); // the added creator try to submit another app await (0, chai_1.expect)(x2EarnApps.connect(creator2).submitApp(creator4.address, creator4.address, creator4.address, "metadataURI2")).to.be.revertedWithCustomError(x2EarnApps, "CreatorNFTAlreadyUsed"); // we remove the creator2 from the app, to let him submit another app await x2EarnApps.connect(creator2).removeAppCreator(app1Id, creator2.address); // creator2 should be eligible to submit another app ( go back to the process of minting a new creator NFT) await x2EarnCreator.safeMint(creator2.address); (0, chai_1.expect)(await x2EarnApps.isCreatorOfAnyApp(creator2.address)).to.eql(false); await x2EarnApps.connect(creator2).submitApp(creator3.address, creator3.address, creator3.address, "metadataURI2"); }); (0, mocha_1.it)("An app can have MAX 3 creators", async function () { const { x2EarnApps } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps.connect(creator1).submitApp(creator2.address, creator2.address, creator2.address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(creator2.address)); // App should be registered successfully (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // User should be listed as one of the apps creators (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([creator1.address]); // Adding another creator should fail (0, chai_1.expect)(await x2EarnApps.connect(creator2).addCreator(app1Id, creator2.address)); (0, chai_1.expect)(await x2EarnApps.connect(creator2).addCreator(app1Id, creator3.address)); await (0, chai_1.expect)(x2EarnApps.connect(creator2).addCreator(app1Id, creator4.address)).to.be.revertedWithCustomError(x2EarnApps, "X2EarnMaxCreatorsReached"); }); (0, mocha_1.it)("Same creator cannot be part of more than 1 app", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // App should be registered successfully (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // User should be listed as one of the apps creators (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([otherAccounts[0].address]); // App admin can add more creators for the app await x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[1].address); // New creator should be added to the app (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([otherAccounts[0].address, otherAccounts[1].address]); await x2EarnApps .connect(otherAccounts[2]) .submitApp(otherAccounts[3].address, otherAccounts[3].address, otherAccounts[3].address, "metadataURI2"); // Adding the creator of the appid2 to the appid1 should fail because he is already a creator of the appid1 await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[1].address)).to.be.revertedWithCustomError(x2EarnApps, "X2EarnAlreadyCreator"); }); (0, mocha_1.it)("Should mint a creator NFT for a creator that gets added after registration that doesnt currently hold one", async function () { const { x2EarnApps, otherAccounts, x2EarnCreator } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // Adding a new creator that doesnt hold a creator NFT await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[10].address)).to.emit(x2EarnCreator, "Transfer"); // New creator should be added to the app (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([otherAccounts[0].address, otherAccounts[10].address]); // New creator should have a creator NFT minted for them (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[10].address)).to.eql(1n); // Adding a new creator(otherAccounts[2]) that already holds a creator NFT should not mint a new one const balanceBefore = await x2EarnCreator.balanceOf(otherAccounts[2].address); // Adding the user with a creator NFT await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[2].address)).to.not.emit(x2EarnCreator, "Transfer"); // Balance of the user should remain the same (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[2].address)).to.eql(balanceBefore); }); (0, mocha_1.it)("Should burn a creator NFT for a creator that gets removed from an app and is not a creator for any other app", async function () { const { x2EarnApps, otherAccounts, x2EarnCreator } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // Adding a new creator await x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[1].address); // New creator should be added to the app (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([otherAccounts[0].address, otherAccounts[1].address]); // New creator should have a creator NFT minted for them (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(1n); // Removing the creator await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[2]).removeAppCreator(app1Id, otherAccounts[1].address)).to.emit(x2EarnCreator, "Transfer"); // New creator should have their creator NFT burned (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(0n); }); (0, mocha_1.it)("A creator should be part of only one app", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); // try to submit another app with the same creator will fail await (0, chai_1.expect)(x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[3].address, otherAccounts[3].address, otherAccounts[3].address, "metadataURI")).to.be.revertedWithCustomError(x2EarnApps, "CreatorNFTAlreadyUsed"); }); (0, mocha_1.it)("Should not be able to remove a creator that is not part of the app", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // Removing a creator that is not part of the app should fail await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[2]).removeAppCreator(app1Id, otherAccounts[3].address)).to.be.revertedWithCustomError(x2EarnApps, "X2EarnNonexistentCreator"); }); (0, mocha_1.it)("Should not be able to add a creator to an app that does not exist", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // Adding a creator to an app that does not exist should fail await (0, chai_1.expect)(x2EarnApps.addCreator(app1Id, otherAccounts[1].address)).to.be.revertedWithCustomError(x2EarnApps, "X2EarnNonexistentApp"); }); (0, mocha_1.it)("Should not be able to remove a creator from an app that does not exist", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // Removing a creator from an app that does not exist should fail await (0, chai_1.expect)(x2EarnApps.removeAppCreator(app1Id, otherAccounts[1].address)).to.be.revertedWithCustomError(x2EarnApps, "X2EarnNonexistentApp"); }); (0, mocha_1.it)("Should revoke all creator rights if their XApp is blacklisted", async function () { const { x2EarnApps, otherAccounts, x2EarnCreator } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); // Other Accounts 0 creates 1 apps -> Creator of the app1 await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // decide to change the creator of the app for another creator addressse await x2EarnApps.connect(otherAccounts[2]).removeAppCreator(app1Id, otherAccounts[0].address); await x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[1].address); // It gets endorsed await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[2]); // creator should have a creator NFT minted for them (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(1n); // Blacklisting the first app await x2EarnApps.setVotingEligibility(app1Id, false); // Blacklist the app // creator should have their creator NFT burned (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(0n); // Should still be considered creators of the app for info purposes (0, chai_1.expect)(await x2EarnApps.appCreators(app1Id)).to.deep.equal([otherAccounts[1].address]); }); (0, mocha_1.it)("Should regrant creator rights if their XApp is unblacklisted", async function () { const { x2EarnApps, otherAccounts, x2EarnCreator } = await (0, helpers_2.getOrDeployContractInstances)({ forceDeploy: true, }); // Other Accounts 0 creates 1 apps -> Creator of the app1 await x2EarnApps .connect(otherAccounts[0]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); // decide to change the creator of the app for another creator addressse await x2EarnApps.connect(otherAccounts[2]).removeAppCreator(app1Id, otherAccounts[0].address); // default creator should have their creator NFT burned (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[0].address)).to.eql(0n); await x2EarnApps.connect(otherAccounts[2]).addCreator(app1Id, otherAccounts[1].address); // Both apps get endorsed await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[2]); // Each account should have a creator NFT minted for them (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(1n); // Blacklisting the first app await x2EarnApps.setVotingEligibility(app1Id, false); // Blacklist the app // creator should have their creator NFT burned (0, chai_1.expect)(await x2EarnCreator.balanceOf(otherAccounts[1].address)).to.eql(0n); // Should all still be considered creators of the app for info purposes (0, chai_1.expect)(await x2EarnApps.appCreators(app