Core Contracts

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

Vote Amplification Through Post-Vote Token Withdrawal

Summary

The governance system is vulnerable to vote manipulation through post-vote token withdrawals. Users can amplify their voting power by withdrawing tokens after casting votes, as the system uses current total supply for quorum calculations while keeping votes at their original value.

Vulnerability Details

The vulnerability exists because:

Votes are recorded at their full value when cast and remain unchanged:

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
uint256 weight = _veToken.getVotingPower(msg.sender);
proposalVote.hasVoted[msg.sender] = true;
if (support) {
proposalVote.forVotes += weight;
} else {
proposalVote.againstVotes += weight;
}
}

But quorum is calculated using current total supply:

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

Success conditions check:

return currentQuorum >= requiredQuorum &&
proposalVote.forVotes > proposalVote.againstVotes;

Example Attack:

Initial state:
- Alice: 100 votes
- Bob: 100 votes
- Charlie: 150 votes
- Total: 350 votes
- Required for pass: 179 votes (51% of 350)
Attack sequence:
1. Alice votes YES (100)
2. Bob votes YES (100)
- Total YES = 200 votes
3. Charlie votes NO (150)
- Total NO = 150 votes
4. Bob withdraws their 100 tokens
- New total supply = 250 tokens
- YES votes still = 200
- NO votes still = 150
Proposal passes because:
- Meets quorum (350 votes > 179)
- YES votes (200) > NO votes (150)
Attack succeeded because:
- At voting time: YES = 57% (200/350)
- After withdrawal: YES = 80% (200/250)
- Votes counted at full value despite withdrawal

Impact

  • Allows malicious actors to artificially inflate their voting power

  • Undermines the democratic process of governance

  • Makes quorum requirements ineffective as a security measure

  • Could lead to proposals passing with artificially inflated support

Tools Used

  • Manual code review

Recommendations

Snapshot total supply at proposal creation:

struct ProposalCore {
uint256 snapshotTotalSupply; // Add this
// ... other fields
}
function propose(...) {
// ... other code
proposal.snapshotTotalSupply = _veToken.getTotalVotingPower();
}
function quorum(uint256 proposalId) public view returns (uint256) {
return (proposals[proposalId].snapshotTotalSupply * quorumNumerator) / QUORUM_DENOMINATOR;
}
  • Lock token withdrawals during active votes:

function withdraw(uint256 amount) external {
require(!hasActiveVotes(msg.sender), "Cannot withdraw with active votes");
// ... withdrawal logic
}
Updates

Lead Judging Commences

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

Governance::quorum uses current total voting power instead of proposal creation snapshot, allowing manipulation of threshold requirements to force proposals to pass or fail

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

Governance::quorum uses current total voting power instead of proposal creation snapshot, allowing manipulation of threshold requirements to force proposals to pass or fail

Support

FAQs

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