Core Contracts

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

castVote() uses current voting power instead of past snapshot

Description

castVote() uses _veToken.getVotingPower() which gets the CURRENT voting power, rather than _veToken.getVotingPowerForProposal() which gets the voting power at the proposal's snapshot block. This is incorrect - votes should use the snapshotted voting power to prevent vote manipulation through token purchases after a proposal is created. The code ought to be:

/**
* @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
*/
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);
}
ProposalVote storage proposalVote = _proposalVotes[proposalId];
if (proposalVote.hasVoted[msg.sender]) {
revert AlreadyVoted(proposalId, msg.sender, block.timestamp);
}
- uint256 weight = _veToken.getVotingPower(msg.sender);
+ uint256 weight = _veToken.getVotingPowerForProposal(msg.sender, proposalId);
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;
}

Impact

Users can increase their voting power after a proposal has been created and use that to vote. The reverse is also true i.e. a user who had some voting power when the proposal was created may not have it after a few days (within the votingPeriod of 7 days while the voting is still open) & hence loses the opportunity to rightfully exercise their vote.

Note that another variation to this is:

  • User has voting power and votes.

  • Before the proposal voting ends, user's locked tokens expire.

  • The expired tokens can be transferred to another wallet, then locked once again and used for voting on the same proposal, leading to double-voting.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.castVote uses current voting power instead of proposal creation snapshot, enabling vote manipulation through token transfers and potential double-voting

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.castVote uses current voting power instead of proposal creation snapshot, enabling vote manipulation through token transfers and potential double-voting

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.