@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
JavaScript
"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