UNPKG

@vechain/vebetterdao-contracts

Version:

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

685 lines 169 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hardhat_network_helpers_1 = require("@nomicfoundation/hardhat-network-helpers"); const local_1 = require("@repo/config/contracts/envs/local"); const sdk_core_1 = require("@vechain/sdk-core"); const chai_1 = require("chai"); const hardhat_1 = require("hardhat"); const mocha_1 = require("mocha"); const airdrop_1 = require("../../scripts/helpers/airdrop"); const seedAccounts_1 = require("../../scripts/helpers/seedAccounts"); const helpers_1 = require("../helpers"); const xnodes_1 = require("../helpers/xnodes"); (0, mocha_1.describe)("X-Apps - Metadata and Endorsement - @shard15c", function () { // We prepare the environment for 4 creators let creator1; let creator2; (0, mocha_1.before)(async function () { const { creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); creator1 = creators[1]; creator2 = creators[2]; }); (0, mocha_1.describe)("Apps metadata", function () { (0, mocha_1.it)("Admin should be able to update baseURI", async function () { const { x2EarnApps, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const newBaseURI = "ipfs://new-base-uri"; await x2EarnApps.connect(owner).setBaseURI(newBaseURI); (0, chai_1.expect)(await x2EarnApps.baseURI()).to.eql(newBaseURI); }); (0, mocha_1.it)("Non-admin should not be able to update baseURI", async function () { const { x2EarnApps, otherAccounts } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await (0, helpers_1.catchRevert)(x2EarnApps.connect(otherAccounts[0]).setBaseURI("ipfs://new-base-uri")); }); (0, mocha_1.it)("Should be able to fetch app metadata", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const baseURI = await x2EarnApps.baseURI(); const appURI = await x2EarnApps.appURI(app1Id); (0, chai_1.expect)(appURI).to.eql(baseURI + "metadataURI"); }); (0, mocha_1.it)("Admin role can update app metadata", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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 newMetadataURI = "metadataURI2"; await x2EarnApps.connect(owner).updateAppMetadata(app1Id, newMetadataURI); const appURI = await x2EarnApps.appURI(app1Id); (0, chai_1.expect)(appURI).to.eql((await x2EarnApps.baseURI()) + newMetadataURI); }); (0, mocha_1.it)("Admin of app can update app metadata", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); const newMetadataURI = "metadataURI2"; await x2EarnApps.connect(appAdmin).updateAppMetadata(app1Id, newMetadataURI); const appURI = await x2EarnApps.appURI(app1Id); (0, chai_1.expect)(appURI).to.eql((await x2EarnApps.baseURI()) + newMetadataURI); }); (0, mocha_1.it)("Moderator can update app metadata", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; const appModerator = otherAccounts[10]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); await x2EarnApps.connect(appAdmin).addAppModerator(app1Id, appModerator.address); (0, chai_1.expect)(await x2EarnApps.isAppModerator(app1Id, appModerator.address)).to.be.true; const newMetadataURI = "metadataURI2"; await x2EarnApps.connect(appModerator).updateAppMetadata(app1Id, newMetadataURI); const appURI = await x2EarnApps.appURI(app1Id); (0, chai_1.expect)(appURI).to.eql((await x2EarnApps.baseURI()) + newMetadataURI); }); (0, mocha_1.it)("Unatuhtorized users cannot update app metadata", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; const unauthorizedUser = otherAccounts[8]; const oldMetadataURI = "metadataURI"; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", oldMetadataURI); const newMetadataURI = "metadataURI2"; await (0, chai_1.expect)(x2EarnApps.connect(unauthorizedUser).updateAppMetadata(app1Id, newMetadataURI)).to.be.reverted; const appURI = await x2EarnApps.appURI(app1Id); (0, chai_1.expect)(appURI).to.eql((await x2EarnApps.baseURI()) + oldMetadataURI); }); (0, mocha_1.it)("Cannot update metadata of non existing app", async function () { const { x2EarnApps, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const newMetadataURI = "metadataURI2"; await (0, chai_1.expect)(x2EarnApps.connect(owner).updateAppMetadata(app1Id, newMetadataURI)).to.be.reverted; }); (0, mocha_1.it)("Cannot get app uri of non existing app", async function () { const { x2EarnApps } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); await (0, chai_1.expect)(x2EarnApps.appURI(app1Id)).to.be.reverted; }); }); (0, mocha_1.describe)("XApp Endorsement", function () { (0, mocha_1.it)("If an XAPP is endorsed with a score of 100 they should be eligble for XAllocation Voting", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pedning endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Node strength level 3 corresponds (Mjolnir) to an endorsement score of 50 const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Node strength level 3 corresponds (Mjolnir) to an endorsement score of 50 const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Need a third endorser to reach 100 points // Endorse XAPP with Mjolnir node holders - each can contribute max 49 points (0, chai_1.expect)(await x2EarnApps.getNodeEndorsementScore(nodeId1)).to.eql(50n); // Node ID 1 has an endorsement score is 50 await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); // Endorse with node holder 1 (capped at 49) (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.eql(49n); // XAPP endorsement score is 49 (capped) (0, chai_1.expect)(await x2EarnApps.getNodeEndorsementScore(nodeId2)).to.eql(50n); // Node Id 2 has an endorsement score is 50 await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); // Endorse with node holder 2 (capped at 49) (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.eql(98n); // XAPP endorsement score is 98 // Add third endorser to reach 100+ points threshold await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); // Add 2 more points (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.eql(100n); // XAPP endorsement score is now 100 const appIdsPendingEndorsement2 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement2.length).to.eql(0); (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(true); }); (0, mocha_1.it)("If an XAPP is endorsed with a score less than 100 they should not be eligble for XAllocation Voting", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pedning endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create one Mjolnir node holder with an endorsement score of 50 const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Node strength level 3 corresponds (Mjolnir) to an endorsement score of 50 // Endorse XAPP with Mjolnir node holder -> XAPP endorsement score is 49 (capped) -> XAPP is not eligible for XAllocation Voting await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); // Endorsing with 49 points (max per node per app) (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.eql(49n); // XAPP endorsement score is 49 (capped) const appIdsPendingEndorsement2 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement2.length).to.eql(1); (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(false); }); (0, mocha_1.it)("If an XAPP has a score of 100 and is unendorsed by a node holder and their score falls below a 100 they will enter a grace period", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); // Node ID 1 has endorsed app1Id - check via getNodeActiveEndorsements let endorsements1 = await x2EarnApps.getNodeActiveEndorsements(nodeId1); (0, chai_1.expect)(endorsements1.length).to.eql(1); (0, chai_1.expect)(endorsements1[0].appId).to.eql(app1Id); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // remove endorsement from one of the node holders (removes 49 points, leaves 51) const tx = await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); // Node ID 1 should no longer have endorsed app1Id endorsements1 = await x2EarnApps.getNodeActiveEndorsements(nodeId1); (0, chai_1.expect)(endorsements1.length).to.eql(0); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let events = receipt?.logs; let decodedEvents = events?.map(event => { return x2EarnApps.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "AppUnendorsed"); (0, chai_1.expect)(event).to.not.equal(undefined); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 51 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("If an XAPP has a score of 100 and its app admin removes endorsement by a node holder and their score falls below a 100 they will enter a grace period", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // App admin removes endorsement from one of the node holders (removes 49 points, leaves 51) const tx = await x2EarnApps.connect(otherAccounts[0]).removeNodeEndorsement(app1Id, nodeId1); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let events = receipt?.logs; let decodedEvents = events?.map(event => { return x2EarnApps.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "AppUnendorsed"); (0, chai_1.expect)(event).to.not.equal(undefined); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 51 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("If an XAPP is in the grace period for longer than 2 cycles and has not got reendorsed they are removed from voting rounds", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pedning endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // No apps pending endorsement (0, chai_1.expect)((await x2EarnApps.unendorsedAppIds()).length).to.eql(0); // No apps pending endorsement (0, chai_1.expect)((await x2EarnApps.unendorsedApps()).length).to.eql(0); // remove endorsement from one of the node holders (removes 49 points, leaves 51) await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); // App is pending endorsent (0, chai_1.expect)((await x2EarnApps.unendorsedApps()).length).to.eql(1); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 50 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 1st cycle unedorsed let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 2nd cycle unendorsed let round3 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round3); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement this time it will remove the app from the voting rounds await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 3rd cycle unendorsed let round4 = await (0, helpers_1.startNewAllocationRound)(); // app should not be eligible for the current round as it is not in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round4); (0, chai_1.expect)(isEligibleForVote).to.eql(false); // App is still pending endorsent (0, chai_1.expect)((await x2EarnApps.unendorsedApps()).length).to.eql(1); }); (0, mocha_1.it)("If an XAPP is in the grace period for longer than 2 cycles and has not got reendorsed they are removed from voting rounds and any endorser can remove their endorsement", async function () { const config = (0, local_1.createLocalConfig)(); const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // remove endorsement from one of the node holders (removes 49 points, leaves 51) const tx = await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); // check event emitted let events = receipt?.logs; let decodedEvents = events?.map(event => { return x2EarnApps.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "AppEndorsementStatusUpdated"); (0, chai_1.expect)(event).to.not.equal(undefined); const eventGracePeriod = decodedEvents.find(event => event?.name === "AppUnendorsedGracePeriodStarted"); (0, chai_1.expect)(eventGracePeriod).to.not.equal(undefined); (0, chai_1.expect)(eventGracePeriod?.args[0]).to.eql(app1Id); (0, chai_1.expect)(eventGracePeriod?.args[1]).to.eql(BigInt(receipt.blockNumber)); (0, chai_1.expect)(eventGracePeriod?.args[2]).to.eql(BigInt(receipt.blockNumber + config.XAPP_GRACE_PERIOD)); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 50 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 1st cycle unedorsed let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 2nd cycle unendorsed let round3 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round3); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // remove endorsement from one of the node holders -> grace period is passed -> app is removed from voting rounds await x2EarnApps.connect(otherAccounts[2]).unendorseApp(app1Id, nodeId2, 0); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 3rd cycle unendorsed let round4 = await (0, helpers_1.startNewAllocationRound)(); // app should not be eligible for the current round as it is not in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round4); (0, chai_1.expect)(isEligibleForVote).to.eql(false); }); (0, mocha_1.it)("If an XAPP is in the grace period for longer than 2 cycles and has not got reendorsed they are removed from voting rounds and app admin can rmeove endorsements", async function () { const config = (0, local_1.createLocalConfig)(); const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // remove endorsement from one of the node holders (removes 49 points, leaves 51) const tx = await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); // check event emitted let events = receipt?.logs; let decodedEvents = events?.map(event => { return x2EarnApps.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "AppEndorsementStatusUpdated"); (0, chai_1.expect)(event).to.not.equal(undefined); const eventGracePeriod = decodedEvents.find(event => event?.name === "AppUnendorsedGracePeriodStarted"); (0, chai_1.expect)(eventGracePeriod).to.not.equal(undefined); (0, chai_1.expect)(eventGracePeriod?.args[0]).to.eql(app1Id); (0, chai_1.expect)(eventGracePeriod?.args[1]).to.eql(BigInt(receipt.blockNumber)); (0, chai_1.expect)(eventGracePeriod?.args[2]).to.eql(BigInt(receipt.blockNumber + config.XAPP_GRACE_PERIOD)); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 50 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 1st cycle unedorsed let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 2nd cycle unendorsed let round3 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round3); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app admin removes endorsement from the remaining node holders await x2EarnApps.connect(otherAccounts[0]).removeNodeEndorsement(app1Id, nodeId2); await x2EarnApps.connect(otherAccounts[0]).removeNodeEndorsement(app1Id, nodeId3); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 3rd cycle unendorsed let round4 = await (0, helpers_1.startNewAllocationRound)(); // app should not be eligible for the current round as it is not in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round4); (0, chai_1.expect)(isEligibleForVote).to.eql(false); const endorsers = await x2EarnApps.getEndorsers(app1Id); (0, chai_1.expect)(endorsers.length).to.eql(0); }); (0, mocha_1.it)("If the grace period is updated it should should update the grace period for apps that are already in the grace period", async function () { const config = (0, local_1.createLocalConfig)(); const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // remove endorsement from one of the node holders (removes 49 points, leaves 51) const tx = await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); // check event emitted let events = receipt?.logs; let decodedEvents = events?.map(event => { return x2EarnApps.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "AppEndorsementStatusUpdated"); (0, chai_1.expect)(event).to.not.equal(undefined); const eventGracePeriod = decodedEvents.find(event => event?.name === "AppUnendorsedGracePeriodStarted"); (0, chai_1.expect)(eventGracePeriod).to.not.equal(undefined); (0, chai_1.expect)(eventGracePeriod?.args[0]).to.eql(app1Id); (0, chai_1.expect)(eventGracePeriod?.args[1]).to.eql(BigInt(receipt.blockNumber)); (0, chai_1.expect)(eventGracePeriod?.args[2]).to.eql(BigInt(receipt.blockNumber + config.XAPP_GRACE_PERIOD)); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 50 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 1st cycle unedorsed let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is eligible as in grace period (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(true); // If we update the grace period now to 1 block the app should not be eligible if we check endorsement await x2EarnApps.updateGracePeriod(1); // check endorsement this time it will remove the app from the voting rounds await x2EarnApps.checkEndorsement(app1Id); // app should still be eligible for the current round as it was not removed before the round started isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is no longer eligible (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(false); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 2nd cycle unendorsed let round3 = await (0, helpers_1.startNewAllocationRound)(); // app id not eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round3); (0, chai_1.expect)(isEligibleForVote).to.eql(false); // Updating grace period now has no effect as the app is no longer eligible await x2EarnApps.updateGracePeriod(500000); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // app should not be eligible now (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.eql(false); }); (0, mocha_1.it)("If an XAPP is no longer in eligible for voting as they lost their endorsement they can get added in by getting reendorsed", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[3]); // Mjolnir - 50 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round (100 points) let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // remove endorsement from one of the node holders (removes 49 points, leaves 51) await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); // app should still be eligible for the current round isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // app should be pending endorsement -> score is now 50 -> grace period starts (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // App is pending endorsent (0, chai_1.expect)((await x2EarnApps.unendorsedApps()).length).to.eql(1); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 1st cycle unedorsed let round2 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round2); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 2nd cycle unendorsed let round3 = await (0, helpers_1.startNewAllocationRound)(); // app should still be eligible for the current round as it is in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round3); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // check endorsement this time it will remove the app from the voting rounds await x2EarnApps.checkEndorsement(app1Id); // check endorsement await x2EarnApps.checkEndorsement(app1Id); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 3rd cycle unendorsed let round4 = await (0, helpers_1.startNewAllocationRound)(); // app should not be eligible for the current round as it is not in the grace period isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round4); (0, chai_1.expect)(isEligibleForVote).to.eql(false); // App is pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(true); // reendorse the app await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); // App is not pending endorsement (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.eql(false); // App is not pending endorsent (0, chai_1.expect)((await x2EarnApps.unendorsedApps()).length).to.eql(0); // wait for round to end await (0, helpers_1.waitForCurrentRoundToEnd)(); // start new round -> 4th cycle reendorsed let round5 = await (0, helpers_1.startNewAllocationRound)(); isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round5); (0, chai_1.expect)(isEligibleForVote).to.eql(true); }); (0, mocha_1.it)("If an XNode endorser transfers its XNode XApp will not enter grace period, they will remain endorsed", async function () { const config = (0, local_1.createLocalConfig)(); config.X2EARN_NODE_COOLDOWN_PERIOD = 1; const { x2EarnApps, xAllocationVoting, otherAccounts, owner, stargateNftMock } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, config, }); (0, chai_1.expect)(await x2EarnApps.hasRole(await x2EarnApps.GOVERNANCE_ROLE(), owner.address)).to.eql(true); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); // Register XAPP -> XAPP is pending endorsement await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const appIdsPendingEndorsement1 = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(appIdsPendingEndorsement1.length).to.eql(1); // Create three nodes - need 3 to reach 100 points (49+49+2) const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); // Mjolnir - 50 points const nodeId2 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[2]); // Mjolnir - 50 points const nodeId3 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[4]); // Mjolnir - 50 points // Skip ahead by a round so node is no longer in cooldown await (0, helpers_1.startNewAllocationRound)(); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[4]).endorseApp(app1Id, nodeId3, 2); await (0, helpers_1.waitForCurrentRoundToEnd)(); // Skip ahead by a round so node is no longer in cooldown let round1 = await (0, helpers_1.startNewAllocationRound)(); // app should be eligible for the current round let isEligibleForVote = await xAllocationVoting.isEligibleForVote(app1Id, round1); (0, chai_1.expect)(isEligibleForVote).to.eql(true); // Skip ahead 1 day to be able to transfer node await hardhat_network_helpers_1.time.setNextBlockTimestamp((await hardhat_network_helpers_1.time.latest()) + 86400); // XNode holder transfers its XNode const tokenId = (await stargateNftMock.idsOwnedBy(otherAccounts[1]))[0]; await stargateNftMock.connect(otherAccounts[1]).transferFrom(otherAccounts[1], otherAccounts[3], tokenId); const tokenId1 = (await s