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