Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Valid

Unrestricted Voting on Cancelled Proposals in `Governance::castVote` Function

Summary

The Governance::castVote function fails to check whether a proposal has been canceled before allowing votes. While it verifies the proposal's existence and voting period status, it does not account for the canceled flag. This allows users to vote on proposals that are no longer active, violating governance protocol rules.

Vulnerability Details

In the castVote function, the only checks performed are to ensure that the proposal exists, the voting window is open, and the user has not already voted. There is no verification of the proposal's cancellation status. For example, if a proposal is cancelled via the cancel() function (which sets proposal.canceled to true), a user can still invoke castVote on that proposal. The function would then record the vote as if the proposal were active, despite its cancelled state.

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.startTime == 0) revert ProposalDoesNotExist(proposalId);
if (block.timestamp < proposal.startTime) {
revert VotingNotStarted(proposalId, proposal.startTime, block.timestamp);
}
if (block.timestamp > proposal.endTime) {
revert VotingEnded(proposalId, proposal.endTime, block.timestamp);
}
ProposalVote storage proposalVote = _proposalVotes[proposalId];
if (proposalVote.hasVoted[msg.sender]) {
revert AlreadyVoted(proposalId, msg.sender, block.timestamp);
}
uint256 weight = _veToken.getVotingPower(msg.sender);
if (weight == 0) {
revert NoVotingPower(msg.sender, block.number);
}
proposalVote.hasVoted[msg.sender] = true;
if (support) {
proposalVote.forVotes += weight;
} else {
proposalVote.againstVotes += weight;
}
emit VoteCast(msg.sender, proposalId, support, weight, "");
return weight;
}

When a proposal is canceled via the cancel function, it sets proposal.canceled = true the state() function correctly returns ProposalState.Canceled for canceled proposals However, the castVote function only checks: Proposal existence (startTime != 0),Voting period timing (block.timestamp between startTime and endTime), Voter eligibility (has not already voted, has voting power)

Missing check for proposal.canceled allows voting on canceled proposals if within the original voting window.

POC:

  1. User A creates a proposal and later cancels it, setting proposal.canceled to true.

  2. User B then calls castVote on the cancelled proposal.

  3. Since there is no check for cancellation, User B's vote is accepted, leading to inaccurate vote tallies.

Impact

Votes cast on cancelled proposals may distort the final vote count and state determination, undermining the integrity of the governance process.

Tools Used

Manual Review

Recommendation

Modify the castVote function to include a check for proposal.canceled before allowing votes. If proposal.canceled is true, revert the transaction to prevent voting on cancelled proposals.

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.startTime == 0) revert ProposalDoesNotExist(proposalId);
+ if (proposal.canceled) revert ProposalCanceled(proposalId); // Added check for canceled proposals
if (block.timestamp < proposal.startTime) {
revert VotingNotStarted(proposalId, proposal.startTime, block.timestamp);
}
if (block.timestamp > proposal.endTime) {
revert VotingEnded(proposalId, proposal.endTime, block.timestamp);
}
ProposalVote storage proposalVote = _proposalVotes[proposalId];
if (proposalVote.hasVoted[msg.sender]) {
revert AlreadyVoted(proposalId, msg.sender, block.timestamp);
}
uint256 weight = _veToken.getVotingPower(msg.sender);
if (weight == 0) {
revert NoVotingPower(msg.sender, block.number);
}
proposalVote.hasVoted[msg.sender] = true;
if (support) {
proposalVote.forVotes += weight;
} else {
proposalVote.againstVotes += weight;
}
emit VoteCast(msg.sender, proposalId, support, weight, "");
return weight;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance::castVote lacks canceled/executed proposal check, allowing users to waste gas voting on proposals that can never be executed

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance::castVote lacks canceled/executed proposal check, allowing users to waste gas voting on proposals that can never be executed

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.