Core Contracts

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

Lack of Canceled Proposal Check in castVote Leads to Irrecoverable Voting Power Loss

Summary

In the castVote function there is no check to disallow users to vote on already canceled proposals. Users who vote on canceled proposal permenently losses their voting powers as their weights are locked and counted in the vote totals for a proposal that can no longer be executed.

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L170-L211

The castVote function allows users to vote on proposals without checking if the proposal has been canceled. When a proposal is canceled, it should no longer be eligible for voting. However, the current implementation does not enforce this rule.

Impact

Users losses their voting powers making them unable to vote on other active proposals.

Tools Used

Manual review

Recommendations

/**
* @notice Casts a vote on a governance proposal
* @dev Allows veToken holders to vote on active proposals
* - One vote per proposal per address
* - Vote weight based on veToken voting power
* - Supports yes/no voting
* - Voting window between start and end times
* @param proposalId The ID of the proposal to vote on
* @param support True to vote in favor, false to vote against
* @return weight The voting power used for this vote
*/
//@audit can i vote on a canceled proposals?
//@audit-high
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);
}
if (proposal.canceled) {
revert ProposalCanceled(proposalId);
}
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 7 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 7 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.

Give us feedback!