@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
736 lines • 153 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const hardhat_1 = require("hardhat");
const chai_1 = require("chai");
const helpers_1 = require("./helpers");
const mocha_1 = require("mocha");
const upgrades_core_1 = require("@openzeppelin/upgrades-core");
const helpers_2 = require("../scripts/helpers");
const xnodes_1 = require("./helpers/xnodes");
const local_1 = require("@repo/config/contracts/envs/local");
const config_1 = require("./helpers/config");
const xAllocationVotingLibraries_1 = require("../scripts/libraries/xAllocationVotingLibraries");
(0, mocha_1.describe)("X-Allocation Voting - @shard14-core", function () {
// Environment params
let creator1;
let creator2;
let creator3;
let creator4;
(0, mocha_1.before)(async function () {
const { creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true });
creator1 = creators[0];
creator2 = creators[1];
creator3 = creators[2];
creator4 = creators[3];
});
(0, mocha_1.describe)("Deployment", function () {
(0, mocha_1.it)("Admins should be set correctly", async function () {
const { xAllocationVoting, owner, timeLock } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
const ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
(0, chai_1.expect)(await xAllocationVoting.hasRole(ADMIN_ROLE, await timeLock.getAddress())).to.eql(true);
(0, chai_1.expect)(await xAllocationVoting.hasRole(ADMIN_ROLE, owner.address)).to.eql(true);
});
(0, mocha_1.it)("Should not support invalid interface", async function () {
const { xAllocationVoting } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
const INVALID_ID = "0xffffffff";
(0, chai_1.expect)(await xAllocationVoting.supportsInterface(INVALID_ID)).to.eql(false);
});
(0, mocha_1.it)("Can set multiple admins during deployment", async function () {
const { voterRewards, timeLock, emissions, x2EarnApps, vot3, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: false,
});
const xAllocationVotingV1 = (await (0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await vot3.getAddress(),
quorumPercentage: 1,
initialVotingPeriod: 2,
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), otherAccounts[2].address, otherAccounts[2].address],
upgrader: owner.address,
contractsAddressManager: otherAccounts[2].address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: 2,
appSharesCap: 2,
votingThreshold: BigInt(1),
},
]));
// Upgrade XAllocationVoting V1 to XAllocationVoting V2
const xAllocationVoting = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV1", "XAllocationVotingV2", await xAllocationVotingV1.getAddress(), [], { version: 2 }));
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.DEFAULT_ADMIN_ROLE(), await timeLock.getAddress())).to.eql(true);
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.DEFAULT_ADMIN_ROLE(), otherAccounts[2].address)).to.eql(true);
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.DEFAULT_ADMIN_ROLE(), otherAccounts[2].address)).to.eql(true);
});
(0, mocha_1.it)("Should support ERC 165 interface", async () => {
const { xAllocationVoting } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true });
(0, chai_1.expect)(await xAllocationVoting.supportsInterface("0x01ffc9a7")).to.equal(true); // ERC165
});
(0, mocha_1.it)("Clock mode is set correctly", async function () {
const { xAllocationVoting, vot3 } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: false,
});
(0, chai_1.expect)(await xAllocationVoting.CLOCK_MODE()).to.eql(await vot3.CLOCK_MODE());
});
(0, mocha_1.it)("Clock returns block number if token does not implement clock function", async function () {
const { xAllocationVoting, timeLock, voterRewards, emissions, otherAccounts, x2EarnApps, b3tr, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: false,
});
let clock = await xAllocationVoting.clock();
(0, chai_1.expect)(parseInt(clock.toString())).to.eql(await hardhat_1.ethers.provider.getBlockNumber());
const xAllocationVotingWithB3TRV1 = (await (0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await b3tr.getAddress(),
quorumPercentage: 1,
initialVotingPeriod: 2,
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), otherAccounts[2].address, otherAccounts[2].address],
upgrader: owner.address,
contractsAddressManager: otherAccounts[2].address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: 2,
appSharesCap: 2,
votingThreshold: BigInt(1),
},
]));
// Upgrade XAllocationVoting V1 to XAllocationVoting V2
const xAllocationVotingWithB3TR = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV1", "XAllocationVotingV2", await xAllocationVotingWithB3TRV1.getAddress(), [], { version: 2 }));
clock = await xAllocationVotingWithB3TR.clock();
(0, chai_1.expect)(parseInt(clock.toString())).to.eql(await hardhat_1.ethers.provider.getBlockNumber());
//CLOKC_MODE should return "mode=blocknumber&from=default"
(0, chai_1.expect)(await xAllocationVotingWithB3TR.CLOCK_MODE()).to.eql("mode=blocknumber&from=default");
});
(0, mocha_1.it)("Should revert if VOT3 is set to zero address in initilisation", async () => {
const config = (0, local_1.createLocalConfig)();
const { owner, x2EarnApps, timeLock, emissions, voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
config,
});
await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: helpers_1.ZERO_ADDRESS,
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE, // quorum percentage
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1, // X Alloc voting period
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), owner.address],
upgrader: owner.address,
contractsAddressManager: owner.address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE,
appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP,
votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD,
},
])).to.be.reverted;
});
(0, mocha_1.it)("Should revert if VoterRewards is set to zero address in initilisation", async () => {
const config = (0, local_1.createLocalConfig)();
const { owner, x2EarnApps, timeLock, emissions, vot3 } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
config,
});
await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await vot3.getAddress(),
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE, // quorum percentage
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1, // X Alloc voting period
timeLock: await timeLock.getAddress(),
voterRewards: helpers_1.ZERO_ADDRESS,
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), owner.address],
upgrader: owner.address,
contractsAddressManager: owner.address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE,
appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP,
votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD,
},
])).to.be.reverted;
});
(0, mocha_1.it)("Should revert if Emissions is set to zero address in initilisation", async () => {
const config = (0, local_1.createLocalConfig)();
const { owner, x2EarnApps, timeLock, vot3, voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
config,
});
await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await vot3.getAddress(),
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE, // quorum percentage
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1, // X Alloc voting period
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: helpers_1.ZERO_ADDRESS,
admins: [await timeLock.getAddress(), owner.address],
upgrader: owner.address,
contractsAddressManager: owner.address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE,
appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP,
votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD,
},
])).to.be.reverted;
});
(0, mocha_1.it)("Should revert if an admin is set to zero address in initilisation", async () => {
const config = (0, local_1.createLocalConfig)();
const { owner, x2EarnApps, timeLock, vot3, voterRewards, emissions } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
config,
});
await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await vot3.getAddress(),
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE, // quorum percentage
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1, // X Alloc voting period
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), helpers_1.ZERO_ADDRESS],
upgrader: owner.address,
contractsAddressManager: owner.address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE,
appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP,
votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD,
},
])).to.be.reverted;
});
});
(0, mocha_1.describe)("Contract upgradeablity", () => {
(0, mocha_1.it)("Admin should be able to upgrade the contract", async function () {
const { xAllocationVoting, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
const xAllocLibs = await (0, xAllocationVotingLibraries_1.xAllocationVotingLibraries)();
// Deploy the implementation contract
const Contract = await hardhat_1.ethers.getContractFactory("XAllocationVoting", {
libraries: {
AutoVotingLogic: await xAllocLibs.AutoVotingLogic.getAddress(),
ExternalContractsUtils: await xAllocLibs.ExternalContractsUtils.getAddress(),
VotingSettingsUtils: await xAllocLibs.VotingSettingsUtils.getAddress(),
VotesUtils: await xAllocLibs.VotesUtils.getAddress(),
VotesQuorumFractionUtils: await xAllocLibs.VotesQuorumFractionUtils.getAddress(),
RoundEarningsSettingsUtils: await xAllocLibs.RoundEarningsSettingsUtils.getAddress(),
RoundFinalizationUtils: await xAllocLibs.RoundFinalizationUtils.getAddress(),
RoundsStorageUtils: await xAllocLibs.RoundsStorageUtils.getAddress(),
RoundVotesCountingUtils: await xAllocLibs.RoundVotesCountingUtils.getAddress(),
},
});
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.getAddress());
const UPGRADER_ROLE = await xAllocationVoting.UPGRADER_ROLE();
(0, chai_1.expect)(await xAllocationVoting.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true);
await (0, chai_1.expect)(xAllocationVoting.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be
.reverted;
const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.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)("Only admin should be able to upgrade the contract", async function () {
const { xAllocationVoting, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
// Deploy the implementation contract
const Contract = await hardhat_1.ethers.getContractFactory("TimeLock");
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.getAddress());
const UPGRADER_ROLE = await xAllocationVoting.UPGRADER_ROLE();
(0, chai_1.expect)(await xAllocationVoting.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false);
await (0, chai_1.expect)(xAllocationVoting.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to
.be.reverted;
const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.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 { xAllocationVoting, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
// Deploy the implementation contract
const Contract = await hardhat_1.ethers.getContractFactory("TimeLock");
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.getAddress());
const UPGRADER_ROLE = await xAllocationVoting.UPGRADER_ROLE();
(0, chai_1.expect)(await xAllocationVoting.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false);
await (0, chai_1.expect)(xAllocationVoting.connect(owner).grantRole(UPGRADER_ROLE, otherAccount.address)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationVoting.connect(owner).revokeRole(UPGRADER_ROLE, owner.address)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationVoting.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to
.not.be.reverted;
const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.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 be able to upgrade the xAllocationVoting contract through governance", async function () {
const config = (0, local_1.createLocalConfig)();
config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD = 0;
const { xAllocationVoting, timeLock, governor, owner, otherAccount, vot3, veBetterPassport } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
config,
});
const xAllocLibs = await (0, xAllocationVotingLibraries_1.xAllocationVotingLibraries)();
await (0, helpers_1.getVot3Tokens)(otherAccount, "1000");
await vot3.connect(otherAccount).approve(await governor.getAddress(), "1000");
const UPGRADER_ROLE = await xAllocationVoting.UPGRADER_ROLE();
await (0, chai_1.expect)(xAllocationVoting.connect(owner).grantRole(UPGRADER_ROLE, await timeLock.getAddress())).to.not.be
.reverted;
// Deploy the implementation contract
const Contract = await hardhat_1.ethers.getContractFactory("XAllocationVoting", {
libraries: {
AutoVotingLogic: await xAllocLibs.AutoVotingLogic.getAddress(),
ExternalContractsUtils: await xAllocLibs.ExternalContractsUtils.getAddress(),
VotingSettingsUtils: await xAllocLibs.VotingSettingsUtils.getAddress(),
VotesUtils: await xAllocLibs.VotesUtils.getAddress(),
VotesQuorumFractionUtils: await xAllocLibs.VotesQuorumFractionUtils.getAddress(),
RoundEarningsSettingsUtils: await xAllocLibs.RoundEarningsSettingsUtils.getAddress(),
RoundFinalizationUtils: await xAllocLibs.RoundFinalizationUtils.getAddress(),
RoundsStorageUtils: await xAllocLibs.RoundsStorageUtils.getAddress(),
RoundVotesCountingUtils: await xAllocLibs.RoundVotesCountingUtils.getAddress(),
},
});
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
await (0, helpers_1.bootstrapAndStartEmissions)();
// Whitelist user
await veBetterPassport.whitelist(otherAccount.address);
await veBetterPassport.toggleCheck(1);
const LatestContract = await hardhat_1.ethers.getContractAt("XAllocationVoting", await xAllocationVoting.getAddress());
// Now we can create a proposal
const encodedFunctionCall = LatestContract.interface.encodeFunctionData("upgradeToAndCall", [
await implementation.getAddress(),
"0x",
]);
const description = "Upgrading XAllocationVoting contracts";
const descriptionHash = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(description));
const currentRoundId = await xAllocationVoting.currentRoundId();
const tx = await governor
.connect(owner)
.propose([await xAllocationVoting.getAddress()], [0], [encodedFunctionCall], description, currentRoundId + 1n, 0);
const proposalId = await (0, helpers_1.getProposalIdFromTx)(tx);
await (0, helpers_1.waitForProposalToBeActive)(proposalId);
await governor.connect(otherAccount).castVote(proposalId, 1);
await (0, helpers_1.waitForVotingPeriodToEnd)(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.eql(4n); // succeded
await governor.queue([await xAllocationVoting.getAddress()], [0], [encodedFunctionCall], descriptionHash);
(0, chai_1.expect)(await governor.state(proposalId)).to.eql(5n);
await governor.execute([await xAllocationVoting.getAddress()], [0], [encodedFunctionCall], descriptionHash);
(0, chai_1.expect)(await governor.state(proposalId)).to.eql(6n);
const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationVoting.getAddress());
(0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase());
});
(0, mocha_1.it)("Cannot initialize twice", async function () {
const { voterRewards, timeLock, emissions, x2EarnApps, vot3, otherAccounts, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: false,
});
const xAllocationVotingV1 = (await (0, helpers_2.deployProxy)("XAllocationVotingV1", [
{
vot3Token: await vot3.getAddress(),
quorumPercentage: 1,
initialVotingPeriod: 2,
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), otherAccounts[2].address, otherAccounts[2].address],
upgrader: owner.address,
contractsAddressManager: otherAccounts[2].address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: 2,
appSharesCap: 2,
votingThreshold: BigInt(1),
},
]));
await (0, helpers_1.catchRevert)(xAllocationVotingV1.initialize({
vot3Token: await vot3.getAddress(),
quorumPercentage: 1,
initialVotingPeriod: 2,
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), otherAccounts[2].address, otherAccounts[2].address],
upgrader: owner.address,
contractsAddressManager: otherAccounts[2].address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: 2,
appSharesCap: 2,
votingThreshold: BigInt(1),
}));
});
(0, mocha_1.it)("Should return correct version of the contract", async () => {
const { xAllocationVoting } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
(0, chai_1.expect)(await xAllocationVoting.version()).to.equal("10");
});
(0, mocha_1.it)("Should not break storage when upgrading to V2, V3, V4, V5, V6, V7 and V8", async () => {
const config = (0, config_1.createTestConfig)();
const configContracts = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
const { otherAccounts, x2EarnApps, xAllocationPool, b3tr, vot3, galaxyMember, timeLock, treasury, owner, veBetterPassport, minterAccount, } = configContracts;
// set personhood threshold to 0
await veBetterPassport.connect(owner).setThresholdPoPScore(0);
await veBetterPassport.toggleCheck(4);
const emissionsV1 = (await (0, helpers_2.deployProxy)("Emissions", [
{
minter: minterAccount.address,
admin: owner.address,
upgrader: owner.address,
contractsAddressManager: owner.address,
decaySettingsManager: owner.address,
b3trAddress: await b3tr.getAddress(),
destinations: [
await xAllocationPool.getAddress(),
owner.address,
await treasury.getAddress(),
config.MIGRATION_ADDRESS,
],
initialXAppAllocation: config.INITIAL_X_ALLOCATION,
cycleDuration: config.EMISSIONS_CYCLE_DURATION,
decaySettings: [
config.EMISSIONS_X_ALLOCATION_DECAY_PERCENTAGE,
config.EMISSIONS_VOTE_2_EARN_DECAY_PERCENTAGE,
config.EMISSIONS_X_ALLOCATION_DECAY_PERIOD,
config.EMISSIONS_VOTE_2_EARN_ALLOCATION_DECAY_PERIOD,
],
treasuryPercentage: config.EMISSIONS_TREASURY_PERCENTAGE,
maxVote2EarnDecay: config.EMISSIONS_MAX_VOTE_2_EARN_DECAY_PERCENTAGE,
migrationAmount: config.MIGRATION_AMOUNT,
},
]));
const emissions = (await (0, helpers_2.upgradeProxy)("EmissionsV1", "Emissions", await emissionsV1.getAddress(), [config.EMISSIONS_IS_NOT_ALIGNED ?? false], {
version: 2,
}));
const voterRewardsV1 = (await (0, helpers_2.deployProxy)("VoterRewardsV1", [
owner.address, // admin
owner.address, // upgrader
owner.address, // contractsAddressManager
await emissions.getAddress(),
await galaxyMember.getAddress(),
await b3tr.getAddress(),
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[0, 10, 20, 50, 100, 150, 200, 400, 900, 2400],
]));
const voterRewards = (await (0, helpers_2.upgradeProxy)("VoterRewardsV1", "VoterRewards", await voterRewardsV1.getAddress(), [], {
version: 2,
}));
// Set vote 2 earn (VoterRewards deployed contract) address in emissions
await emissions.connect(owner).setVote2EarnAddress(await voterRewards.getAddress());
// Deploy and upgrade through V3
const xAllocationVotingV3 = (await (0, helpers_2.deployAndUpgrade)(["XAllocationVotingV1", "XAllocationVotingV2", "XAllocationVotingV3"], [
[
{
vot3Token: await vot3.getAddress(),
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE,
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1,
timeLock: await timeLock.getAddress(),
voterRewards: await voterRewards.getAddress(),
emissions: await emissions.getAddress(),
admins: [await timeLock.getAddress(), owner.address],
upgrader: owner.address,
contractsAddressManager: owner.address,
x2EarnAppsAddress: await x2EarnApps.getAddress(),
baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE,
appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP,
votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD,
},
],
[await veBetterPassport.getAddress()],
[],
], {
versions: [undefined, 2, 3],
}));
(0, chai_1.expect)(await xAllocationVotingV3.version()).to.equal("3");
await emissions.setXAllocationsGovernorAddress(await xAllocationVotingV3.getAddress());
(0, chai_1.expect)(await emissions.xAllocationsGovernor()).to.eql(await xAllocationVotingV3.getAddress());
await xAllocationPool.setXAllocationVotingAddress(await xAllocationVotingV3.getAddress());
(0, chai_1.expect)(await xAllocationPool.xAllocationVoting()).to.eql(await xAllocationVotingV3.getAddress());
await xAllocationPool.setEmissionsAddress(await emissions.getAddress());
(0, chai_1.expect)(await xAllocationPool.emissions()).to.eql(await emissions.getAddress());
// Grant Vote registrar role to XAllocationVoting
await voterRewards
.connect(owner)
.grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), await xAllocationVotingV3.getAddress());
// Grant admin role to voter rewards for registering x allocation voting
await xAllocationVotingV3
.connect(owner)
.grantRole(await xAllocationVotingV3.DEFAULT_ADMIN_ROLE(), emissions.getAddress());
//Set the emissions address and the admin as the ROUND_STARTER_ROLE in XAllocationVoting
const roundStarterRole = await xAllocationVotingV3.ROUND_STARTER_ROLE();
await xAllocationVotingV3
.connect(owner)
.grantRole(roundStarterRole, await emissions.getAddress())
.then(async (tx) => await tx.wait());
await xAllocationVotingV3
.connect(owner)
.grantRole(roundStarterRole, owner.address)
.then(async (tx) => await tx.wait());
const user1 = otherAccounts[0];
const user2 = otherAccounts[1];
const user3 = otherAccounts[2];
// fund wallets
await (0, helpers_1.getVot3Tokens)(user1, "1000");
await (0, helpers_1.getVot3Tokens)(user2, "1000");
await (0, helpers_1.getVot3Tokens)(user3, "1000");
// add apps
const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address));
const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[3].address));
const app3Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[4].address));
await x2EarnApps
.connect(creator1)
.submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI");
await x2EarnApps
.connect(creator2)
.submitApp(otherAccounts[3].address, otherAccounts[3].address, otherAccounts[3].address, "metadataURI");
await x2EarnApps
.connect(creator3)
.submitApp(otherAccounts[4].address, otherAccounts[4].address, otherAccounts[4].address, "metadataURI");
await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[2]);
await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[3]);
await (0, xnodes_1.endorseApp)(app3Id, otherAccounts[4]);
// Grant minter role to emissions contract
await b3tr.connect(owner).grantRole(await b3tr.MINTER_ROLE(), await emissions.getAddress());
// Bootstrap emissions
await emissions.connect(minterAccount).bootstrap();
// start round
await emissions.connect(minterAccount).start();
(0, chai_1.expect)(await xAllocationVotingV3.currentRoundId()).to.equal(1n);
// make people vote
await xAllocationVotingV3.connect(user1).castVote(1, [app1Id], [hardhat_1.ethers.parseEther("100")]);
await xAllocationVotingV3
.connect(user2)
.castVote(1, [app1Id, app2Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("200")]);
// Upgrade to V4 (using the V3 contract address)
const xAllocationVotingV4 = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV3", "XAllocationVotingV4", await xAllocationVotingV3.getAddress(), [], {
version: 4,
}));
const xAllocationVotingV5 = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV4", "XAllocationVotingV5", await xAllocationVotingV4.getAddress(), // Use V4's address
[], {
version: 5,
}));
(0, chai_1.expect)(await xAllocationVotingV5.version()).to.equal("5");
// check that round is ok
(0, chai_1.expect)(await xAllocationVotingV5.currentRoundId()).to.equal(1n);
(0, chai_1.expect)(await xAllocationVotingV5.state(1n)).to.equal(0n); // Active
(0, chai_1.expect)(await xAllocationVotingV5.hasVoted(1, user1.address)).to.be.true;
(0, chai_1.expect)(await xAllocationVotingV5.hasVoted(1, user2.address)).to.be.true;
(0, chai_1.expect)(await xAllocationVotingV5.hasVoted(1, user3.address)).to.be.false;
(0, chai_1.expect)(await xAllocationVotingV5.getAppVotes(1, app1Id)).to.equal(hardhat_1.ethers.parseEther("200"));
(0, chai_1.expect)(await xAllocationVotingV5.getAppVotes(1, app2Id)).to.equal(hardhat_1.ethers.parseEther("200"));
(0, chai_1.expect)(await xAllocationVotingV5.getAppVotes(1, app3Id)).to.equal(hardhat_1.ethers.parseEther("0"));
// check that can still vote on the new round
await xAllocationVotingV5.connect(user3).castVote(1, [app1Id], [hardhat_1.ethers.parseEther("100")]);
(0, chai_1.expect)(await xAllocationVotingV5.getAppVotes(1, app1Id)).to.equal(hardhat_1.ethers.parseEther("300"));
// check that round is over correctly
const blockNextCycle = await emissions.getNextCycleBlock();
await (0, helpers_1.waitForBlock)(Number(blockNextCycle));
(0, chai_1.expect)(await emissions.isCycleEnded(1)).to.be.true;
await emissions.distribute();
(0, chai_1.expect)(await xAllocationVotingV4.currentRoundId()).to.equal(2n);
// check that rewards are distributed correctly
await (0, chai_1.expect)(xAllocationPool.claim(1, app1Id)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationPool.claim(1, app2Id)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationPool.claim(1, app3Id)).to.not.be.reverted;
// can cast vote for round 2
await xAllocationVotingV4.connect(user1).castVote(2, [app1Id], [hardhat_1.ethers.parseEther("100")]);
// Upgrade to V5 (using the V4 contract address)
const xAllocationVotingV6 = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV5", "XAllocationVotingV6", await xAllocationVotingV4.getAddress(), // Use V4's address
[], {
version: 6,
}));
(0, chai_1.expect)(await xAllocationVotingV6.version()).to.equal("6");
// check that round is ok
(0, chai_1.expect)(await xAllocationVotingV6.currentRoundId()).to.equal(2n);
(0, chai_1.expect)(await xAllocationVotingV6.state(2n)).to.equal(0n); // Active
(0, chai_1.expect)(await xAllocationVotingV6.hasVoted(1, user1.address)).to.be.true;
(0, chai_1.expect)(await xAllocationVotingV6.hasVoted(1, user2.address)).to.be.true;
(0, chai_1.expect)(await xAllocationVotingV6.hasVoted(1, user3.address)).to.be.true;
// Update these expectations to match the actual state after user3's vote
(0, chai_1.expect)(await xAllocationVotingV6.getAppVotes(1, app1Id)).to.equal(hardhat_1.ethers.parseEther("300"));
(0, chai_1.expect)(await xAllocationVotingV6.getAppVotes(1, app2Id)).to.equal(hardhat_1.ethers.parseEther("200"));
(0, chai_1.expect)(await xAllocationVotingV6.getAppVotes(1, app3Id)).to.equal(hardhat_1.ethers.parseEther("0"));
// check that can still vote on the new round
await xAllocationVotingV6.connect(user3).castVote(2, [app1Id], [hardhat_1.ethers.parseEther("100")]);
(0, chai_1.expect)(await xAllocationVotingV6.getAppVotes(2, app1Id)).to.equal(hardhat_1.ethers.parseEther("200"));
// check that round is over correctly
await (0, helpers_1.waitForBlock)(Number(await emissions.getNextCycleBlock()));
(0, chai_1.expect)(await emissions.isCycleEnded(2)).to.be.true;
await emissions.distribute();
(0, chai_1.expect)(await xAllocationVotingV6.currentRoundId()).to.equal(3n);
// check that rewards are distributed correctly
await (0, chai_1.expect)(xAllocationPool.claim(2, app1Id)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationPool.claim(2, app2Id)).to.not.be.reverted;
await (0, chai_1.expect)(xAllocationPool.claim(2, app3Id)).to.not.be.reverted;
// can cast vote for round 3
await xAllocationVotingV6.connect(user1).castVote(3, [app1Id], [hardhat_1.ethers.parseEther("100")]);
// Upgrade to V7 (using the V6 contract address)
const xAllocationVotingV7 = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV6", "XAllocationVotingV7", await xAllocationVotingV6.getAddress(), // Use V6's address
[], {
version: 7,
}));
(0, chai_1.expect)(await xAllocationVotingV7.version()).to.equal("7");
// check that round is ok
(0, chai_1.expect)(await xAllocationVotingV7.currentRoundId()).to.equal(3n);
(0, chai_1.expect)(await xAllocationVotingV7.state(3n)).to.equal(0n); // Active
const xAllocLibs = await (0, xAllocationVotingLibraries_1.xAllocationVotingLibraries)();
// Latest version
const xAllocationVoting = (await (0, helpers_2.upgradeProxy)("XAllocationVotingV7", "XAllocationVoting", await xAllocationVotingV7.getAddress(), // Use V7's address
[], {
version: 8,
libraries: {
AutoVotingLogic: await xAllocLibs.AutoVotingLogic.getAddress(),
ExternalContractsUtils: await xAllocLibs.ExternalContractsUtils.getAddress(),
VotingSettingsUtils: await xAllocLibs.VotingSettingsUtils.getAddress(),
VotesUtils: await xAllocLibs.VotesUtils.getAddress(),
VotesQuorumFractionUtils: await xAllocLibs.VotesQuorumFractionUtils.getAddress(),
RoundEarningsSettingsUtils: await xAllocLibs.RoundEarningsSettingsUtils.getAddress(),
RoundFinalizationUtils: await xAllocLibs.RoundFinalizationUtils.getAddress(),
RoundsStorageUtils: await xAllocLibs.RoundsStorageUtils.getAddress(),
RoundVotesCountingUtils: await xAllocLibs.RoundVotesCountingUtils.getAddress(),
},
}));
(0, chai_1.expect)(await xAllocationVoting.version()).to.equal("10");
});
});
(0, mocha_1.describe)("Settings", function () {
(0, mocha_1.describe)("General settings", function () {
(0, mocha_1.it)("Contract should not be able to receive ether", async function () {
const { xAllocationVoting, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false });
await (0, chai_1.expect)(owner.sendTransaction({
to: await xAllocationVoting.getAddress(),
value: hardhat_1.ethers.parseEther("1.0"), // Sends exactly 1.0 ether
})).to.be.reverted;
(0, chai_1.expect)(await hardhat_1.ethers.provider.getBalance(await xAllocationVoting.getAddress())).to.eql(0n);
});
(0, mocha_1.describe)("emissions address", function () {
(0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set a new emissions contract correctly", async function () {
const { xAllocationVoting, owner, emissions } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
await (0, helpers_1.bootstrapAndStartEmissions)();
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.be.true;
const oldEmissionsAddress = await emissions.getAddress();
const emissionsSetIface = new hardhat_1.ethers.Interface([
"event EmissionsSet(address oldContractAddress, address newContractAddress)",
]);
const receipt = await (await xAllocationVoting.connect(owner).setEmissionsAddress(owner.address)).wait();
let emissionsSetEvent;
for (const log of receipt?.logs ?? []) {
try {
const p = emissionsSetIface.parseLog({ topics: [...log.topics], data: log.data });
if (p !== null && p.name === "EmissionsSet") {
emissionsSetEvent = p;
break;
}
}
catch {
/* ignore non-matching logs */
}
}
(0, chai_1.expect)(emissionsSetEvent).to.not.be.undefined;
(0, chai_1.expect)(emissionsSetEvent.args.oldContractAddress).to.equal(oldEmissionsAddress);
(0, chai_1.expect)(emissionsSetEvent.args.newContractAddress).to.equal(owner.address);
});
(0, mocha_1.it)("Cannot set a new emissions contract to zero address", async function () {
const { xAllocationVoting, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
await (0, helpers_1.bootstrapAndStartEmissions)();
await (0, chai_1.expect)(xAllocationVoting.connect(owner).setEmissionsAddress(helpers_1.ZERO_ADDRESS)).to.be.reverted;
});
(0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE should be able to set a new emissions contract", async function () {
const { xAllocationVoting, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.be.false;
await (0, chai_1.expect)(xAllocationVoting.connect(otherAccount).setEmissionsAddress(otherAccount.address)).to.be.reverted;
});
});
(0, mocha_1.describe)("x2EarnApps address", function () {
(0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set x2EarnApps address correctly", async function () {
const { xAllocationVoting, owner, x2EarnApps } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.be.true;
const oldX2EarnAppsAddress = await x2EarnApps.getAddress();
const tx = await xAllocationVoting.connect(owner).setX2EarnAppsAddress(owner.address);
const x2EarnAppsSetIface = new hardhat_1.ethers.Interface([
"event X2EarnAppsSet(address oldContractAddress, address newContractAddress)",
]);
const receipt = await tx.wait();
let x2EarnAppsSetEvent;
for (const log of receipt?.logs ?? []) {
try {
const p = x2EarnAppsSetIface.parseLog({ topics: [...log.topics], data: log.data });
if (p !== null && p.name === "X2EarnAppsSet") {
x2EarnAppsSetEvent = p;
break;
}
}
catch {
/* ignore non-matching logs */
}
}
(0, chai_1.expect)(x2EarnAppsSetEvent).to.not.be.undefined;
(0, chai_1.expect)(x2EarnAppsSetEvent.args.oldContractAddress).to.equal(oldX2EarnAppsAddress);
(0, chai_1.expect)(x2EarnAppsSetEvent.args.newContractAddress).to.equal(owner.address);
});
(0, mocha_1.it)("Cannot set x2EarnApps address to zero address", async function () {
const { xAllocationVoting, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
await (0, chai_1.expect)(xAllocationVoting.connect(owner).setX2EarnAppsAddress(helpers_1.ZERO_ADDRESS)).to.be.reverted;
});
(0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set x2EarnApps address", async function () {
const { xAllocationVoting, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.be.false;
await (0, chai_1.expect)(xAllocationVoting.connect(otherAccount).setX2EarnAppsAddress(otherAccount.address)).to.be
.reverted;
});
});
(0, mocha_1.describe)("VoterRewards address", function () {
(0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set voter rewards address correctly", async function () {
const { xAllocationVoting, owner, voterRewards } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
(0, chai_1.expect)(await xAllocationVoting.hasRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.be.true;
const oldVoterRewardsAddress = await voterRewards.getAddress();
const tx = await xAllocationVoting.connect(owner).setVoterRewardsAddress(owner.address);
const voterRewardsSetIface = new hardhat_1.ethers.Interface([
"event VoterRewardsSet(address oldContractAddress, address newContractAddress)",
]);
const receipt = await tx.wait();
let voterRewardsSetEvent;
for (const log of receipt?.logs ?? []) {
try {
const p = voterRewardsSetIface.parseLog({ topics: [...log.topics], data: log.data });
if (p !== null && p.name === "VoterRewardsSet") {
voterRewardsSetEvent = p;
break;
}
}
catch {
/* ignore non-matching logs */
}
}
(0, chai_1.expect)(voterRewardsSetEvent).to.not.be.undefined;
(0, chai_1.expect)(voterRewardsSetEvent.args.oldContractAddress).to.equal(oldVoterRewardsAddress);
(0, chai_1.expect)(voterRewardsSetEvent.args.newContractAddress).to.equal(owner.address);
});
(0, mocha_1.it)("Cannot set voter rewards address to zero address", async function () {
const { xAllocationVoting, owner } = await (0, helpers_1.getOrDeployContractInstances)({
forceDeploy: true,
});
await (0, chai_1.expect)(xAllocationVoting.connect(owner).setVoterRewardsAddress(helpers_1.ZERO_ADDRESS)).to.be.reverted;
});
(0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can