@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
629 lines (628 loc) • 41.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const mocha_1 = require("mocha");
const fixture_test_1 = require("./fixture.test");
const typechain_types_1 = require("../../typechain-types");
const hardhat_1 = require("hardhat");
const chai_1 = require("chai");
const common_1 = require("../helpers/common");
(0, mocha_1.describe)("Governance - Mark in development/completed - @shard4k", function () {
let governor;
let vot3;
let b3tr;
let minterAccount;
let proposer;
let secondaryAccount;
let treasury;
let grantsManager;
let owner;
let voter;
let veBetterPassport;
let timeLock;
let grantsManagerAddress;
let treasuryAddress;
let emissions;
let xAllocationVoting;
let contractToPassToMethods;
let treasuryContract;
let grantsManagerInterface;
let governorProposalLogicInterface;
let governorInterface;
let otherAccounts;
let b3trContract;
(0, mocha_1.beforeEach)(async function () {
const fixture = await (0, fixture_test_1.setupGovernanceFixtureWithEmissions)();
governor = fixture.governor;
vot3 = fixture.vot3;
b3tr = fixture.b3tr;
minterAccount = fixture.minterAccount;
proposer = fixture.proposer;
secondaryAccount = fixture.otherAccount;
treasury = fixture.treasury;
grantsManager = fixture.grantsManager;
owner = fixture.owner;
voter = fixture.voter;
veBetterPassport = fixture.veBetterPassport;
timeLock = fixture.timeLock;
emissions = fixture.emissions;
xAllocationVoting = fixture.xAllocationVoting;
otherAccounts = fixture.otherAccounts;
// Setup proposer for all tests
await emissions.connect(minterAccount).start();
await (0, fixture_test_1.setupProposer)(proposer, b3tr, vot3, minterAccount);
await vot3.connect(proposer).approve(await governor.getAddress(), hardhat_1.ethers.parseEther("1000"));
grantsManagerAddress = await grantsManager.getAddress();
treasuryAddress = await treasury.getAddress();
treasuryContract = await hardhat_1.ethers.getContractFactory("Treasury");
contractToPassToMethods = {
b3tr,
vot3,
minterAccount,
governor,
treasury,
emissions,
xAllocationVoting,
veBetterPassport,
owner,
timeLock,
grantsManager,
};
grantsManagerInterface = typechain_types_1.GrantsManager__factory.createInterface();
governorProposalLogicInterface = typechain_types_1.GovernorProposalLogic__factory.createInterface();
governorInterface = typechain_types_1.B3TRGovernor__factory.createInterface();
b3trContract = await hardhat_1.ethers.getContractFactory("B3TR");
});
(0, mocha_1.describe)("State Transitions - Mark as IN-DEVELOPMENT", function () {
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should NOT be able to mark as IN-DEVELOPMENT from SUCCEEDED state - must execute first", async function () {
const functionToCall = "tokenDetails";
const encodedFunctionCall = b3trContract.interface.encodeFunctionData(functionToCall, []);
const targets = [await b3tr.getAddress()];
const values = [0];
const calldatas = [encodedFunctionCall];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create executable proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Deposit and support
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
await (0, common_1.waitForCurrentRoundToEnd)();
// Setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
// Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Start queue/execution round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Proposal should be succeeded
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(4); // Succeeded
// Should NOT be able to mark as IN-DEVELOPMENT from Succeeded state
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await (0, chai_1.expect)(governor.connect(owner).markAsInDevelopment(proposalId)).to.be.revertedWithCustomError(governor, "GovernorRestrictedProposal");
// State should remain Succeeded
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(4); // Succeeded
});
});
(0, mocha_1.describe)("Permissions - Mark as IN-DEVELOPMENT", function () {
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should NOT be able to mark as IN-DEVELOPMENT if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Random account should NOT be able to mark as IN-DEVELOPMENT
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsInDevelopment(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should NOT be able to mark as IN-DEVELOPMENT if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Random account should NOT be able to mark as IN-DEVELOPMENT
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsInDevelopment(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to mark as IN-DEVELOPMENT if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Random account should NOT be able to mark as IN-DEVELOPMENT
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsInDevelopment(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should be able to mark as IN-DEVELOPMENT if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should be able to mark as IN-DEVELOPMENT if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to mark as IN-DEVELOPMENT even with the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await (0, chai_1.expect)(governor.connect(owner).markAsInDevelopment(proposalId)).to.be.revertedWithCustomError(governor, "GovernorRestrictedProposal");
});
});
(0, mocha_1.describe)("Permissions - Mark as COMPLETED", function () {
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should NOT be able to mark as COMPLETED if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Random account should NOT be able to mark as COMPLETED
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsCompleted(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should NOT be able to mark as COMPLETED if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Random account should NOT be able to mark as COMPLETED
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsCompleted(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to mark as COMPLETED if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Random account should NOT be able to mark as COMPLETED
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).markAsCompleted(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should be able to mark as COMPLETED if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Should be able to mark as COMPLETED
await governor.connect(owner).markAsCompleted(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(9); // Completed
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should be able to mark as COMPLETED if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Should be able to mark as COMPLETED
await governor.connect(owner).markAsCompleted(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(9); // Completed
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to mark as COMPLETED even with the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Should NOT be able to mark as COMPLETED
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await (0, chai_1.expect)(governor.connect(owner).markAsInDevelopment(proposalId)).to.be.revertedWithCustomError(governor, "GovernorRestrictedProposal");
await (0, chai_1.expect)(governor.connect(owner).markAsCompleted(proposalId)).to.be.revertedWithCustomError(governor, "GovernorRestrictedProposal");
});
});
(0, mocha_1.describe)("Permissions - Reset Development State", function () {
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should NOT be able to reset development state if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Mark as IN-DEVELOPMENT first
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Random account should NOT be able to reset development state
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).resetDevelopmentState(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should NOT be able to reset development state if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Mark as IN-DEVELOPMENT first
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Random account should NOT be able to reset development state
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).resetDevelopmentState(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to reset development state if not the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Random account should NOT be able to reset development state
await (0, chai_1.expect)(governor.connect(otherAccounts[3]).resetDevelopmentState(proposalId)).to.be.reverted;
});
(0, mocha_1.it)("(TEXT-ONLY PROPOSAL) Should be able to reset development state if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const targets = [];
const values = [];
const calldatas = [];
const description = `description-${this.test?.title}`;
const startRoundId = (await (0, common_1.getRoundId)()) + 1;
// Create proposal
const tx = await governor.connect(proposer).propose(targets, values, calldatas, description, startRoundId, 0);
const proposalId = await (0, common_1.getProposalIdFromTx)(tx);
// Mint tokens and approve for spending in proposal deposit
const proposalDepositThreshold = await governor.proposalDepositThreshold(proposalId);
await (0, fixture_test_1.setupSupporter)(proposer, vot3, proposalDepositThreshold, governor);
await governor.connect(proposer).deposit(proposalDepositThreshold, proposalId);
//Since we create proposal already with full support, we can skip support phase
await (0, common_1.waitForCurrentRoundToEnd)();
// Before starting the voting round we should setup voters
await (0, fixture_test_1.setupVoter)(otherAccounts[0], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[1], b3tr, vot3, minterAccount, owner, veBetterPassport);
await (0, fixture_test_1.setupVoter)(otherAccounts[2], b3tr, vot3, minterAccount, owner, veBetterPassport);
//Start voting round
await (0, common_1.startNewAllocationRound)({ emissions, xAllocationVoting });
// Wait for proposal to be active
await (0, common_1.waitForProposalToBeActive)(proposalId, { governor });
// Vote
await governor.connect(otherAccounts[0]).castVote(proposalId, 1);
await governor.connect(otherAccounts[1]).castVote(proposalId, 1);
await governor.connect(otherAccounts[2]).castVote(proposalId, 1);
// Wait for voting period to end
await (0, common_1.waitForVotingPeriodToEnd)(proposalId);
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Should be able to reset development state
await governor.connect(owner).resetDevelopmentState(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(4); // Succeeded
});
(0, mocha_1.it)("(EXECUTABLE PROPOSAL) Should be able to reset development state if the PROPOSAL_STATE_MANAGER_ROLE", async function () {
//Create and execute a proposal doing a tokenDetails call
const executeTx = await (0, common_1.createProposalAndExecuteIt)(proposer, otherAccounts[0], b3tr, b3trContract, "description", "tokenDetails");
const receipt = await executeTx.wait();
if (!receipt)
throw new Error("No receipt");
// Get proposalId from ProposalExecuted event
const executedEvent = receipt.logs.find(log => {
try {
const parsed = governor.interface.parseLog({ topics: [...log.topics], data: log.data });
return parsed?.name === "ProposalExecuted";
}
catch {
return false;
}
});
if (!executedEvent)
throw new Error("ProposalExecuted event not found");
const proposalId = governor.interface.parseLog({
topics: [...executedEvent.topics],
data: executedEvent.data,
})?.args[0];
// Should be able to mark as IN-DEVELOPMENT
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await governor.connect(owner).markAsInDevelopment(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(8); // InDevelopment
// Should be able to reset development state
await governor.connect(owner).resetDevelopmentState(proposalId);
(0, chai_1.expect)(await governor.state(proposalId)).to.equal(6); // Executed
});
(0, mocha_1.it)("(GRANT PROPOSAL) Should NOT be able to reset development state even with the PROPOSAL_STATE_MANAGER_ROLE", async function () {
const description = "https://ipfs.io/ipfs/Qm..."; // project details metadata URI cannot be changed later
const milestonesDetailsMetadataURI = "https://ipfs.io/ipfs/Qm..."; // milestones details can be changed later
const values = [hardhat_1.ethers.parseEther("10000"), hardhat_1.ethers.parseEther("20000")];
//Create grant and execute it
const { proposalId } = await (0, common_1.createProposalWithMultipleFunctionsAndExecuteItGrant)(proposer, // proposer
owner, // voter
[treasury, treasury], // targets ( 2 transfers )
treasuryContract, // contract to pass to avoid re-deploying the contracts
description, // description ( will be empty in the proposal, because if modified, the proposalId and milestoneId will be modified => lost in the see)
["transferB3TR", "transferB3TR"], // functionToCall
[
[grantsManagerAddress, values[0]],
[grantsManagerAddress, values[1]],
], // args of transferb3tr
"0", // deposit amount
proposer.address, milestonesDetailsMetadataURI, // milestones
contractToPassToMethods);
// Should NOT be able to reset development state
// Because grants can never be in development by b3trgovernor
await governor.connect(owner).grantRole(await governor.PROPOSAL_STATE_MANAGER_ROLE(), owner.address);
await (0, chai_1.expect)(governor.connect(owner).resetDevelopmentState(proposalId)).to.be.revertedWithCustomError(governor, "GovernorUnexpectedProposalState");
});
});
});