@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
JavaScript
"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);
});
});
});