Core Contracts

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

`Governance::castVote` Allows Voting on Cancelled and Executed Proposals

Summary

Governance::castVote function lacks validation checks to prevent voting on proposals that have been cancelled or executed. As a result, users can continue casting votes on proposals that are no longer active.

Vulnerability Details

In the castVote function, the contract only validates:

  • Proposal exists

  • Voting window timing (start/end)

  • User hasn't already voted

  • User has voting power

Failing to check if the proposal has been canceled or executed.

[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L181-L194)

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);
}
// Missing checks for executed/cancelled status
...

Proof of Code:
Add this test to Governance.test.js

it.only("should not allow voting on canceled proposal", async () => {
// Initial setup - mint tokens to user1
await veToken.mock_setInitialVotingPower(
await user1.getAddress(),
ethers.parseEther("100000")
);
// Create proposal
const targets = [await testTarget.getAddress()];
const values = [42];
const calldatas = [testTarget.interface.encodeFunctionData("setValue", [42])];
const description = "Test Proposal";
await governance.connect(user1).propose(
targets,
values,
calldatas,
description,
0 // ParameterChange
);
// Move time forward 4 days
await time.increase(4 * 24 * 3600);
// Cancel the proposal
await governance.connect(user1).cancel(0);
// Try to vote - should revert but doesnt making test fail
await expect(
governance.connect(user1).castVote(0, true)
).to.be.reverted;
});

Impact

  • Vote tallies may continue to increase after a proposal is executed/cancelled

  • Creates misleading governance metrics and confused users

  • Users can waste gas attempting to vote on proposals that are no longer active

Tools Used

Manual Review

Foundry

Recommendations

Add validation checks in Governance::castVote function to prevent voting on executed or 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 (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.executed) revert ProposalAlreadyExecuted(proposalId);
+ if (proposal.canceled) revert ProposalCanceled(proposalId);
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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 3 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.