@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
JavaScript
"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