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
12 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!