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