Summary
Cancel Function can be called with the already cancelled prosposal ID.
Vulnerability Details
Function cancel is used to cancels active proposals but due to no check user can call already canceled state proposal id this is check effect Interaction
function cancel(uint256 proposalId) external override {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.startTime == 0) revert ProposalDoesNotExist(proposalId);
ProposalState currentState = state(proposalId);
if (currentState == ProposalState.Executed || currentState == ProposalState.Canceled) {
revert InvalidProposalState(proposalId, currentState, ProposalState.Active, "Cannot cancel executed proposal");
}
if (msg.sender != proposal.proposer &&
_veToken.getVotingPower(proposal.proposer) >= proposalThreshold) {
revert InsufficientProposerVotes(proposal.proposer,
_veToken.getVotingPower(proposal.proposer), proposalThreshold, "Proposer lost required voting power");
}
proposal.canceled = true;
emit ProposalCanceled(proposalId, msg.sender, "Proposal canceled by proposer");
}
Impact
cancel Function can be called with already cancel proposal Id.
Tools Used
Manual View
POC
it("should cancel Cancelled 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);
await time.increaseTo(startTime + VOTING_PERIOD);
await network.provider.send("evm_mine");
await governance.execute(proposalId);
expect(await governance.state(proposalId)).to.equal(ProposalState.Queued);
const timelockDelay = await timelock.getMinDelay();
await time.increase(timelockDelay);
await network.provider.send("evm_mine");
await governance.cancel(proposalId);
expect(await governance.state(proposalId)).to.equal(ProposalState.Canceled);
await governance.cancel(proposalId);
});
Recommendations
add Check in cancel() function
if (currentState == ProposalState.Executed || currentState == ProposalState.Canceled) {
revert InvalidProposalState(proposalId, currentState, ProposalState.Active, "Cannot cancel executed proposal");
}