Core Contracts

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

Quorum calculation uses current total voting power instead of snapshot at proposal creation, allowing manipulation of proposal execution

Summary

The quorum() function uses the current total voting power (_veToken.getTotalVotingPower()) instead of taking a snapshot at proposal creation time. This allows malicious actors to manipulate proposal execution by minting or burning veRAAC tokens after voting ends.

Vulnerability Details

The quorum() function calculates the required quorum based on the current total voting power:

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

This creates two critical issues:

  1. A proposal that reached quorum during voting period may become unexecutable if veRAAC tokens are minted after voting ends, increasing the required quorum

  2. A proposal that didn't reach quorum during voting can become executable later if veRAAC tokens are burned, decreasing the required quorum

Impact

High severity as this allows direct manipulation of governance proposal execution:

  • Malicious actors can prevent execution of passed proposals by minting veRAAC tokens

  • Failed proposals can be made executable by burning veRAAC tokens

  • Core protocol governance functionality can be subverted

  • Breaks key invariant that proposal outcome should be determined only by votes during voting period

Tools Used

Manual review

Proof of Concept

Add the following test case to the test/unit/core/governance/proposals/Governance.test.js file:

it("quorum manipulation prevents execution", async () => {
// Setup voting power
await veToken.mock_setVotingPower(await user1.getAddress(), ethers.parseEther("6000000"));
const startTime = await moveToNextTimeframe();
expect(await governance.state(proposalId)).to.equal(ProposalState.Active);
// Cast vote
await governance.connect(user1).castVote(proposalId, true);
expect(await governance.state(proposalId)).to.equal(ProposalState.Active);
// Wait for voting period to end
await time.increaseTo(startTime + VOTING_PERIOD);
await network.provider.send("evm_mine");
// Verify state is Succeeded
expect(await governance.state(proposalId)).to.equal(ProposalState.Succeeded);
// Mint more veRAAC tokens
await veToken.mint(user1.address, ethers.parseEther("1000000000000"));
// Try to queue the proposal but it fails because of the quorum changed
await expect(governance.execute(proposalId))
.to.be.revertedWithCustomError(governance, "InvalidProposalState");
// Verify state is Defeated
expect(await governance.state(proposalId)).to.equal(ProposalState.Defeated);
});

Recommendations

Store the total voting power at proposal creation time and use it for quorum calculations:

struct ProposalCore {
// ... existing fields ...
+ uint256 snapshotTotalVotingPower; // Add this field
}
function propose(...) external override returns (uint256) {
// ... existing code ...
+ proposal.snapshotTotalVotingPower = _veToken.getTotalVotingPower();
// ... rest of code ...
}
function quorum(uint256 proposalId) public view override returns (uint256) {
- return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
+ return (_proposals[proposalId].snapshotTotalVotingPower * quorumNumerator) / QUORUM_DENOMINATOR;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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 4 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.