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