UNPKG

@vechain/vebetterdao-contracts

Version:

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

639 lines (638 loc) 45.7 kB
import { expect } from "chai"; import { ethers } from "hardhat"; import { describe, it } from "mocha"; import { bootstrapAndStartEmissions, catchRevert, getOrDeployContractInstances, getVot3Tokens, startNewAllocationRound, waitForCurrentRoundToEnd, ZERO_ADDRESS, } from "../helpers"; import { endorseApp } from "../helpers/xnodes"; describe("X-Apps - Team Management - @shard15b", function () { describe("Admin address", function () { it("Admin can update the admin address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const admin = await x2EarnApps.appAdmin(app1Id); expect(admin).to.eql(otherAccounts[0].address); await x2EarnApps.connect(owner).setAppAdmin(app1Id, otherAccounts[1].address); const updatedAdmin = await x2EarnApps.appAdmin(app1Id); expect(updatedAdmin).to.eql(otherAccounts[1].address); expect(updatedAdmin).to.not.eql(admin); }); it("Cannot update the admin address of a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const newAdminAddress = ethers.Wallet.createRandom().address; await expect(x2EarnApps.connect(owner).setAppAdmin(app1Id, newAdminAddress)).to.be.reverted; }); it("Cannot set the admin address of an app to ZERO address", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await catchRevert(x2EarnApps.connect(otherAccounts[0]).setAppAdmin(app1Id, ZERO_ADDRESS)); }); it("User with DEFAULT_ADMIN_ROLE can update the admin address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const admin = await x2EarnApps.appAdmin(app1Id); expect(admin).to.eql(otherAccounts[0].address); await x2EarnApps.connect(otherAccounts[0]).setAppAdmin(app1Id, otherAccounts[1].address); const updatedAdmin = await x2EarnApps.appAdmin(app1Id); expect(updatedAdmin).to.eql(otherAccounts[1].address); expect(updatedAdmin).to.not.eql(admin); }); it("Non admins cannot update the admin address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); // check that is not admin expect(await x2EarnApps.isAppAdmin(app1Id, otherAccounts[1].address)).to.eql(false); await catchRevert(x2EarnApps.connect(otherAccounts[1]).setAppAdmin(app1Id, otherAccounts[1].address)); // user without DEFAULT_ADMIN_ROLE expect(await x2EarnApps.hasRole(await x2EarnApps.DEFAULT_ADMIN_ROLE(), otherAccounts[0].address)).to.eql(false); await catchRevert(x2EarnApps.connect(otherAccounts[1]).setAppAdmin(app1Id, otherAccounts[2].address)); }); }); describe("Team wallet address", function () { it("Should be able to fetch app team wallet address", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const teamWalletAddress = await x2EarnApps.teamWalletAddress(app1Id); expect(teamWalletAddress).to.eql(otherAccounts[0].address); }); it("Governance admin role can update the team wallet address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const appReceiverAddress1 = await x2EarnApps.teamWalletAddress(app1Id); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; await x2EarnApps.connect(owner).updateTeamWalletAddress(app1Id, otherAccounts[1].address); const appReceiverAddress2 = await x2EarnApps.teamWalletAddress(app1Id); expect(appReceiverAddress2).to.eql(otherAccounts[1].address); expect(appReceiverAddress1).to.not.eql(appReceiverAddress2); }); it("App admin can update the team wallet address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); const appReceiverAddress1 = await x2EarnApps.teamWalletAddress(app1Id); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, appAdmin.address); expect(isAdmin).to.be.false; expect(await x2EarnApps.isAppAdmin(app1Id, appAdmin.address)).to.be.true; await x2EarnApps.connect(appAdmin).updateTeamWalletAddress(app1Id, otherAccounts[1].address); const appReceiverAddress2 = await x2EarnApps.teamWalletAddress(app1Id); expect(appReceiverAddress2).to.eql(otherAccounts[1].address); expect(appReceiverAddress1).to.not.eql(appReceiverAddress2); }); it("Moderators cannot update the team wallet address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const appReceiverAddress1 = await x2EarnApps.teamWalletAddress(app1Id); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); const isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; await expect(x2EarnApps.connect(otherAccounts[1]).updateTeamWalletAddress(app1Id, otherAccounts[1].address)).to.be .reverted; const appReceiverAddress2 = await x2EarnApps.teamWalletAddress(app1Id); expect(appReceiverAddress2).to.eql(appReceiverAddress1); }); it("Moderators cannot update the team wallet address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const appReceiverAddress1 = await x2EarnApps.teamWalletAddress(app1Id); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); const isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; await expect(x2EarnApps.connect(otherAccounts[1]).updateTeamWalletAddress(app1Id, otherAccounts[1].address)).to.be .reverted; const appReceiverAddress2 = await x2EarnApps.teamWalletAddress(app1Id); expect(appReceiverAddress2).to.eql(appReceiverAddress1); }); it("Non-admin cannot update the team wallet address of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const appReceiverAddress1 = await x2EarnApps.teamWalletAddress(app1Id); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, otherAccounts[1].address); expect(isAdmin).to.be.false; await expect(x2EarnApps.connect(otherAccounts[1]).updateTeamWalletAddress(app1Id, otherAccounts[1].address)).to.be .reverted; const appReceiverAddress2 = await x2EarnApps.teamWalletAddress(app1Id); expect(appReceiverAddress2).to.eql(appReceiverAddress1); }); it("Cannot update the team wallet address of a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const newTeamWalletAddress = ethers.Wallet.createRandom().address; await expect(x2EarnApps.connect(owner).updateTeamWalletAddress(app1Id, newTeamWalletAddress)).to.be.reverted; }); it("Team wallet address cannot be updated to ZERO address", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await catchRevert(x2EarnApps.connect(otherAccounts[0]).updateTeamWalletAddress(app1Id, ZERO_ADDRESS)); }); }); describe("App Moderators", function () { it("By default there is no moderator for an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[0].address); expect(isModerator).to.be.false; const moderators = await x2EarnApps.appModerators(app1Id); expect(moderators).to.eql([]); }); it("DEFAULT_ADMIN_ROLE can add a moderator to an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); const isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; }); it("DEFAULT_ADMIN_ROLE can remove a moderator from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; let isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; await x2EarnApps.connect(owner).removeAppModerator(app1Id, otherAccounts[1].address); isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.false; }); it("App admin can add a moderator to an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, appAdmin.address); expect(isAdmin).to.be.false; expect(await x2EarnApps.isAppAdmin(app1Id, appAdmin.address)).to.be.true; await x2EarnApps.connect(appAdmin).addAppModerator(app1Id, otherAccounts[1].address); const isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; }); it("App admin can remove a moderator from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); await x2EarnApps.connect(appAdmin).addAppModerator(app1Id, otherAccounts[1].address); await x2EarnApps.connect(appAdmin).addAppModerator(app1Id, otherAccounts[2].address); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, appAdmin.address); expect(isAdmin).to.be.false; expect(await x2EarnApps.isAppAdmin(app1Id, appAdmin.address)).to.be.true; let isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; await x2EarnApps.connect(appAdmin).removeAppModerator(app1Id, otherAccounts[2].address); isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[2].address); expect(isModerator).to.be.false; expect(await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address)).to.be.true; }); it("Can correctly fetch all moderators of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[2].address); const moderators = await x2EarnApps.appModerators(app1Id); expect(moderators).to.eql([otherAccounts[1].address, otherAccounts[2].address]); }); it("Can know if an address is a moderator of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); let isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[2].address); expect(isModerator).to.be.false; }); it("Cannot add a moderator to a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await expect(x2EarnApps.connect(owner).addAppModerator(app1Id, owner.address)).to.be.reverted; }); it("Cannot remove a moderator from a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await expect(x2EarnApps.connect(owner).removeAppModerator(app1Id, owner.address)).to.be.reverted; }); it("Cannot add ZERO_ADDRESS as a moderator of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(otherAccounts[0]).addAppModerator(app1Id, ZERO_ADDRESS)).to.be.reverted; }); it("Cannot remove ZERO_ADDRESS as a moderator of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(otherAccounts[0]).removeAppModerator(app1Id, ZERO_ADDRESS)).to.be.reverted; }); it("Non admin or user without DEFAULT_ADMIN_ROLE cannot add a moderator to an app", async function () { const { x2EarnApps, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await expect(x2EarnApps.connect(otherAccounts[0]).addAppModerator(app1Id, otherAccounts[0].address)).to.be .reverted; }); it("Non admin or user without DEFAULT_ADMIN_ROLE cannot remove a moderator from an app", async function () { const { x2EarnApps, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await expect(x2EarnApps.connect(otherAccounts[0]).removeAppModerator(app1Id, otherAccounts[0].address)).to.be .reverted; }); it("Removing a moderator from an app does not affect other moderators of the app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[1].address); await x2EarnApps.connect(owner).addAppModerator(app1Id, otherAccounts[2].address); let isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.true; isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[2].address); expect(isModerator).to.be.true; await x2EarnApps.connect(owner).removeAppModerator(app1Id, otherAccounts[1].address); isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[1].address); expect(isModerator).to.be.false; isModerator = await x2EarnApps.isAppModerator(app1Id, otherAccounts[2].address); expect(isModerator).to.be.true; }); it("An error is thrown when trying to remove a non existing moderator from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(owner).removeAppModerator(app1Id, otherAccounts[1].address)).to.be.reverted; }); it("Cannot remove a moderator with ZERO_ADDRESS from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(owner).removeAppModerator(app1Id, ZERO_ADDRESS)).to.be.reverted; }); it("Cannot remove moderator of non existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); await expect(x2EarnApps.connect(owner).removeAppModerator(app1Id, owner.address)).to.be.reverted; }); it("Cannot have exceed the maximum number of moderators for an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = ethers.keccak256(ethers.toUtf8Bytes("My app")); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); const limit = await x2EarnApps.MAX_MODERATORS(); const addModeratorPromises = []; for (let i = 1; i <= limit; i++) { const randomWallet = ethers.Wallet.createRandom(); addModeratorPromises.push(x2EarnApps.connect(appAdmin).addAppModerator(app1Id, randomWallet.address)); } // Wait for all addAppModerator transactions to complete await Promise.all(addModeratorPromises); await expect(x2EarnApps.connect(appAdmin).addAppModerator(app1Id, otherAccounts[10].address)).to.be.reverted; // check that having 100 moderators do not affect the app const moderators = await x2EarnApps.appModerators(app1Id); expect(moderators.length).to.eql(100); // check that the last moderator is not the one that failed expect(moderators[99]).to.not.eql(otherAccounts[10].address); expect(await x2EarnApps.isAppModerator(app1Id, otherAccounts[10].address)).to.be.false; }); }); describe("Reward distributors", function () { it("Admin can add a reward distributor to an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[1].address); const isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.true; }); it("Admin can remove a reward distributor from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[1].address); let isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.true; await x2EarnApps.connect(owner).removeRewardDistributor(app1Id, otherAccounts[1].address); isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.false; }); it("Cannot add a reward distributor to a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await expect(x2EarnApps.connect(owner).addRewardDistributor(app1Id, owner.address)).to.be.reverted; }); it("Cannot remove a reward distributor from a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await expect(x2EarnApps.connect(owner).removeRewardDistributor(app1Id, owner.address)).to.be.reverted; }); it("Cannot add ZERO_ADDRESS as a reward distributor of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(otherAccounts[0]).addRewardDistributor(app1Id, ZERO_ADDRESS)).to.be.reverted; }); it("Cannot remove ZERO_ADDRESS as a reward distributor of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(otherAccounts[0]).removeRewardDistributor(app1Id, ZERO_ADDRESS)).to.be.reverted; }); it("Cannot remove a non existing reward distributor from an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(owner).removeRewardDistributor(app1Id, otherAccounts[1].address)).to.be.reverted; }); it("When having more than one distributor, updating one address won't affect the others", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[1].address); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[2].address); let isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.true; isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[2].address); expect(isDistributor).to.be.true; await x2EarnApps.connect(owner).removeRewardDistributor(app1Id, otherAccounts[1].address); isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.false; isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[2].address); expect(isDistributor).to.be.true; }); it("Can correctly fetch all reward distributors of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[1].address); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[2].address); const distributors = await x2EarnApps.rewardDistributors(app1Id); expect(distributors).to.eql([otherAccounts[1].address, otherAccounts[2].address]); }); it("Can know if an address is a reward distributor of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).addRewardDistributor(app1Id, otherAccounts[1].address); let isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[1].address); expect(isDistributor).to.be.true; isDistributor = await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[2].address); expect(isDistributor).to.be.false; }); it("Cannot add a reward distributor to an app if not an admin", async function () { const { x2EarnApps, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await expect(x2EarnApps.connect(otherAccounts[0]).addRewardDistributor(app1Id, otherAccounts[1].address)).to.be .reverted; }); it("Cannot remove a reward distributor from an app if not an admin", async function () { const { x2EarnApps, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await expect(x2EarnApps.connect(otherAccounts[0]).removeRewardDistributor(app1Id, otherAccounts[1].address)).to.be .reverted; }); it("Cannot have exceed the maximum number of reward distributors for an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const limit = await x2EarnApps.MAX_REWARD_DISTRIBUTORS(); const app1Id = await x2EarnApps.hashAppName("My app"); const appAdmin = otherAccounts[9]; await x2EarnApps.connect(owner).submitApp(otherAccounts[0].address, appAdmin.address, "My app", "metadataURI"); const addDistributorPromises = []; for (let i = 1; i <= limit; i++) { const randomWallet = ethers.Wallet.createRandom(); addDistributorPromises.push(x2EarnApps.connect(appAdmin).addRewardDistributor(app1Id, randomWallet.address)); } // Wait for all addRewardDistributor transactions to complete await Promise.all(addDistributorPromises); await expect(x2EarnApps.connect(appAdmin).addRewardDistributor(app1Id, otherAccounts[10].address)).to.be.reverted; // check that having 100 distributors do not affect the app const distributors = await x2EarnApps.rewardDistributors(app1Id); expect(distributors.length).to.eql(100); // check that the last distributor is not the one that failed expect(distributors[99]).to.not.eql(otherAccounts[10].address); expect(await x2EarnApps.isRewardDistributor(app1Id, otherAccounts[10].address)).to.be.false; }); }); describe("Team allocation percentage", function () { it("By default, the team allocation percentage of an app is 0", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); const teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(0n); }); it("Admin can update the team allocation percentage of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 50); let teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(50n); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 60); teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(60n); }); it("Admin can remove the team allocation percentage of an app by setting it to 0", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 50); let teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(50n); }); it("Cannot update the team allocation percentage of a non-existing app", async function () { const { x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const appId = await x2EarnApps.hashAppName("non-existing app"); await expect(x2EarnApps.connect(owner).setTeamAllocationPercentage(appId, 50)).to.be.reverted; }); it("Cannot update the team allocation percentage of an app to more than 100", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await expect(x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 101)).to.be.reverted; }); it("Cannot update the team allocation percentage of an app to less than 0", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); // uint256 cannot be negative, so ethers will throw an encoding error try { await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, -1); expect.fail("Should have thrown an error"); } catch (error) { expect(error.code).to.equal("INVALID_ARGUMENT"); } }); it("Non-admin cannot update the team allocation percentage of an app", async function () { const { x2EarnApps, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true }); const app1Id = await x2EarnApps.hashAppName("My app"); await expect(x2EarnApps.connect(otherAccounts[0]).setTeamAllocationPercentage(app1Id, 50)).to.be.reverted; }); it("User with DEFAULT_ADMIN_ROLE can update the team allocation percentage of an app", async function () { const { x2EarnApps, otherAccounts, owner } = await getOrDeployContractInstances({ forceDeploy: true }); const adminRole = await x2EarnApps.DEFAULT_ADMIN_ROLE(); const isAdmin = await x2EarnApps.hasRole(adminRole, owner.address); expect(isAdmin).to.be.true; const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 50); let teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(50n); }); it("Team allocation percentage of an app is 0 and apps need to withdraw, then they can change this", async function () { const { x2EarnApps, otherAccounts, owner, xAllocationVoting, xAllocationPool, b3tr, x2EarnRewardsPool, veBetterPassport, } = await getOrDeployContractInstances({ forceDeploy: true }); const voter = otherAccounts[1]; await getVot3Tokens(voter, "1"); await veBetterPassport.whitelist(voter.address); await veBetterPassport.toggleCheck(1); const app1Id = await x2EarnApps.hashAppName("My app"); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, "My app", "metadataURI"); await endorseApp(app1Id, otherAccounts[0]); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 0); let teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(0n); // start round await bootstrapAndStartEmissions(); // vote let roundId = await xAllocationVoting.currentRoundId(); await xAllocationVoting.connect(voter).castVote(roundId, [app1Id], [ethers.parseEther("1")]); await waitForCurrentRoundToEnd(); // get balance of team wallet address const teamWalletAddress = await x2EarnApps.teamWalletAddress(app1Id); const teamWalletBalanceBefore = await b3tr.balanceOf(teamWalletAddress); expect(teamWalletBalanceBefore).to.eql(0n); const x2EarnRewardsPoolBalanceBefore = await b3tr.balanceOf(await x2EarnRewardsPool.getAddress()); let appEarnings = await xAllocationPool.roundEarnings(roundId, app1Id); // admin claims for app await xAllocationPool.connect(owner).claim(roundId, app1Id); // all funds should have been sent to the x2EarnRewardsPool contract const teamWalletBalanceAfter = await b3tr.balanceOf(teamWalletAddress); const x2EarnRewardsPoolBalanceAfter = await b3tr.balanceOf(await x2EarnRewardsPool.getAddress()); expect(teamWalletBalanceAfter).to.eql(0n); expect(x2EarnRewardsPoolBalanceAfter).to.eql(x2EarnRewardsPoolBalanceBefore + appEarnings[0]); // admin should be able to withdraw the funds await x2EarnRewardsPool.connect(otherAccounts[0]).withdraw(appEarnings[0], app1Id, ""); const x2EarnRewardsPoolBalanceAfterWithdraw = await b3tr.balanceOf(await x2EarnRewardsPool.getAddress()); expect(x2EarnRewardsPoolBalanceAfterWithdraw).to.eql(x2EarnRewardsPoolBalanceAfter - appEarnings[0]); const teamWalletBalanceAfterWithdraw = await b3tr.balanceOf(teamWalletAddress); expect(teamWalletBalanceAfterWithdraw).to.eql(appEarnings[0]); // now we start a new round and the app can change the team allocation percentage await startNewAllocationRound(); roundId = await xAllocationVoting.currentRoundId(); await xAllocationVoting.connect(voter).castVote(roundId, [app1Id], [ethers.parseEther("1")]); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 30); teamAllocationPercentage = await x2EarnApps.teamAllocationPercentage(app1Id); expect(teamAllocationPercentage).to.eql(30n); await waitForCurrentRoundToEnd(); appEarnings = await xAllocationPool.roundEarnings(roundId, app1Id); // admin claims for app await xAllocationPool.connect(owner).claim(roundId, app1Id); // now the team wallet should have received some funds const teamWalletBalanceAfter2 = await b3tr.balanceOf(teamWalletAddress); expect(teamWalletBalanceAfter2).to.eql(teamWalletBalanceAfterWithdraw + (appEarnings[0] * 30n) / 100n); // 70% of funds should have been sent to the x2EarnRewardsPool contract const x2EarnRewardsPoolBalanceAfter2 = await b3tr.balanceOf(await x2EarnRewardsPool.getAddress()); expect(x2EarnRewardsPoolBalanceAfter2).to.eql(x2EarnRewardsPoolBalanceAfterWithdraw + (appEarnings[0] * 70n) / 100n); // admin of app can deposit back the funds to the x2EarnRewardsPool await b3tr.connect(otherAccounts[0]).approve(await x2EarnRewardsPool.getAddress(), teamWalletBalanceAfter2); await x2EarnRewardsPool.connect(otherAccounts[0]).deposit(teamWalletBalanceAfter2.toString(), app1Id); const x2EarnRewardsPoolBalanceAfter3 = await b3tr.balanceOf(await x2EarnRewardsPool.getAddress()); expect(x2EarnRewardsPoolBalanceAfter3).to.eql(x2EarnRewardsPoolBalanceAfter2 + teamWalletBalanceAfter2); expect(await b3tr.balanceOf(teamWalletAddress)).to.eql(0n); }); }); });