The ID of a proposal is generated by hashing its parameters, with the hash of the description used as a salt. However, if a recurring proposal needs to be executed at regular intervals with identical parameters, reusing the same description will result in the same proposal ID. As users vote on the proposal, reaching quorum will alter its state. Consequently, when the proposal is resubmitted, it will no longer be executable since its associated operation ID will no longer be active.
it("should execute successful proposal", async () => {
await veToken.mock_setVotingPower(await user1.getAddress(), ethers.parseEther("6000000"));
const startTime = await moveToNextTimeframe();
expect(await governance.state(proposalId)).to.equal(ProposalState.Active);
await governance.connect(user1).castVote(proposalId, true);
expect(await governance.state(proposalId)).to.equal(ProposalState.Active);
await time.increaseTo(startTime + VOTING_PERIOD);
await network.provider.send("evm_mine");
expect(await governance.state(proposalId)).to.equal(ProposalState.Succeeded);
await governance.execute(proposalId);
expect(await governance.state(proposalId)).to.equal(ProposalState.Queued);
const targets = [await testTarget.getAddress()];
const values = [0];
const calldatas = [
testTarget.interface.encodeFunctionData("setValue", [42])
];
const tx = await governance.connect(owner).propose(
targets,
values,
calldatas,
"Test Proposal",
0
);
const receipt = await tx.wait();
const event = receipt.logs.find(
log => governance.interface.parseLog(log)?.name === 'ProposalCreated'
);
proposalId = event.args.proposalId;
await time.increase(VOTING_DELAY);
await network.provider.send("evm_mine");
await governance.connect(user1).castVote(proposalId, true);
await time.increase(VOTING_PERIOD);
await network.provider.send("evm_mine");
const state = await governance.state(proposalId);
expect(state).to.equal(ProposalState.Queued);
});
Manual review.
Add randomness to the hash using for example network parameters such as the block number or the timestamp.