Core Contracts

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

Governance: Missing Voting Power Snapshotting

Summary

The Governance contract allows users to create proposals and vote using their current voting power (token balance) instead of a historical snapshot. This enables attackers to manipulate governance decisions by temporarily increasing their token holdings (e.g., via flash loans, short-term staking) and then withdrawing after voting. Without snapshotting, governance actions do not reflect genuine long-term stakeholder interests.

Code Snippet

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L134

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L196

Vulnerability Details

Example Scenario :

Attacker Borrows Tokens: Alice borrows a large amount of veRAAC tokens via a flash loan or temporary staking.

Create Proposal/Vote: Alice uses her inflated voting power to create a malicious proposal (e.g., drain funds) or vote on a critical decision.

Return Tokens: After the proposal/vote is submitted, Alice returns the borrowed tokens.

Code Proof:

In Governance.sol, proposals and votes use current balances (getVotingPower):

function propose(...) external override {
uint256 proposerVotes = _veToken.getVotingPower(msg.sender); // No snapshot
if (proposerVotes < proposalThreshold) revert InsufficientProposerVotes(...);
...
}
function castVote(...) external override {
uint256 weight = _veToken.getVotingPower(msg.sender); // No snapshot
...
}

Attack Simulation:

  • At block N, Alice has 100 veRAAC.

  • At block N+1, she borrows 1,000,000 veRAAC via a flash loan.

  • At block N+2, she creates a proposal requiring 500,000 veRAAC.

  • At block N+3, she repays the loan.

Result: Proposal is created with transient voting power, even though Alice never held the tokens long-term.

Impact

Governance Attacks: Malicious proposals (e.g., fund drainage, parameter manipulation) can pass with transient voting power.

Loss of Trust: Legitimate users lose faith in governance if decisions are made by short-term token holders.

Financial Loss: A proposal to mint unlimited tokens could devalue RAAC.

Tools Used

Manual review

Recommendations

Implement snapshotting to record voting power at the time of proposal creation/voting.

Step 1: Modify Governance Contract

Use getPastVotingPower to check balances at a fixed historical block:

// Governance.sol
function propose(...) external override {
// Use voting power from the previous block to prevent flash loan manipulation
uint256 proposerVotes = _veToken.getPastVotingPower(msg.sender, block.number - 1);
if (proposerVotes < proposalThreshold) revert InsufficientProposerVotes(...);
...
}
function castVote(...) external override {
// Use voting power from the proposal's start block
uint256 weight = _veToken.getPastVotingPower(msg.sender, proposal.startTime);
...
}

Step 2: Ensure Voting Token Supports Snapshots
Modify the veRAAC token contract (e.g., inherit OpenZeppelin’s ERC20Snapshot):

// veRAACToken.sol
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
contract veRAACToken is ERC20Snapshot {
function snapshot() public onlyOwner {
_snapshot();
}
// Track voting power with snapshots
function getPastVotingPower(address account, uint256 blockNumber) public view returns (uint256) {
return getPastBalanceOf(account, blockNumber);
}
}

Step 3: Enforce Snapshotting in Governance Parameters

Automatically trigger a snapshot when a proposal is created.

Add a delay between proposal creation and voting to prevent last-minute manipulation.

// Governance.sol
function propose(...) external override {
_veToken.snapshot(); // Capture voting power at current block
uint256 snapshotBlock = block.number;
...
emit ProposalCreated(..., snapshotBlock);
}
function castVote(...) external override {
require(block.number >= proposal.startBlock + 1, "Voting delay active");
uint256 weight = _veToken.getPastVotingPower(msg.sender, proposal.snapshotBlock);
...
}

Why This Fix Works

Snapshotting: Voting power is locked at a historical block, preventing flash loans or temporary staking from influencing governance.

Time Delays: A mandatory delay between proposal creation and voting ensures all participants have equal information.

Updates

Lead Judging Commences

inallhonesty Lead Judge
5 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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