Core Contracts

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

Denial‑of‑Vote via Unrestricted `recordVote(...)` Function

Overview

The veRAACToken contract includes a recordVote function for marking users as having voted on a given proposal:

function recordVote(
address voter,
uint256 proposalId
) external {
if (_hasVotedOnProposal[voter][proposalId]) revert AlreadyVoted();
_hasVotedOnProposal[voter][proposalId] = true;
uint256 power = getVotingPower(voter);
emit VoteCast(voter, proposalId, power);
}

However -> no access control or validation ensures that the caller is either:

  • The governance contract, or

  • The voter themselves, or

  • Any other authorized entity.

Instead, any address can call recordVote(...) for any voter on any proposalId. This allows a malicious actor to front-run or forcibly “mark” another user as having voted—without that user’s consent.

Attack Path / PoC

  1. Malicious Caller Identifies a Target and Proposal:

    • Suppose user victim has not yet voted on proposalId.

    • The attacker calls:

      veRAACToken.recordVote(victim, proposalId);
    • Because _hasVotedOnProposal[victim][proposalId] is initially false, the function sets _hasVotedOnProposal[victim][proposalId] to true.

  2. Denial of Actual Vote:

    • When victim later attempts to cast a real vote (likely through the legitimate governance flow), the contract checks:

      if (_hasVotedOnProposal[victim][proposalId]) revert AlreadyVoted();
    • Because it is now true, the call reverts with AlreadyVoted().

    • The victim is thereby blocked from submitting their legitimate vote.

  3. Impact on Governance:

    • The victim’s voting power is effectively nullified for that proposal.

    • The attacker, by calling recordVote preemptively, denies victim from casting a proper vote.

    • This is especially impactful for close votes, or in systems where large amounts of voting power are concentrated.

Consequences

  • Governance Disruption: Attackers can systematically eliminate particular users from voting, skewing outcomes.

  • User Confusion and Frustration: The victim receives an AlreadyVoted() error when trying to vote, despite never having cast a vote.

Recommended Fix

Add access control or other validation to recordVote. Common approaches include:

  1. Restrict Calls to an Authorized Governance Contract:

    address public governanceContract;
    modifier onlyGovernance() {
    require(msg.sender == governanceContract, "Not governance");
    _;
    }
    function recordVote(address voter, uint256 proposalId) external onlyGovernance {
    // ...
    }
  2. Check that msg.sender == voter:
    If the design intends for the user themselves to directly call recordVote, you can require:

    require(msg.sender == voter, "Caller must be voter");
  3. Handle Voting in an External Governance Contract:
    Typically, the veToken contract only provides the means to retrieve a user’s voting power. The governance contract itself tracks who has voted and calls getVotingPower(...) or getPastVotes(...). In that pattern, recordVote(...) may not be needed at all.

Updates

Lead Judging Commences

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

veRAACToken::recordVote lacks access control, allowing anyone to emit fake events

Support

FAQs

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

Give us feedback!