UNPKG

@vechain/vebetterdao-contracts

Version:

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

688 lines 190 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const mocha_1 = require("mocha"); const helpers_1 = require("./helpers"); const chai_1 = require("chai"); const hardhat_1 = require("hardhat"); const local_1 = require("@repo/config/contracts/envs/local"); const config_1 = require("./helpers/config"); const upgrades_core_1 = require("@openzeppelin/upgrades-core"); const helpers_2 = require("../scripts/helpers"); const hardhat_network_helpers_1 = require("@nomicfoundation/hardhat-network-helpers"); const xnodes_1 = require("./helpers/xnodes"); (0, mocha_1.describe)("VoterRewards - @shard10-core", () => { // Environment params let creator1; let creator2; (0, mocha_1.before)(async function () { const { creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); creator1 = creators[0]; creator2 = creators[1]; }); (0, mocha_1.describe)("Contract parameters", () => { (0, mocha_1.it)("Should have correct parameters set on deployment", async () => { const { voterRewards, owner, galaxyMember, emissions } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Contract address checks (0, chai_1.expect)(await voterRewards.emissions()).to.equal(await emissions.getAddress()); (0, chai_1.expect)(await voterRewards.galaxyMember()).to.equal(await galaxyMember.getAddress()); // Admin role (0, chai_1.expect)(await voterRewards.hasRole(await voterRewards.DEFAULT_ADMIN_ROLE(), owner.address)).to.equal(true); // NFT Levels multipliers for (const level of helpers_1.levels) { (0, chai_1.expect)(await voterRewards.levelToMultiplier(level)).to.equal(helpers_1.multipliers[helpers_1.levels.indexOf(level)]); } }); (0, mocha_1.it)("Should be able to set new emissions contract", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).setEmissions(otherAccount.address); (0, chai_1.expect)(await voterRewards.emissions()).to.equal(otherAccount.address); }); (0, mocha_1.it)("Should not be able to set new emissions contract if not admin", async () => { const { voterRewards, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await (0, chai_1.expect)(voterRewards.connect(otherAccount).setEmissions(otherAccount.address)).to.be.reverted; }); (0, mocha_1.it)("Should be able to set new Galaxy Member contract", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).setGalaxyMember(otherAccount.address); (0, chai_1.expect)(await voterRewards.galaxyMember()).to.equal(otherAccount.address); }); (0, mocha_1.it)("Should not be able to set new Galaxy Member contract if not admin", async () => { const { voterRewards, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await (0, chai_1.expect)(voterRewards.connect(otherAccount).setGalaxyMember(otherAccount.address)).to.be.reverted; }); (0, mocha_1.it)("Should not be able to register vote if proposal start is zero", async () => { const { voterRewards, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await voterRewards.connect(owner).grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), otherAccount.address); await (0, chai_1.expect)(voterRewards .connect(otherAccount) .registerVote(0, otherAccount.address, hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther(Math.sqrt(1000).toString()))).to.be.reverted; }); (0, mocha_1.it)("Should revert if admin is set to zero address in initilisation", async () => { const config = (0, local_1.createLocalConfig)(); const { owner, b3tr, galaxyMember, emissions } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, config, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ helpers_1.ZERO_ADDRESS, // admin owner.address, // upgrader owner.address, // contractsAddressManager await emissions.getAddress(), await galaxyMember.getAddress(), await b3tr.getAddress(), helpers_1.levels, helpers_1.multipliers, ])).to.be.reverted; }); (0, mocha_1.it)("Should not be able to register vote for zero address voter", async () => { const { voterRewards, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await voterRewards.connect(owner).grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), otherAccount.address); await (0, chai_1.expect)(voterRewards .connect(otherAccount) .registerVote(1, helpers_1.ZERO_ADDRESS, hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther(Math.sqrt(1000).toString()))).to.be.reverted; }); (0, mocha_1.it)("Should return correct scaling factor", async () => { const { voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.SCALING_FACTOR()).to.equal(10 ** 6); }); (0, mocha_1.it)("Should return correct b3tr address", async () => { const { voterRewards, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.b3tr()).to.equal(await b3tr.getAddress()); }); (0, mocha_1.it)("Should be able to set level to multiplier", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).setLevelToMultiplierNow(1, 2); (0, chai_1.expect)(await voterRewards.levelToMultiplier(1)).to.equal(2); await (0, chai_1.expect)(voterRewards.connect(owner).setLevelToMultiplierNow(0, 2)).to.be.reverted; // Level cannot be zero await (0, chai_1.expect)(voterRewards.connect(owner).setLevelToMultiplierNow(1, 0)).to.be.reverted; // Multiplier cannot be zero await (0, chai_1.expect)(voterRewards.connect(otherAccount).setLevelToMultiplierNow(1, 2)).to.be.reverted; // Should not be able to set level to multiplier if not admin }); (0, mocha_1.it)("Should be able to set galaxy member address", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).setGalaxyMember(otherAccount.address); (0, chai_1.expect)(await voterRewards.galaxyMember()).to.equal(otherAccount.address); await (0, chai_1.expect)(voterRewards.connect(otherAccount).setGalaxyMember(otherAccount.address)).to.be.reverted; // Should not be able to set galaxy member address if not admin await (0, chai_1.expect)(voterRewards.connect(owner).setGalaxyMember(helpers_1.ZERO_ADDRESS)).to.be.reverted; // Galaxy member address cannot be zero }); (0, mocha_1.it)("Should be able to set emissions address", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).setEmissions(otherAccount.address); (0, chai_1.expect)(await voterRewards.emissions()).to.equal(otherAccount.address); await (0, chai_1.expect)(voterRewards.connect(otherAccount).setEmissions(otherAccount.address)).to.be.reverted; // Should not be able to set emissions address if not admin await (0, chai_1.expect)(voterRewards.connect(owner).setEmissions(helpers_1.ZERO_ADDRESS)).to.be.reverted; // Emissions address cannot be zero }); (0, mocha_1.it)("Admin should be able to set vote registrar role address", async () => { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await voterRewards.connect(owner).grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), otherAccount.address); }); (0, mocha_1.it)(" admin should be able to set vote registrar role address", async () => { const { voterRewards, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.hasRole(await voterRewards.VOTE_REGISTRAR_ROLE(), otherAccount.address)).to.eql(false); await (0, chai_1.expect)(voterRewards.connect(otherAccount).grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), otherAccount.address)).to.be.reverted; }); (0, mocha_1.it)("Should be able to disable Quadratic Rewards", async () => { const { voterRewards, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.isQuadraticRewardingDisabledAtBlock(await hardhat_1.ethers.provider.getBlockNumber())).to.eql(false); const tx = await voterRewards.connect(owner).toggleQuadraticRewarding(); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); const events = receipt?.logs; const decodedEvents = events?.map(event => { return voterRewards.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const event = decodedEvents.find(event => event?.name === "QuadraticRewardingToggled"); (0, chai_1.expect)(event).to.not.equal(undefined); (0, chai_1.expect)(await voterRewards.isQuadraticRewardingDisabledAtBlock(await hardhat_1.ethers.provider.getBlockNumber())).to.eql(true); }); (0, mocha_1.it)("Quadratic Rewards should be enabled by default", async () => { const { voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.isQuadraticRewardingDisabledAtBlock(1)).to.eql(false); }); (0, mocha_1.it)("Only admin should be able to disable Quadratic Rewards", async () => { const { voterRewards, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await (0, chai_1.expect)(voterRewards.connect(otherAccount).toggleQuadraticRewarding()).to.be.reverted; }); (0, mocha_1.it)("Clock should return correct block number", async () => { const { voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await voterRewards.clock()).to.equal(await hardhat_1.ethers.provider.getBlockNumber()); }); }); (0, mocha_1.describe)("Contract upgradeablity", () => { (0, mocha_1.it)("Admin should be able to upgrade the contract", async function () { const { voterRewards, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VoterRewards"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); const UPGRADER_ROLE = await voterRewards.UPGRADER_ROLE(); (0, chai_1.expect)(await voterRewards.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); await (0, chai_1.expect)(voterRewards.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Admin should be able to upgrade the contract", async function () { const { voterRewards, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VoterRewards"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); const UPGRADER_ROLE = await voterRewards.UPGRADER_ROLE(); (0, chai_1.expect)(await voterRewards.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(voterRewards.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.be .reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Admin can change UPGRADER_ROLE", async function () { const { voterRewards, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VoterRewards"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); const UPGRADER_ROLE = await voterRewards.UPGRADER_ROLE(); (0, chai_1.expect)(await voterRewards.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(voterRewards.connect(owner).grantRole(UPGRADER_ROLE, otherAccount.address)).to.not.be.reverted; await (0, chai_1.expect)(voterRewards.connect(owner).revokeRole(UPGRADER_ROLE, owner.address)).to.not.be.reverted; await (0, chai_1.expect)(voterRewards.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not .be.reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await voterRewards.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Should not be able to initialize the contract after already being initialized", async function () { const { voterRewards, owner, emissions, galaxyMember, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)(voterRewards.initialize(owner.address, owner.address, owner.address, await emissions.getAddress(), await galaxyMember.getAddress(), await b3tr.getAddress(), helpers_1.levels, helpers_1.multipliers)).to.be.reverted; }); (0, mocha_1.it)("Should not be able to deploy proxy with galaxy member address as zero address", async function () { const { owner, emissions, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ owner.address, owner.address, owner.address, await emissions.getAddress(), helpers_1.ZERO_ADDRESS, await b3tr.getAddress(), helpers_1.levels, helpers_1.multipliers, ])).to.be.reverted; }); (0, mocha_1.it)("Should not be able to deploy proxy with emissions address as zero address", async function () { const { owner, galaxyMember, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ owner.address, owner.address, owner.address, helpers_1.ZERO_ADDRESS, await galaxyMember.getAddress(), await b3tr.getAddress(), helpers_1.levels, helpers_1.multipliers, ])).to.be.reverted; }); (0, mocha_1.it)("Should not be able to deploy proxy with b3tr address as zero address", async function () { const { owner, emissions, galaxyMember } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ owner.address, owner.address, owner.address, await emissions.getAddress(), await galaxyMember.getAddress(), helpers_1.ZERO_ADDRESS, helpers_1.levels, helpers_1.multipliers, ])).to.be.reverted; }); (0, mocha_1.it)("Should not be able to deploy proxy with incorrect levels and multipliers", async function () { const { owner, emissions, galaxyMember, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ owner.address, owner.address, owner.address, await emissions.getAddress(), await galaxyMember.getAddress(), await b3tr.getAddress(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9], // Incorrect multipliers length should be same as levels length ])).to.be.reverted; }); (0, mocha_1.it)("Should not be able to deploy proxy with levels empty", async function () { const { owner, emissions, galaxyMember, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VoterRewardsV1", [ owner.address, owner.address, owner.address, await emissions.getAddress(), await galaxyMember.getAddress(), await b3tr.getAddress(), [], [], ])).to.be.reverted; }); (0, mocha_1.it)("Should return correct version of the contract", async () => { const { voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await voterRewards.version()).to.equal("7"); }); }); (0, mocha_1.describe)("X Allocation voting rewards", () => { (0, mocha_1.it)("Should track voting rewards correctly involving multiple voters", async () => { const config = (0, local_1.createLocalConfig)(); const { xAllocationVoting, otherAccounts, otherAccount, xAllocationPool, owner, voterRewards, emissions, b3tr, minterAccount, x2EarnApps, veBetterPassport, } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, config, }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const app1 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[0].address)); await (0, xnodes_1.endorseApp)(app1, otherAccounts[0]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[1].address, otherAccounts[1].address, otherAccounts[1].address, "metadataURI"); const app2 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[1].address)); await (0, xnodes_1.endorseApp)(app2, otherAccounts[1]); const voter2 = otherAccounts[3]; const voter3 = otherAccounts[4]; await veBetterPassport.whitelist(otherAccount.address); await veBetterPassport.whitelist(voter2.address); await veBetterPassport.whitelist(voter3.address); await veBetterPassport.toggleCheck(1); await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); await (0, helpers_1.getVot3Tokens)(voter2, "1000"); await (0, helpers_1.getVot3Tokens)(voter3, "1000"); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); let tx = await emissions.connect(minterAccount).start(); let receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let events = receipt?.logs; let decodedEvents = events?.map(event => { return xAllocationVoting.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const proposalEvent = decodedEvents.find(event => event?.name === "RoundCreated"); (0, chai_1.expect)(proposalEvent).to.not.equal(undefined); (0, chai_1.expect)(await emissions.getCurrentCycle()).to.equal(1); (0, chai_1.expect)(await b3tr.balanceOf(await xAllocationPool.getAddress())).to.equal(config.INITIAL_X_ALLOCATION); (0, chai_1.expect)(await emissions.nextCycle()).to.equal(2); await (0, helpers_1.waitForNextCycle)(); await emissions.connect(minterAccount).distribute(); const roundId = await xAllocationVoting.currentRoundId(); (0, chai_1.expect)(roundId).to.equal(2); (0, chai_1.expect)(await xAllocationVoting.roundDeadline(roundId)).to.lt(await emissions.getNextCycleBlock()); tx = await xAllocationVoting .connect(otherAccount) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("300"), hardhat_1.ethers.parseEther("200")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); events = receipt?.logs; decodedEvents = events ?.map(event => { return voterRewards.interface.parseLog({ topics: event?.topics, data: event?.data, }); }) .filter(e => e?.name === "VoteRegistered"); (0, chai_1.expect)(decodedEvents[0]?.args?.[0]).to.equal(2); // Cycle (0, chai_1.expect)(decodedEvents[0]?.args?.[1]).to.equal(otherAccount.address); // Voter (0, chai_1.expect)(await emissions.isCycleEnded(roundId)).to.equal(false); await (0, helpers_1.catchRevert)(voterRewards.claimReward(roundId, otherAccount.address)); // Should not be able to claim rewards before cycle ended (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(roundId, otherAccount)).to.equal(hardhat_1.ethers.parseEther("22.360679774")); // I'm expecting 22.36 because I voted 300 for app1 and 200 for app2 at the first cycle which is 500 and the square root of 500 is 22.36 tx = await xAllocationVoting .connect(voter2) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("200"), hardhat_1.ethers.parseEther("100")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(roundId, voter2)).to.equal(hardhat_1.ethers.parseEther("17.320508075")); // I'm expecting 17.32 because I voted 200 for app1 and 100 for app2 at the first cycle which is 300 and the square root of 300 is 17.32 await (0, helpers_1.catchRevert)(voterRewards.claimReward(roundId, voter2.address)); // Should not be able to claim rewards before cycle ended tx = await xAllocationVoting .connect(voter3) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("500")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(roundId, voter3)).to.equal(hardhat_1.ethers.parseEther("24.494897427")); // I'm expecting 24.49 because I voted 100 for app1 and 500 for app2 at the first cycle which is 600 and the square root of 600 is 24.49 // Votes should be tracked correctly let appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("800")); let totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("1400")); // Total voters should be tracked correctly const totalVoters = await xAllocationVoting.totalVoters(roundId); (0, chai_1.expect)(totalVoters).to.eql(BigInt(3)); // Voter rewards checks (0, chai_1.expect)(await voterRewards.cycleToTotal(roundId)).to.equal(hardhat_1.ethers.parseEther("64.176085276")); // Total votes -> Math.sqrt(500) + Math.sqrt(300) + Math.sqrt(600) (0, chai_1.expect)(await voterRewards.cycleToTotal(roundId)).to.equal((await voterRewards.cycleToVoterToTotal(roundId, otherAccount)) + (await voterRewards.cycleToVoterToTotal(roundId, voter2)) + (await voterRewards.cycleToVoterToTotal(roundId, voter3))); // Total votes await (0, helpers_1.waitForRoundToEnd)(Number(roundId)); // Votes should be the same after round ended appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("800")); totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("1400")); await (0, helpers_1.waitForNextCycle)(); (0, chai_1.expect)(await emissions.isCycleDistributed(await emissions.nextCycle())).to.equal(false); (0, chai_1.expect)(await emissions.isNextCycleDistributable()).to.equal(true); // Reward claiming (0, chai_1.expect)(await emissions.isCycleDistributed(1)).to.equal(true); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.equal((await emissions.getVote2EarnAmount(1)) + (await emissions.getVote2EarnAmount(2)) + (await emissions.getGMAmount(2))); const voter1Rewards = await voterRewards.getReward(roundId, otherAccount.address); const voter2Rewards = await voterRewards.getReward(roundId, voter2.address); const voter3Rewards = await voterRewards.getReward(roundId, voter3.address); tx = await voterRewards.connect(otherAccount).claimReward(roundId, otherAccount.address); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await b3tr.balanceOf(otherAccount.address)).to.equal(voter1Rewards); events = receipt?.logs; decodedEvents = events?.map(event => { return voterRewards.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const rewardClaimedEvent = decodedEvents.find(event => event?.name === "RewardClaimedV2"); (0, chai_1.expect)(rewardClaimedEvent?.args?.[0]).to.equal(roundId); // Cycle (0, chai_1.expect)(rewardClaimedEvent?.args?.[1]).to.equal(otherAccount.address); // Voter (0, chai_1.expect)(rewardClaimedEvent?.args?.[2]).to.equal(696853966016598011228309n); // Reward await voterRewards.connect(voter2).claimReward(roundId, voter2.address); await voterRewards.connect(voter3).claimReward(roundId, voter3.address); await (0, chai_1.expect)(voterRewards.connect(voter2).claimReward(1, helpers_1.ZERO_ADDRESS)).to.be.reverted; // Should not be able to claim rewards for zero address (0, chai_1.expect)(await b3tr.balanceOf(voter2.address)).to.equal(voter2Rewards); (0, chai_1.expect)(await b3tr.balanceOf(voter3.address)).to.equal(voter3Rewards); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.lt(hardhat_1.ethers.parseEther("22500001")); // Round 1 + GM pool round 2 }); (0, mocha_1.it)("Should track voting rewards correctly involving multiple voters when Quadratic Rewarding is disabled", async () => { const config = (0, local_1.createLocalConfig)(); const { xAllocationVoting, otherAccounts, otherAccount, xAllocationPool, owner, voterRewards, emissions, b3tr, minterAccount, x2EarnApps, veBetterPassport, } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await voterRewards.connect(owner).toggleQuadraticRewarding(); (0, chai_1.expect)(await voterRewards.isQuadraticRewardingDisabledAtBlock(await hardhat_1.ethers.provider.getBlockNumber())).to.eql(true); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const app1 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[0].address)); await (0, xnodes_1.endorseApp)(app1, otherAccounts[0]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[1].address, otherAccounts[1].address, otherAccounts[1].address, "metadataURI"); const app2 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[1].address)); await (0, xnodes_1.endorseApp)(app2, otherAccounts[1]); const voter2 = otherAccounts[3]; const voter3 = otherAccounts[4]; await veBetterPassport.whitelist(otherAccount.address); await veBetterPassport.whitelist(voter2.address); await veBetterPassport.whitelist(voter3.address); await veBetterPassport.toggleCheck(1); await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); await (0, helpers_1.getVot3Tokens)(voter2, "1000"); await (0, helpers_1.getVot3Tokens)(voter3, "1000"); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); let tx = await emissions.connect(minterAccount).start(); let receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let events = receipt?.logs; let decodedEvents = events?.map(event => { return xAllocationVoting.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const proposalEvent = decodedEvents.find(event => event?.name === "RoundCreated"); (0, chai_1.expect)(proposalEvent).to.not.equal(undefined); (0, chai_1.expect)(await emissions.getCurrentCycle()).to.equal(1); (0, chai_1.expect)(await b3tr.balanceOf(await xAllocationPool.getAddress())).to.equal(config.INITIAL_X_ALLOCATION); (0, chai_1.expect)(await emissions.nextCycle()).to.equal(2); const roundId = await xAllocationVoting.currentRoundId(); (0, chai_1.expect)(roundId).to.equal(1); (0, chai_1.expect)(await xAllocationVoting.roundDeadline(roundId)).to.lt(await emissions.getNextCycleBlock()); tx = await xAllocationVoting .connect(otherAccount) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("300"), hardhat_1.ethers.parseEther("200")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); events = receipt?.logs; decodedEvents = events ?.map(event => { return voterRewards.interface.parseLog({ topics: event?.topics, data: event?.data, }); }) .filter(e => e?.name === "VoteRegistered"); (0, chai_1.expect)(decodedEvents[0]?.args?.[0]).to.equal(1); // Cycle (0, chai_1.expect)(decodedEvents[0]?.args?.[1]).to.equal(otherAccount.address); // Voter (0, chai_1.expect)(decodedEvents[0]?.args?.[2]).to.equal(hardhat_1.ethers.parseEther("500")); // Votes (0, chai_1.expect)(decodedEvents[0]?.args?.[3]).to.equal(hardhat_1.ethers.parseEther("500")); // Reward weight (0, chai_1.expect)(await emissions.isCycleEnded(1)).to.equal(false); await (0, helpers_1.catchRevert)(voterRewards.claimReward(1, otherAccount.address)); // Should not be able to claim rewards before cycle ended (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, otherAccount)).to.equal(hardhat_1.ethers.parseEther("500")); // I'm expecting 500 because I voted 300 for app1 and 200 for app2 at the first cycle which is 500 tx = await xAllocationVoting .connect(voter2) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("200"), hardhat_1.ethers.parseEther("100")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, voter2)).to.equal(hardhat_1.ethers.parseEther("300")); // I'm expecting 300 because I voted 200 for app1 and 100 for app2 at the first cycle which is 300 await (0, helpers_1.catchRevert)(voterRewards.claimReward(1, voter2.address)); // Should not be able to claim rewards before cycle ended tx = await xAllocationVoting .connect(voter3) .castVote(roundId, [app1, app2], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("500")]); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, voter3)).to.equal(hardhat_1.ethers.parseEther("600")); // I'm expecting 600 because I voted 100 for app1 and 500 for app2 at the first cycle which is 600 // Votes should be tracked correctly let appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("800")); let totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("1400")); // Total voters should be tracked correctly const totalVoters = await xAllocationVoting.totalVoters(roundId); (0, chai_1.expect)(totalVoters).to.eql(BigInt(3)); // Voter rewards checks (0, chai_1.expect)(await voterRewards.cycleToTotal(1)).to.equal(hardhat_1.ethers.parseEther("1400")); // Total votes (0, chai_1.expect)(await voterRewards.cycleToTotal(1)).to.equal((await voterRewards.cycleToVoterToTotal(1, otherAccount)) + (await voterRewards.cycleToVoterToTotal(1, voter2)) + (await voterRewards.cycleToVoterToTotal(1, voter3))); // Total votes await (0, helpers_1.waitForRoundToEnd)(Number(roundId)); // Votes should be the same after round ended appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("800")); totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("1400")); await (0, helpers_1.waitForNextCycle)(); (0, chai_1.expect)(await emissions.isCycleDistributed(await emissions.nextCycle())).to.equal(false); (0, chai_1.expect)(await emissions.isNextCycleDistributable()).to.equal(true); // Reward claiming (0, chai_1.expect)(await emissions.isCycleDistributed(1)).to.equal(true); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.equal(await emissions.getVote2EarnAmount(1)); const voter1Rewards = await voterRewards.getReward(1, otherAccount.address); const voter2Rewards = await voterRewards.getReward(1, voter2.address); const voter3Rewards = await voterRewards.getReward(1, voter3.address); tx = await voterRewards.connect(otherAccount).claimReward(1, otherAccount); receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); (0, chai_1.expect)(await b3tr.balanceOf(otherAccount.address)).to.equal(voter1Rewards); events = receipt?.logs; decodedEvents = events?.map(event => { return voterRewards.interface.parseLog({ topics: event?.topics, data: event?.data, }); }); const rewardClaimedEvent = decodedEvents.find(event => event?.name === "RewardClaimedV2"); (0, chai_1.expect)(rewardClaimedEvent?.args?.[0]).to.equal(1); // Cycle (0, chai_1.expect)(rewardClaimedEvent?.args?.[1]).to.equal(otherAccount.address); // Voter (0, chai_1.expect)(rewardClaimedEvent?.args?.[2]).to.equal(714285714285714285714285n); // Reward await voterRewards.connect(voter2).claimReward(1, voter2.address); await voterRewards.connect(voter3).claimReward(1, voter3.address); await (0, chai_1.expect)(voterRewards.connect(voter2).claimReward(1, helpers_1.ZERO_ADDRESS)).to.be.reverted; // Should not be able to claim rewards for zero address (0, chai_1.expect)(await b3tr.balanceOf(voter2.address)).to.equal(voter2Rewards); (0, chai_1.expect)(await b3tr.balanceOf(voter3.address)).to.equal(voter3Rewards); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.lt(hardhat_1.ethers.parseEther("1")); }); (0, mocha_1.it)("Should track voting rewards correctly involving multiple voters and multiple rounds", async () => { const { xAllocationVoting, otherAccounts, otherAccount: voter1, owner, voterRewards, emissions, b3tr, minterAccount, x2EarnApps, veBetterPassport, } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await x2EarnApps .connect(owner) .submitApp(otherAccounts[0].address, otherAccounts[0].address, otherAccounts[0].address, "metadataURI"); const app1 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[0].address)); await (0, xnodes_1.endorseApp)(app1, otherAccounts[0]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[1].address, otherAccounts[1].address, otherAccounts[1].address, "metadataURI"); const app2 = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[1].address)); await (0, xnodes_1.endorseApp)(app2, otherAccounts[1]); const voter2 = otherAccounts[3]; const voter3 = otherAccounts[4]; await veBetterPassport.whitelist(voter1.address); await veBetterPassport.whitelist(voter2.address); await veBetterPassport.whitelist(voter3.address); await veBetterPassport.toggleCheck(1); await (0, helpers_1.getVot3Tokens)(voter1, "1000"); await (0, helpers_1.getVot3Tokens)(voter2, "1000"); await (0, helpers_1.getVot3Tokens)(voter3, "1000"); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); await emissions.connect(minterAccount).start(); const roundId = await xAllocationVoting.currentRoundId(); const isdisabled = await voterRewards.isQuadraticRewardingDisabledForCurrentCycle(); (0, chai_1.expect)(isdisabled).to.equal(false); (0, chai_1.expect)(roundId).to.equal(1); (0, chai_1.expect)(await xAllocationVoting.roundDeadline(roundId)).to.lt(await emissions.getNextCycleBlock()); // Vote on apps for the first round await (0, helpers_1.voteOnApps)([app1, app2], [voter1, voter2, voter3], [ [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("0")], // Voter 1 votes 1000 for app1 [hardhat_1.ethers.parseEther("200"), hardhat_1.ethers.parseEther("100")], // Voter 2 votes 200 for app1 and 100 for app2 [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("500")], // Voter 3 votes 500 for app1 and 500 for app2 ], roundId); (0, chai_1.expect)(await emissions.isCycleEnded(1)).to.equal(false); await (0, helpers_1.catchRevert)(voterRewards.claimReward(1, voter1.address)); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, voter1)).to.equal(hardhat_1.ethers.parseEther("31.622776601")); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, voter2)).to.equal(hardhat_1.ethers.parseEther("17.320508075")); await (0, helpers_1.catchRevert)(voterRewards.claimReward(1, voter2.address)); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(1, voter3)).to.equal(hardhat_1.ethers.parseEther("31.622776601")); // Votes should be tracked correctly let appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("1700")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); let totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("2300")); // Total voters should be tracked correctly let totalVoters = await xAllocationVoting.totalVoters(roundId); (0, chai_1.expect)(totalVoters).to.eql(BigInt(3)); // Voter rewards checks (0, chai_1.expect)(await voterRewards.cycleToTotal(1)).to.equal(hardhat_1.ethers.parseEther("80.566061277")); // Total votes -> Math.sqrt(1000) + Math.sqrt(300) + Math.sqrt(1000) (0, chai_1.expect)(await voterRewards.cycleToTotal(1)).to.equal((await voterRewards.cycleToVoterToTotal(1, voter1)) + (await voterRewards.cycleToVoterToTotal(1, voter2)) + (await voterRewards.cycleToVoterToTotal(1, voter3))); // Total votes await (0, helpers_1.waitForRoundToEnd)(Number(roundId)); // Votes should be the same after round ended appVotes = await xAllocationVoting.getAppVotes(roundId, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("1700")); appVotes = await xAllocationVoting.getAppVotes(roundId, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); totalVotes = await xAllocationVoting.totalVotes(roundId); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("2300")); await (0, helpers_1.waitForNextCycle)(); (0, chai_1.expect)(await emissions.isCycleDistributed(await emissions.nextCycle())).to.equal(false); (0, chai_1.expect)(await emissions.isNextCycleDistributable()).to.equal(true); // Reward claiming (0, chai_1.expect)(await emissions.isCycleDistributed(1)).to.equal(true); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.equal(await emissions.getVote2EarnAmount(1)); const voter1Rewards = await voterRewards.getReward(1, voter1.address); const voter2Rewards = await voterRewards.getReward(1, voter2.address); const voter3Rewards = await voterRewards.getReward(1, voter3.address); await voterRewards.connect(voter1).claimReward(1, voter1); (0, chai_1.expect)(await b3tr.balanceOf(voter1.address)).to.equal(voter1Rewards); (0, chai_1.expect)(await b3tr.balanceOf(await voterRewards.getAddress())).to.equal((await emissions.getVote2EarnAmount(1)) - voter1Rewards); // Second round await emissions.connect(voter1).distribute(); // Anyone can distribute the cycle const roundId2 = await xAllocationVoting.currentRoundId(); (0, chai_1.expect)(roundId2).to.equal(2); (0, chai_1.expect)(await xAllocationVoting.roundDeadline(roundId)).to.lt(await emissions.getNextCycleBlock()); // Vote on apps for the second round await (0, helpers_1.voteOnApps)([app1, app2], [voter1, voter2, voter3], [ [hardhat_1.ethers.parseEther("0"), hardhat_1.ethers.parseEther("1000")], // Voter 1 votes 1000 for app2 [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("500")], // Voter 2 votes 100 for app1 and 500 for app2 [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("500")], // Voter 3 votes 500 for app1 and 500 for app2 ], roundId2); (0, chai_1.expect)(await emissions.isCycleEnded(2)).to.equal(false); await (0, helpers_1.catchRevert)(voterRewards.claimReward(2, voter1.address)); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(2, voter1)).to.equal(hardhat_1.ethers.parseEther("31.622776601")); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(2, voter2)).to.equal(hardhat_1.ethers.parseEther("24.494897427")); await (0, helpers_1.catchRevert)(voterRewards.claimReward(2, voter2.address)); (0, chai_1.expect)(await voterRewards.cycleToVoterToTotal(2, voter3)).to.equal(hardhat_1.ethers.parseEther("31.622776601")); // Votes should be tracked correctly appVotes = await xAllocationVoting.getAppVotes(roundId2, app1); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("600")); appVotes = await xAllocationVoting.getAppVotes(roundId2, app2); (0, chai_1.expect)(appVotes).to.eql(hardhat_1.ethers.parseEther("2000")); totalVotes = await xAllocationVoting.totalVotes(roundId2); (0, chai_1.expect)(totalVotes).to.eql(hardhat_1.ethers.parseEther("2600")); // Total voters should be tracked correctly totalVoters = await xAllocationVoting.totalVoters(roundId2); (0, chai_1.expect)(totalVoters).to.eql(BigInt(3)); // Voter rewards checks (0, chai_1.expect)(await voterRewards.cycleToTotal(2)).to.equal(hardhat_1.ethers.parseEther("87.740450629")); // Total votes -> Math.sqrt(1000) + Math.sqrt(300) + Math.sqrt(1000) (0, chai_1.expect)(await voterRewards.cycleToTotal(2)).to.equal((await voterRewards.cycleToVoterToTotal(2, voter1)) + (await voterRewards.cycleToVoterToTotal(2, vo