UNPKG

@vechain/vebetterdao-contracts

Version:

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

487 lines (486 loc) 33.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = require("chai"); const hardhat_1 = require("hardhat"); const mocha_1 = require("mocha"); const helpers_1 = require("../helpers"); const xnodes_1 = require("../helpers/xnodes"); (0, mocha_1.describe)("EndorsementUtils Coverage - @shard15g", function () { (0, mocha_1.describe)("Getter functions", function () { (0, mocha_1.it)("getEndorserNodes returns node IDs endorsing an app", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(3, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 10); const endorserNodes = await x2EarnApps.getEndorserNodes(app1Id); (0, chai_1.expect)(endorserNodes.length).to.equal(1); (0, chai_1.expect)(endorserNodes[0]).to.equal(nodeId1); }); (0, mocha_1.it)("maxPointsPerNodePerApp and maxPointsPerApp return configured values", async function () { const { x2EarnApps } = await (0, helpers_1.getOrDeployContractInstances)({}); (0, chai_1.expect)(await x2EarnApps.maxPointsPerNodePerApp()).to.equal(49); (0, chai_1.expect)(await x2EarnApps.maxPointsPerApp()).to.equal(110); }); (0, mocha_1.it)("endorsementsPaused returns false by default and true when paused", async function () { const { x2EarnApps, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.endorsementsPaused()).to.equal(false); await x2EarnApps.connect(owner).pauseEndorsements(); (0, chai_1.expect)(await x2EarnApps.endorsementsPaused()).to.equal(true); await x2EarnApps.connect(owner).unpauseEndorsements(); (0, chai_1.expect)(await x2EarnApps.endorsementsPaused()).to.equal(false); }); (0, mocha_1.it)("migrationCompleted returns correct state", async function () { const { x2EarnApps, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.migrationCompleted()).to.equal(false); await x2EarnApps.connect(owner).markMigrationComplete(); (0, chai_1.expect)(await x2EarnApps.migrationCompleted()).to.equal(true); }); (0, mocha_1.it)("getNodePointsForApp returns points for a specific endorsement", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(0); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 25); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(25); }); (0, mocha_1.it)("getNodeUsedPoints returns total points used across all apps", async function () { const { x2EarnApps, otherAccounts, owner, creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); const app2Id = await x2EarnApps.hashAppName(otherAccounts[2].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await x2EarnApps .connect(creators[1]) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app2Id, nodeId1, 20); (0, chai_1.expect)(await x2EarnApps.getNodeUsedPoints(nodeId1)).to.equal(50); }); (0, mocha_1.it)("getNodeAvailablePoints returns remaining points", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); (0, chai_1.expect)(await x2EarnApps.getNodeAvailablePoints(nodeId1)).to.equal(100); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 40); (0, chai_1.expect)(await x2EarnApps.getNodeAvailablePoints(nodeId1)).to.equal(60); }); (0, mocha_1.it)("getNodePointsInfo returns comprehensive info including locked points", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Set a non-zero cooldown (in rounds) so lockedPoints > 0 await x2EarnApps.connect(owner).updateCooldownPeriod(10); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); // Bootstrap emissions so round tracking works, then endorse await (0, helpers_1.startNewAllocationRound)(); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); const info = await x2EarnApps.getNodePointsInfo(nodeId1); (0, chai_1.expect)(info.totalPoints).to.equal(100); (0, chai_1.expect)(info.usedPoints).to.equal(30); (0, chai_1.expect)(info.availablePoints).to.equal(70); (0, chai_1.expect)(info.lockedPoints).to.equal(30); }); (0, mocha_1.it)("getNodePointsInfo lockedPoints is 0 after cooldown expires", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Cooldown is 0 in local config, so endorsement is not locked const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); const info = await x2EarnApps.getNodePointsInfo(nodeId1); (0, chai_1.expect)(info.totalPoints).to.equal(100); (0, chai_1.expect)(info.usedPoints).to.equal(30); (0, chai_1.expect)(info.availablePoints).to.equal(70); (0, chai_1.expect)(info.lockedPoints).to.equal(0); // Cooldown is 0 rounds }); (0, mocha_1.it)("canUnendorse returns false when not endorsing and true after cooldown", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Set cooldown to 1 round await x2EarnApps.connect(owner).updateCooldownPeriod(1); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); (0, chai_1.expect)(await x2EarnApps.canUnendorse(nodeId1, app1Id)).to.equal(false); // Bootstrap emissions and endorse in round 1 await (0, helpers_1.startNewAllocationRound)(); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 20); (0, chai_1.expect)(await x2EarnApps.canUnendorse(nodeId1, app1Id)).to.equal(false); // Advance to next round (round 2 >= endorsedAtRound(1) + cooldown(1)) await (0, helpers_1.waitForCurrentRoundToEnd)(); await (0, helpers_1.startNewAllocationRound)(); (0, chai_1.expect)(await x2EarnApps.canUnendorse(nodeId1, app1Id)).to.equal(true); }); (0, mocha_1.it)("getNodeActiveEndorsements returns all active endorsements for a node", async function () { const { x2EarnApps, otherAccounts, owner, creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); const app2Id = await x2EarnApps.hashAppName(otherAccounts[5].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await x2EarnApps .connect(creators[1]) .submitApp(otherAccounts[5].address, otherAccounts[5].address, otherAccounts[5].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app2Id, nodeId1, 20); const endorsements = await x2EarnApps.getNodeActiveEndorsements(nodeId1); (0, chai_1.expect)(endorsements.length).to.equal(2); (0, chai_1.expect)(endorsements[0].points).to.equal(30); (0, chai_1.expect)(endorsements[1].points).to.equal(20); }); }); (0, mocha_1.describe)("Pause, migration, and governance setters", function () { (0, mocha_1.it)("Pausing endorsements prevents endorsing and unpausing allows it", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(owner).pauseEndorsements(); await (0, chai_1.expect)(x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 10)).to.be.revertedWithCustomError(x2EarnApps, "EndorsementsPaused"); await x2EarnApps.connect(owner).unpauseEndorsements(); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 10); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(10); }); (0, mocha_1.it)("markMigrationComplete prevents further seeding", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const MIGRATION_ROLE = await x2EarnApps.MIGRATION_ROLE(); await x2EarnApps.connect(owner).grantRole(MIGRATION_ROLE, owner.address); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(owner).seedEndorsement(app1Id, nodeId1, 10); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(10); await x2EarnApps.connect(owner).markMigrationComplete(); await (0, chai_1.expect)(x2EarnApps.connect(owner).seedEndorsement(app1Id, nodeId1, 20)).to.be.reverted; }); (0, mocha_1.it)("Governance can update max points per node per app and max points per app", async function () { const { x2EarnApps, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps.connect(owner).setMaxPointsPerNodePerApp(60); (0, chai_1.expect)(await x2EarnApps.maxPointsPerNodePerApp()).to.equal(60); await x2EarnApps.connect(owner).setMaxPointsPerApp(200); (0, chai_1.expect)(await x2EarnApps.maxPointsPerApp()).to.equal(200); }); (0, mocha_1.it)("setMaxPointsPerApp reverts when below endorsementScoreThreshold", async function () { const { x2EarnApps, endorsementUtils, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.endorsementScoreThreshold()).to.equal(100); await (0, chai_1.expect)(x2EarnApps.connect(owner).setMaxPointsPerApp(99)).to.be.revertedWithCustomError(endorsementUtils, "MaxPointsPerAppBelowThreshold"); }); (0, mocha_1.it)("updateEndorsementScoreThreshold reverts when above maxPointsPerApp", async function () { const { x2EarnApps, endorsementUtils, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await x2EarnApps.maxPointsPerApp()).to.equal(110); await (0, chai_1.expect)(x2EarnApps.connect(owner).updateEndorsementScoreThreshold(111)).to.be.revertedWithCustomError(endorsementUtils, "ThresholdExceedsMaxPointsPerApp"); }); }); (0, mocha_1.describe)("Update existing endorsement (add more points)", function () { (0, mocha_1.it)("Adding points to existing endorsement updates points and resets cooldown", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 20); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(20); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 10); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(30); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(30); const endorserNodes = await x2EarnApps.getEndorserNodes(app1Id); (0, chai_1.expect)(endorserNodes.length).to.equal(1); }); }); (0, mocha_1.describe)("unendorseApp returns true when score stays above threshold", function () { (0, mocha_1.it)("Partial unendorse keeps app eligible when score remains >= threshold", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); const nodeId3 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[3]); 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, 12); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(110); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.equal(true); // Cooldown is 0 in local config, no need to wait await x2EarnApps.connect(otherAccounts[3]).unendorseApp(app1Id, nodeId3, 5); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(105); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.equal(true); }); }); (0, mocha_1.describe)("removeNodeEndorsement returns true when score stays above threshold", function () { (0, mocha_1.it)("Admin removes endorser, app stays eligible when score still >= threshold", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); const nodeId3 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[3]); const nodeId4 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[4]); 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, 10); await x2EarnApps.connect(otherAccounts[4]).endorseApp(app1Id, nodeId4, 2); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(110); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.equal(true); await x2EarnApps.connect(owner).removeNodeEndorsement(app1Id, nodeId4); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(108); (0, chai_1.expect)(await x2EarnApps.isEligibleNow(app1Id)).to.equal(true); }); }); (0, mocha_1.describe)("seedEndorsement full coverage", function () { (0, mocha_1.it)("Seeds new endorsement, updates existing, and emits events", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const MIGRATION_ROLE = await x2EarnApps.MIGRATION_ROLE(); await x2EarnApps.connect(owner).grantRole(MIGRATION_ROLE, owner.address); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); // Seed first endorsement (new entry branch L844-855) await x2EarnApps.connect(owner).seedEndorsement(app1Id, nodeId1, 30); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(30); (0, chai_1.expect)((await x2EarnApps.getEndorserNodes(app1Id)).length).to.equal(1); // Seed second endorsement from different node await x2EarnApps.connect(owner).seedEndorsement(app1Id, nodeId2, 20); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(50); (0, chai_1.expect)((await x2EarnApps.getEndorserNodes(app1Id)).length).to.equal(2); // Update first node's endorsement (existing entry branch L836-843) await x2EarnApps.connect(owner).seedEndorsement(app1Id, nodeId1, 40); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(60); // 50 - 30 + 40 (0, chai_1.expect)((await x2EarnApps.getEndorserNodes(app1Id)).length).to.equal(2); // Verify endorser addresses (L858-860) const endorsers = await x2EarnApps.getEndorsers(app1Id); (0, chai_1.expect)(endorsers.length).to.equal(2); }); }); (0, mocha_1.describe)("getScoreAtTimepoint", function () { (0, mocha_1.it)("Returns the endorsement score at the start and end of an allocation round", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); // Endorse before any round starts await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(30); // Start round 1 — snapshot is taken at this block const roundId = await (0, helpers_1.startNewAllocationRound)(); const roundSnapshot = await xAllocationVoting.roundSnapshot(roundId); // Score at round start should be 30 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, roundSnapshot)).to.equal(30); // Endorse more during the round await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 20); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(50); // End the round await (0, helpers_1.waitForCurrentRoundToEnd)(); const roundDeadline = await xAllocationVoting.roundDeadline(roundId); // Score at round end should be 50 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, roundDeadline)).to.equal(50); // Score at round start should still be 30 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, roundSnapshot)).to.equal(30); }); (0, mocha_1.it)("Returns 0 for timepoints before any endorsement", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const blockBefore = await hardhat_1.ethers.provider.getBlockNumber(); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 10); (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, blockBefore)).to.equal(0); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(10); }); (0, mocha_1.it)("Reverts when querying a future timepoint", async function () { const { x2EarnApps, endorsementUtils, otherAccounts, owner } = await (0, helpers_1.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"); const futureBlock = (await hardhat_1.ethers.provider.getBlockNumber()) + 1000; await (0, chai_1.expect)(x2EarnApps.getScoreAtTimepoint(app1Id, futureBlock)).to.be.revertedWithCustomError(endorsementUtils, "FutureLookup"); }); (0, mocha_1.it)("Tracks score changes across multiple rounds", async function () { const { x2EarnApps, xAllocationVoting, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); const nodeId3 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[3]); // Round 1: endorse with 49 points await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); const round1Id = await (0, helpers_1.startNewAllocationRound)(); const round1Snapshot = await xAllocationVoting.roundSnapshot(round1Id); (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, round1Snapshot)).to.equal(49); // Add more endorsements during round 1 await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 49); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 2); // End round 1, start round 2 await (0, helpers_1.waitForCurrentRoundToEnd)(); const round2Id = await (0, helpers_1.startNewAllocationRound)(); const round2Snapshot = await xAllocationVoting.roundSnapshot(round2Id); // At round 1 start: 49, at round 2 start: 100 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, round1Snapshot)).to.equal(49); (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, round2Snapshot)).to.equal(100); // Unendorse during round 2 await x2EarnApps.connect(otherAccounts[3]).unendorseApp(app1Id, nodeId3, 2); await (0, helpers_1.waitForCurrentRoundToEnd)(); const round2Deadline = await xAllocationVoting.roundDeadline(round2Id); // At round 2 end: 98 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, round2Deadline)).to.equal(98); // Round 1 start still 49 (0, chai_1.expect)(await x2EarnApps.getScoreAtTimepoint(app1Id, round1Snapshot)).to.equal(49); }); }); (0, mocha_1.describe)("_removeEndorsement swap-and-pop branches", function () { (0, mocha_1.it)("Removing non-last endorsement triggers swap in node's endorsements list", async function () { const { x2EarnApps, otherAccounts, owner, creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const app1Id = await x2EarnApps.hashAppName(otherAccounts[0].address); const app2Id = await x2EarnApps.hashAppName(otherAccounts[5].address); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); await x2EarnApps .connect(creators[1]) .submitApp(otherAccounts[5].address, otherAccounts[5].address, otherAccounts[5].address, "metadataURI"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); // Endorse two apps: app1 first (index 0), app2 second (index 1) await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 30); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app2Id, nodeId1, 30); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(30); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app2Id)).to.equal(30); // Cooldown is 0 in local config, no need to wait // Unendorse app1 (index 0) - triggers swap: app2 moves to index 0 (L899-902) await x2EarnApps.connect(otherAccounts[1]).unendorseApp(app1Id, nodeId1, 0); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app2Id)).to.equal(30); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId1, app1Id)).to.equal(0); const activeEndorsements = await x2EarnApps.getNodeActiveEndorsements(nodeId1); (0, chai_1.expect)(activeEndorsements.length).to.equal(1); }); (0, mocha_1.it)("Removing non-first endorser from app triggers node swap in endorser list", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); const nodeId1 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[1]); const nodeId2 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[2]); const nodeId3 = await (0, xnodes_1.createNodeHolder)(7, otherAccounts[3]); await x2EarnApps.connect(otherAccounts[1]).endorseApp(app1Id, nodeId1, 49); await x2EarnApps.connect(otherAccounts[2]).endorseApp(app1Id, nodeId2, 10); await x2EarnApps.connect(otherAccounts[3]).endorseApp(app1Id, nodeId3, 49); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(108); // Admin removes node1 (first, index 0) -> node3 swaps into position (L913-916) await x2EarnApps.connect(owner).removeNodeEndorsement(app1Id, nodeId1); (0, chai_1.expect)(await x2EarnApps.getScore(app1Id)).to.equal(59); const endorserNodes = await x2EarnApps.getEndorserNodes(app1Id); (0, chai_1.expect)(endorserNodes.length).to.equal(2); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId2, app1Id)).to.equal(10); (0, chai_1.expect)(await x2EarnApps.getNodePointsForApp(nodeId3, app1Id)).to.equal(49); }); }); (0, mocha_1.describe)("selfBlacklistApp removes unendorsed app from pending list", function () { (0, mocha_1.it)("Self-blacklisting an unendorsed app removes it from unendorsed list", async function () { const { x2EarnApps, otherAccounts, owner } = await (0, helpers_1.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"); // App should be in unendorsed list after submission (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.equal(true); const unendorsedBefore = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(unendorsedBefore).to.include(app1Id); // Self-blacklist without endorsing first await x2EarnApps.connect(otherAccounts[0]).selfBlacklistApp(app1Id); // App should no longer be in unendorsed list (0, chai_1.expect)(await x2EarnApps.isAppUnendorsed(app1Id)).to.equal(false); const unendorsedAfter = await x2EarnApps.unendorsedAppIds(); (0, chai_1.expect)(unendorsedAfter).to.not.include(app1Id); // App should be blacklisted (0, chai_1.expect)(await x2EarnApps.isBlacklisted(app1Id)).to.equal(true); }); }); });