Core Contracts

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

Governance Takeover Through Quorum Validation Bypass

Summary

The governance system allows proposals to transition to a successful state without properly validating quorum requirements. This creates a vulnerability where minority voters can push through proposals against the protocol's intended democratic process.

From Governance.sol#quorum

function quorum() public view override returns (uint256) {
return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
}

In Governance.sol, the _isProposalSuccessful() function performs an incomplete validation:

function _isProposalSuccessful(uint256 proposalId) internal view returns (bool) {
ProposalVote storage proposalVote = _proposalVotes[proposalId];
uint256 currentQuorum = proposalVote.forVotes + proposalVote.againstVotes;
uint256 requiredQuorum = quorum();
// Validation exists but isn't properly enforced through state transitions
return currentQuorum >= requiredQuorum &&
proposalVote.forVotes > proposalVote.againstVotes;
}

The vulnerability manifests when:

  • Total votes fall below required quorum (4% of total voting power)

  • A proposal receives majority support from this insufficient voter base

  • The system transitions the proposal to "Succeeded" state

The validation gap in execute()

function execute(uint256 proposalId) external override nonReentrant {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.executed) revert ProposalAlreadyExecuted(proposalId, block.timestamp);
ProposalState currentState = state(proposalId);
// Critical: state() function relies on _isProposalSuccessful() which has incomplete quorum validation
if (currentState == ProposalState.Succeeded) {
// Proposal can be queued without proper quorum verification
_queueProposal(proposalId);
} else if (currentState == ProposalState.Queued) {
// Once queued, proposal can be executed regardless of original quorum requirements
_executeProposal(proposalId);
}
}

Vulnerability Details

Imagine a town hall meeting where decisions require 40% attendance to be valid. Now picture someone holding a meeting at 3 AM with just 5% of residents, yet their decisions become binding. This is exactly what's happening in RAAC's governance system.

The governance contract's _isProposalSuccessful() function acts as the meeting moderator, but it's not properly checking attendance. When checking if a proposal passes, it looks at whether more people voted "yes" than "no", but fails to enforce the critical 4% quorum requirement (defined in quorumNumerator).

function _isProposalSuccessful(uint256 proposalId) internal view returns (bool) {
ProposalVote storage proposalVote = _proposalVotes[proposalId];
// Calculate total participation by summing all votes
uint256 totalVotes = proposalVote.forVotes + proposalVote.againstVotes;
// Early return if quorum not met - prevents state transition to Succeeded
if (totalVotes < quorum()) return false;
// Only check majority after quorum is confirmed
return proposalVote.forVotes > proposalVote.againstVotes;
}

Impact

A malicious actor could execute protocol-changing proposals with as little as 0.1% of total voting power. For perspective, in a protocol with 1 million veRAAC tokens, decisions affecting everyone could be made with just 1,000 tokens.

The Attack Flow

An attacker waits for a period of low voter engagement, perhaps during a holiday or market downturn. They create a proposal to, for example, redirect protocol fees to their wallet. With just a small fraction of votes, they can transition the proposal to Succeeded state and execute it through the timelock.

This proposal would pass despite having only 0.15% participation, far below the intended 4% threshold.

Total veRAAC Supply: 1,000,000
Required Quorum (4%): 40,000
Attacker's Votes: 1,000
Supporting Votes: 500
Against Votes: 100

This proposal would pass despite having only 0.15% participation, far below the intended 4% threshold.

Recommendations

maintain the protocol's democratic requirements by enforcing both participation thresholds and majority consensus before allowing proposals to advance.

function _isProposalSuccessful(uint256 proposalId) internal view returns (bool) {
ProposalVote storage proposalVote = _proposalVotes[proposalId];
// Calculate total participation by summing all votes
uint256 totalVotes = proposalVote.forVotes + proposalVote.againstVotes;
// Early return if quorum not met - prevents state transition to Succeeded
if (totalVotes < quorum()) return false;
// Only check majority after quorum is confirmed
return proposalVote.forVotes > proposalVote.againstVotes;
}
  1. Explicit quorum check before any success determination

  2. Proper order of validation (quorum first, then majority)

  3. Early return pattern prevents proposals from succeeding without sufficient participation

  4. Integration with existing state transition checks in execute() function

Updates

Lead Judging Commences

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

Support

FAQs

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