Core Contracts

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

Voting Power Manipulation Due to Lack of Snapshot Mechanism

Summary

The Governance contract determines voting power at the time of voting rather than using a snapshot taken at proposal creation. This allows voters to temporarily increase their voting power to influence proposals, then if needed , reduce it afterward (through emergency withdrawal) , undermining the fairness and security of the governance process.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/proposals/Governance.sol

Vulnerability Details

The contract calls _veToken.getVotingPower(msg.sender) during voting, which returns the voter’s current voting power. There is no mechanism to snapshot voting power at the time of proposal creation or voting start.

problematic implementation

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
// ...
uint256 weight = _veToken.getVotingPower(msg.sender); // <-- Current Voting Power
// ...
}
Attack scenario
  1. Proposal #789 is created with a 7-day voting period.

  2. Attacker deposits tokens to increase their voting power on Day 6.

  3. Attacker votes with maximum power on the same Day 6.

  4. Attacker uses the emergencyWithdraw() function in the veRAACToken contract to withdraws tokens after the 3 day emergency delay period , reducing their voting power.

  5. Proposal outcome is influenced by the attacker’s temporary stake.

Impact

Governance Attacks: Malicious actors can sway critical decisions

Tools Used

manual review

Recommendations

  1. Add Snapshot Timestamp to Proposal Data

Modify the ProposalCore struct to include a snapshot timestamp:

struct ProposalCore {
// ... existing fields ...
uint256 snapshotTimestamp; // Timestamp for voting power snapshot
}
  1. Set Snapshot on Proposal Creation

During proposal creation, store the snapshot timestamp;

function propose(...) external {
// ... existing logic ...
_proposals[proposalId].snapshotTimestamp = block.timestamp - 1;
// ...
}
  1. Use Historical Voting Power in Votes

Update castVote() to use the snapshot timestamp:

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
ProposalCore storage proposal = _proposals[proposalId];
uint256 weight = _veToken.getVotingPower(msg.sender, proposal.snapshotTimestamp);
if (weight == 0) revert NoVotingPower(msg.sender, block.number);
// ... voting logic ...
}
Updates

Lead Judging Commences

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