UNPKG

@vechain/vebetterdao-contracts

Version:

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

708 lines 255 kB
import { ethers } from "hardhat"; import { assert, expect } from "chai"; import { createProposal, getOrDeployContractInstances, getProposalIdFromTx, getVot3Tokens, waitForNextBlock, waitForVotingPeriodToEnd, catchRevert, waitForProposalToBeActive, participateInGovernanceVoting, bootstrapAndStartEmissions, waitForCurrentRoundToEnd, moveBlocks, createProposalAndExecuteIt, createProposalWithMultipleFunctionsAndExecuteIt, payDeposit, bootstrapEmissions, ZERO_ADDRESS, waitForQueuedProposalToBeReady, waitForNextCycle, getEventName, moveToCycle, } from "./helpers"; import { describe, it } from "mocha"; import { createLocalConfig } from "@repo/config/contracts/envs/local"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { B3TRGovernor__factory, } from "../typechain-types"; import { deployAndUpgrade, deployProxy } from "../scripts/helpers"; import { GRANT_PROPOSAL_TYPE, STANDARD_PROPOSAL_TYPE } from "./governance/fixture.test"; describe("Governor and TimeLock - @shard4a1", function () { describe("Governor deployment", function () { it("Should set constructors correctly", async function () { const config = createLocalConfig(); const { governor, vot3, b3tr, owner, timeLock, xAllocationVoting, voterRewards } = await getOrDeployContractInstances({ forceDeploy: true, }); await bootstrapAndStartEmissions(); const votingPeriod = await governor.votingPeriod(); const minVotingDelay = await governor.minVotingDelay(); expect(votingPeriod).to.eql(await xAllocationVoting.votingPeriod()); expect(minVotingDelay.toString()).to.eql(config.B3TR_GOVERNOR_MIN_VOTING_DELAY.toString()); const xAllocationVotingAddress = await governor.xAllocationVoting(); const voterRewardsAddress = await governor.voterRewards(); expect(xAllocationVotingAddress).to.eql(await xAllocationVoting.getAddress()); expect(voterRewardsAddress).to.eql(await voterRewards.getAddress()); // proposers votes should be 0 const clock = await governor.clock(); const proposerVotes = await governor.getVotes(owner, (clock - BigInt(1)).toString()); expect(proposerVotes.toString()).to.eql("0"); // check name of the governor contract const name = await governor.name(); expect(name).to.eql("B3TRGovernor"); // check that the VOT3 address is correct const voteTokenAddress = await governor.token(); expect(voteTokenAddress).to.eql(await vot3.getAddress()); // check that the TimeLock address is correct const timeLockAddress = await governor.timelock(); expect(timeLockAddress).to.eql(await timeLock.getAddress()); // clock mode is set correctly const clockMode = await governor.CLOCK_MODE(); expect(clockMode.toString()).to.eql("mode=blocknumber&from=default"); // check version const version = await governor.version(); expect(version).to.eql("10"); // STANDARD deposit threshold is set correctly const standardDepositThreshold = await governor.depositThresholdPercentageByProposalType(STANDARD_PROPOSAL_TYPE); expect(standardDepositThreshold.toString()).to.eql(config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD.toString()); // GRANT deposit threshold is set correctly const grantDepositThreshold = await governor.depositThresholdPercentageByProposalType(GRANT_PROPOSAL_TYPE); expect(grantDepositThreshold.toString()).to.eql(config.B3TR_GOVERNOR_GRANT_DEPOSIT_THRESHOLD.toString()); // STANDARD voting threshold is set correctly const standardVotingThreshold = await governor.votingThresholdByProposalType(STANDARD_PROPOSAL_TYPE); expect(standardVotingThreshold.toString()).to.eql(config.B3TR_GOVERNOR_VOTING_THRESHOLD.toString()); // GRANT voting threshold is set correctly const grantVotingThreshold = await governor.votingThresholdByProposalType(GRANT_PROPOSAL_TYPE); expect(grantVotingThreshold.toString()).to.eql(config.B3TR_GOVERNOR_GRANT_VOTING_THRESHOLD.toString()); // counting mode is set correctly const countingMode = await governor.COUNTING_MODE(); expect(countingMode.toString()).to.eql("support=bravo&quorum=for,abstain,against"); // should be unpaused const paused = await governor.paused(); expect(paused).to.be.false; // b3tr address is set correctly const b3trAddress = await governor.b3tr(); expect(b3trAddress).to.eql(await b3tr.getAddress()); }); it("Should be able to upgrade the governor contract through governance", async function () { const { governor, owner, otherAccount, b3tr, emissions, xAllocationVoting, vot3, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, veBetterPassport, } = await getOrDeployContractInstances({ forceDeploy: true, }); // Start emissions await bootstrapAndStartEmissions(); // Deploy the implementation contract const Contract = await ethers.getContractFactory("B3TRGovernorV1", { libraries: { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), }, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); await veBetterPassport.whitelist(otherAccount.address); await veBetterPassport.toggleCheck(1); // V1 Contract const V1Contract = await ethers.getContractAt("B3TRGovernor", await governor.getAddress()); // Now we can create a proposal const encodedFunctionCall = V1Contract.interface.encodeFunctionData("upgradeToAndCall", [ await implementation.getAddress(), "0x", ]); const description = "Upgrading Governance contracts"; const descriptionHash = ethers.keccak256(ethers.toUtf8Bytes(description)); const currentRoundId = await xAllocationVoting.currentRoundId(); const tx = await governor .connect(owner) .propose([await governor.getAddress()], [0], [encodedFunctionCall], description, currentRoundId + 1n, 0, { gasLimit: 10000000, }); const proposalId = await getProposalIdFromTx(tx); // Pay the proposal deposit const deposit = await governor.proposalDepositThreshold(proposalId); await getVot3Tokens(owner, ethers.formatEther(deposit)); await vot3.connect(owner).approve(await governor.getAddress(), ethers.parseEther(deposit.toString())); await governor.connect(owner).deposit(deposit, proposalId); await getVot3Tokens(otherAccount, "10000"); await waitForProposalToBeActive(proposalId); await governor.connect(otherAccount).castVote(proposalId, 1); await waitForVotingPeriodToEnd(proposalId); expect(await governor.state(proposalId)).to.eql(4n); // succeded await governor.queue([await governor.getAddress()], [0], [encodedFunctionCall], descriptionHash); expect(await governor.state(proposalId)).to.eql(5n); await governor.execute([await governor.getAddress()], [0], [encodedFunctionCall], descriptionHash); expect(await governor.state(proposalId)).to.eql(6n); await governor.connect(owner).withdraw(proposalId, owner.address); await vot3.connect(owner).approve(await governor.getAddress(), ethers.parseEther("1000")); const newImplAddress = await getImplementationAddress(ethers.provider, await governor.getAddress()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); // Blacklist the old upgradeToAndCall function const funcSig = V1Contract.interface.getFunction("upgradeToAndCall")?.selector; // Check that the new implementation works const newGovernor = Contract.attach(await governor.getAddress()); const tx1 = await newGovernor.connect(owner).setWhitelistFunction(b3tr, funcSig, true); // whitelist the function for b3tr contract const receipt = await tx1.wait(); const name = getEventName(receipt, newGovernor); expect(name).to.eql("FunctionWhitelisted"); // start new round await emissions.distribute(); // create a new proposal const newTx = await newGovernor .connect(owner) .propose([await b3tr.getAddress()], [0], [encodedFunctionCall], description, (await xAllocationVoting.currentRoundId()) + 1n, ethers.parseEther("1000"), { gasLimit: 10000000, }); const proposeReceipt = await newTx.wait(); const event = proposeReceipt?.logs[3]; const decodedLogs = newGovernor.interface.parseLog({ topics: [...event?.topics], data: event ? event.data : "", }); const newProposalId = decodedLogs?.args[0]; expect(newProposalId).to.exist; // expect data of previous contract to be untouched expect(await governor.state(proposalId)).to.eql(6n); expect(await governor.quorumReached(proposalId)).to.eql(true); }); it("Should be able to upgrade the governor contract through governance when libraries change", async function () { const { governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); const { governor, owner, otherAccount, xAllocationVoting, vot3, veBetterPassport } = await getOrDeployContractInstances({ forceDeploy: true, }); await veBetterPassport.whitelist(otherAccount.address); await veBetterPassport.toggleCheck(1); // Start emissions await bootstrapAndStartEmissions(); // Deploy the implementation contract const Contract = await ethers.getContractFactory("B3TRGovernorV1", { libraries: { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), }, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); // V1 Contract const V1Contract = await ethers.getContractAt("B3TRGovernor", await governor.getAddress()); // Now we can create a proposal const encodedFunctionCall = V1Contract.interface.encodeFunctionData("upgradeToAndCall", [ await implementation.getAddress(), "0x", ]); const description = "Upgrading Governance contracts"; const descriptionHash = ethers.keccak256(ethers.toUtf8Bytes(description)); const currentRoundId = await xAllocationVoting.currentRoundId(); const tx = await governor .connect(owner) .propose([await governor.getAddress()], [0], [encodedFunctionCall], description, currentRoundId + 1n, 0, { gasLimit: 10000000, }); const proposalId = await getProposalIdFromTx(tx); // Pay the proposal deposit const deposit = await governor.proposalDepositThreshold(proposalId); await getVot3Tokens(owner, ethers.formatEther(deposit)); await vot3.connect(owner).approve(await governor.getAddress(), ethers.parseEther(deposit.toString())); await governor.connect(owner).deposit(deposit, proposalId); await getVot3Tokens(otherAccount, "10000"); await waitForProposalToBeActive(proposalId); await governor.connect(otherAccount).castVote(proposalId, 1); await waitForVotingPeriodToEnd(proposalId); expect(await governor.state(proposalId)).to.eql(4n); // succeded await governor.queue([await governor.getAddress()], [0], [encodedFunctionCall], descriptionHash); expect(await governor.state(proposalId)).to.eql(5n); await governor.execute([await governor.getAddress()], [0], [encodedFunctionCall], descriptionHash); expect(await governor.state(proposalId)).to.eql(6n); await governor.connect(owner).withdraw(proposalId, owner.address); await vot3.connect(owner).approve(await governor.getAddress(), ethers.parseEther("1000")); const newImplAddress = await getImplementationAddress(ethers.provider, await governor.getAddress()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); // Check that the new implementation works const newGovernor = Contract.attach(await governor.getAddress()); expect(await newGovernor.quorumDenominator()).to.equal(100); }); it("Only governance can upgrade the governor contract", async function () { const { governor, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); await expect(governor.connect(otherAccount).upgradeToAndCall(otherAccount.address, "0x")).to.be.reverted; }); it("Should be able to initialize only once", async function () { const config = createLocalConfig(); const { b3tr, owner, vot3, timeLock, xAllocationVoting, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy Governor const governorV1 = (await deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })); await catchRevert(governorV1.initialize({ vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: 1, // quorum percentage initialDepositThreshold: 1, // voting threshold initialMinVotingDelay: 1, // delay before vote starts initialVotingThreshold: 1, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, })); }); it("Should not be able to set function whitelist if not governance nor admin", async function () { const { governor, otherAccount, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await catchRevert(governor.connect(otherAccount).setWhitelistFunction(governor, "0x12345678", true)); await governor.connect(owner).setWhitelistFunction(governor, "0x12345678", false); // Does not revert as owner is the admin }); it("Should not be able to call setIsFunctionRestrictionEnabled if not governance nor admin", async function () { const { governor, otherAccount, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await catchRevert(governor.connect(otherAccount).setIsFunctionRestrictionEnabled(true)); await governor.connect(owner).setIsFunctionRestrictionEnabled(true); // Doesn't revert as owner is the admin }); it("Should not be able to set whitelist functions if not governance nor admin", async function () { const { governor, otherAccount, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await catchRevert(governor.connect(otherAccount).setWhitelistFunctions(await governor.getAddress(), ["0x12345678"], true)); await governor.connect(owner).setWhitelistFunctions(await governor.getAddress(), ["0x12345678"], false); // Admin can perform the onlyAdminOrGovernance restricted method }); it("Should revert if the governor admin is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { b3tr, owner, vot3, timeLock, xAllocationVoting, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: ZERO_ADDRESS, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should revert if the governor timelock is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { b3tr, owner, vot3, xAllocationVoting, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: ZERO_ADDRESS, xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should revert if the B3TR address is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { owner, vot3, timeLock, xAllocationVoting, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: ZERO_ADDRESS, quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should revert if the VOT3 address is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { b3tr, owner, timeLock, xAllocationVoting, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: ZERO_ADDRESS, timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should revert if the xAllocationVoting address is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { b3tr, owner, vot3, timeLock, voterRewards, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: ZERO_ADDRESS, b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should revert if the voterRewards address is set to the zero address during initilization", async function () { const config = createLocalConfig(); const { b3tr, owner, vot3, timeLock, xAllocationVoting, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("B3TRGovernorV1", [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, // quorum percentage initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, // deposit threshold initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, // delay before vote starts initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, // voting threshold voterRewards: ZERO_ADDRESS, isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), })).to.be.reverted; }); it("Should not have state conflict after upgrading to V4 and V5", async () => { const config = createLocalConfig(); const { owner, b3tr, timeLock, voterRewards, vot3, timelockAdmin, xAllocationVoting, governorClockLogicLib, governorConfiguratorLib, governorDepositLogicLib, governorFunctionRestrictionsLogicLib, governorProposalLogicLib, governorQuorumLogicLib, otherAccount, governorStateLogicLib, governorVotesLogicLib, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, governorClockLogicLibV3, governorConfiguratorLibV3, governorDepositLogicLibV3, governorFunctionRestrictionsLogicLibV3, governorProposalLogicLibV3, governorQuorumLogicLibV3, governorStateLogicLibV3, governorVotesLogicLibV3, governorClockLogicLibV4, governorConfiguratorLibV4, governorDepositLogicLibV4, governorFunctionRestrictionsLogicLibV4, governorProposalLogicLibV4, governorQuorumLogicLibV4, governorStateLogicLibV4, governorVotesLogicLibV4, governorClockLogicLibV5, governorConfiguratorLibV5, governorDepositLogicLibV5, governorFunctionRestrictionsLogicLibV5, governorProposalLogicLibV5, governorQuorumLogicLibV5, governorStateLogicLibV5, governorVotesLogicLibV5, veBetterPassport, } = await getOrDeployContractInstances({ forceDeploy: true, }); await veBetterPassport.toggleCheck(4); // Deploy Governor const governorV4 = (await deployAndUpgrade(["B3TRGovernorV1", "B3TRGovernorV2", "B3TRGovernorV3", "B3TRGovernorV4", "B3TRGovernorV5"], [ [ { vot3Token: await vot3.getAddress(), timelock: await timeLock.getAddress(), xAllocationVoting: await xAllocationVoting.getAddress(), b3tr: await b3tr.getAddress(), quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE, initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD, initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY, initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD, voterRewards: await voterRewards.getAddress(), isFunctionRestrictionEnabled: true, }, { governorAdmin: owner.address, pauser: owner.address, contractsAddressManager: owner.address, proposalExecutor: owner.address, governorFunctionSettingsRoleAddress: owner.address, }, ], [], [], [await veBetterPassport.getAddress()], [], ], { versions: [undefined, 2, 3, 4, 5], libraries: [ { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), }, { GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(), GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(), GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(), GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(), GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(), GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(), GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(), GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(), }, { GovernorClockLogicV3: await governorClockLogicLibV3.getAddress(), GovernorConfiguratorV3: await governorConfiguratorLibV3.getAddress(), GovernorDepositLogicV3: await governorDepositLogicLibV3.getAddress(), GovernorFunctionRestrictionsLogicV3: await governorFunctionRestrictionsLogicLibV3.getAddress(), GovernorProposalLogicV3: await governorProposalLogicLibV3.getAddress(), GovernorQuorumLogicV3: await governorQuorumLogicLibV3.getAddress(), GovernorStateLogicV3: await governorStateLogicLibV3.getAddress(), GovernorVotesLogicV3: await governorVotesLogicLibV3.getAddress(), }, { GovernorClockLogicV4: await governorClockLogicLibV4.getAddress(), GovernorConfiguratorV4: await governorConfiguratorLibV4.getAddress(), GovernorDepositLogicV4: await governorDepositLogicLibV4.getAddress(), GovernorFunctionRestrictionsLogicV4: await governorFunctionRestrictionsLogicLibV4.getAddress(), GovernorProposalLogicV4: await governorProposalLogicLibV4.getAddress(), GovernorQuorumLogicV4: await governorQuorumLogicLibV4.getAddress(), GovernorStateLogicV4: await governorStateLogicLibV4.getAddress(), GovernorVotesLogicV4: await governorVotesLogicLibV4.getAddress(), }, { GovernorClockLogicV5: await governorClockLogicLibV5.getAddress(), GovernorConfiguratorV5: await governorConfiguratorLibV5.getAddress(), GovernorDepositLogicV5: await governorDepositLogicLibV5.getAddress(), GovernorFunctionRestrictionsLogicV5: await governorFunctionRestrictionsLogicLibV5.getAddress(), GovernorProposalLogicV5: await governorProposalLogicLibV5.getAddress(), GovernorQuorumLogicV5: await governorQuorumLogicLibV5.getAddress(), GovernorStateLogicV5: await governorStateLogicLibV5.getAddress(), GovernorVotesLogicV5: await governorVotesLogicLibV5.getAddress(), }, ], })); const b3trGovernorFactory = await ethers.getContractFactory("B3TRGovernorV5", { libraries: { GovernorClockLogicV5: await governorClockLogicLibV5.getAddress(), GovernorConfiguratorV5: await governorConfiguratorLibV5.getAddress(), GovernorDepositLogicV5: await governorDepositLogicLibV5.getAddress(), GovernorFunctionRestrictionsLogicV5: await governorFunctionRestrictionsLogicLibV5.getAddress(), GovernorProposalLogicV5: await governorProposalLogicLibV5.getAddress(), GovernorQuorumLogicV5: await governorQuorumLogicLibV5.getAddress(), GovernorStateLogicV5: await governorStateLogicLibV5.getAddress(), GovernorVotesLogicV5: await governorVotesLogicLibV5.getAddress(), }, }); await voterRewards .connect(owner) .grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), await governorV4.getAddress()); // Grant Roles const PROPOSER_ROLE = await timeLock.PROPOSER_ROLE(); const EXECUTOR_ROLE = await timeLock.EXECUTOR_ROLE(); const CANCELLER_ROLE = await timeLock.CANCELLER_ROLE(); await timeLock.connect(timelockAdmin).grantRole(PROPOSER_ROLE, await governorV4.getAddress()); await timeLock.connect(timelockAdmin).grantRole(EXECUTOR_ROLE, await governorV4.getAddress()); await timeLock.connect(timelockAdmin).grantRole(CANCELLER_ROLE, await governorV4.getAddress()); // first add to the whitelist const funcSig = governorV4.interface.getFunction("updateQuorumNumerator")?.selector; await governorV4.connect(owner).setWhitelistFunction(await governorV4.getAddress(), funcSig, true); const funcSig2 = governorV4.interface.getFunction("upgradeToAndCall")?.selector; await governorV4.connect(owner).setWhitelistFunction(await governorV4.getAddress(), funcSig2, true); const newQuorum = 10n; // load votes await getVot3Tokens(owner, "30000"); await waitForNextBlock(); await bootstrapAndStartEmissions(); const roundId = ((await xAllocationVoting.currentRoundId()) + 1n).toString(); const address = await governorV4.getAddress(); const encodedFunctionCall = b3trGovernorFactory.interface.encodeFunctionData("updateQuorumNumerator", [newQuorum]); const tx0 = await governorV4 .connect(owner) .propose([address], [0], [encodedFunctionCall], "Update Quorum Percentage", roundId.toString(), 0, { gasLimit: 10000000, }); const proposalId = await getProposalIdFromTx(tx0); const proposalThreshold = await governorV4.proposalDepositThreshold(proposalId); await getVot3Tokens(otherAccount, ethers.formatEther(proposalThreshold)); // We also need to wait a block to update the proposer's votes snapshot await waitForNextBlock(); await vot3.connect(otherAccount).approve(await governorV4.getAddress(), proposalThreshold); await governorV4.connect(otherAccount).deposit(proposalThreshold, proposalId); let proposalState = await governorV4.state(proposalId); // proposal id of the proposal in the beforeAll step if (proposalState.toString() !== "1") await moveToCycle(parseInt((await governorV4.proposalStartRound(proposalId)).toString()) + 1); // vote await governorV4.connect(owner).castVote(proposalId, 1, { gasLimit: 10000000 }); // vote for const deadline = await governorV4.proposalDeadline(proposalId); const currentBlock = await governorV4.clock(); await moveBlocks(parseInt((deadline - currentBlock + BigInt(1)).toString())); const descriptionHash = ethers.keccak256(ethers.toUtf8Bytes("Update Quorum Percentage")); await governorV4.queue([address], [0], [encodedFunctionCall], descriptionHash, { gasLimit: 10000000, }); await waitForNextBlock(); await governorV4.execute([address], [0], [encodedFunctionCall], descriptionHash, { gasLimit: 10000000, }); const updatedQuorum = await governorV4["quorumNumerator()"](); expect(updatedQuorum).to.eql(newQuorum); const initialSlot = BigInt("0xd09a0aaf4ab3087bae7fa25ef74ddd4e5a4950980903ce417e66228cf7dc7b00"); // Slot 0 of VoterRewards let storageSlots = []; for (let i = initialSlot; i < initialSlot + BigInt(50); i++) { storageSlots.push(await ethers.provider.getStorage(await governorV4.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000" && slot !== "0x0000000000000000000000000000000200000000000000000000000000000002"); // removing empty slots and slots that track governance proposals getting executed on the governor // Upgrade to V7 const ContractV7 = await ethers.getContractFactory("B3TRGovernor", { libraries: { GovernorClockLogic: await governorClockLogicLib.getAddress(), GovernorConfigurator: await governorConfiguratorLib.getAddress(), GovernorDepositLogic: await governorDepositLogicLib.getAddress(), GovernorFunctionRestrictionsLogic: await governorFunctionRestrictionsLogicLib.getAddress(), GovernorProposalLogic: await governorProposalLogicLib.getAddress(), GovernorQuorumLogic: await governorQuorumLogicLib.getAddress(), GovernorStateLogic: await governorStateLogicLib.getAddress(), GovernorVotesLogic: await governorVotesLogicLib.getAddress(), }, }); const implementationv5 = await ContractV7.deploy(); await implementationv5.waitForDeployment(); // Now we can create a proposal const tx5 = await governorV4.upgradeToAndCall(await implementationv5.getAddress(), "0x"); await tx5.wait(); const governorV7 = ContractV7.attach(await governorV4.getAddress()); let storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await ethers.provider.getStorage(await governorV7.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000" && slot !== "0x0000000000000000000000000000000200000000000000000000000000000002"); // removing empty slots and slots that track governance proposals getting executed on the governor // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { expect(storageSlots[i]).to.equal(storageSlotsAfter[i]); } }); }); describe("Governor settings", function () { let b3trGovernorFactory; this.beforeAll(async function () { const { governorClockLogicLib, governorConfiguratorLib, gov