UNPKG

@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
"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"); }); }); });