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 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!