Core Contracts

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

Users can still cast their votes on a canceled proposal in Governance.sol::castVote()

Summary

Users can still cast their votes on a canceled proposal, since there is lack of a check for a canceled proposal.

Vulnerability Details

The castVote() function does not check if a proposal has been canceled, allowing users to cast votes.
If canceled early, users could still cast their votes until block.timestamp > proposal.endTime

Impact

Voting on a canceled proposal should not be possible, as the proposal is no longer valid. It simply defeats the purpose of canceling a proposal.

PoC with instruction run test instructions:
  1. Place the provided code in test/unit/core/governance/Governance.test.js

  2. Execute the test with this command: npx hardhat test --grep "should not allow to cancel and then castVote, but it happens"

describe("POCS", () => {
it("should not allow to cancel and then castVote, but it happens", async () => {
const targets = [await testTarget.getAddress()];
const values = [0];
const calldatas = [testTarget.interface.encodeFunctionData("setValue", [42])];
const description = "Test Proposal";
// we create the proposal
const tx = await governance.connect(user1).propose(
targets,
values,
calldatas,
description,
0 // ParameterChange
);
const receipt = await tx.wait();
const event = receipt.logs.find(
log => governance.interface.parseLog(log)?.name === 'ProposalCreated'
);
const proposalID = event.args.proposalId;
expect(await governance.state(proposalID)).to.equal(ProposalState.Pending);
await logProposalState(governance, proposalID);
const fiveDays = 5 * 24 * 60 * 60;
await time.increase(fiveDays);
await network.provider.send("evm_mine");
expect(await governance.state(proposalID)).to.equal(ProposalState.Active);
await logProposalState(governance, proposalID);
// we cancel the proposal
await expect(governance.connect(user1).cancel(proposalID)).to.emit(governance, "ProposalCanceled");
// now we castVote with user1
await expect(governance.connect(user1).castVote(proposalID, true)).to.emit(governance, "VoteCast");
})
})

Tools Used

Manual

Recommendations

Add a check to see if a proposal is canceled.
Create a new custom error if you want.

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);
}
+ require(!proposal.canceled, "Proposal has been canceled.")
+ // if (proposal.canceled) revert YourNewCustomError()
...
}
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.