Core Contracts

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

Missing quorum snapshot in `Governance::state` enables proposal state manipulation

Description

The Governance::state function calculates quorum requirements using the current total voting power instead of a snapshot taken at proposal creation. This allows manipulation of proposal outcomes by altering the total voting power after proposal creation but before state determination.

Proof of Concept

  1. Create proposal when total voting power is high (20M supply → 800k quorum)

  2. Cast votes below initial quorum (700k < 800k)

  3. Before voting ends, total voting power decreases through token withdrawals

  4. The reduced total voting power lowers the absolute quorum requirement

  5. Original votes now exceed the new quorum threshold

Add test in Governance.test.js:

it("manipulates proposal state through total voting power changes", async () => {
// Set HIGH initial total supply (20M) for 800k quorum (4%)
await veToken.mock_setTotalSupply(ethers.parseEther("20000000"));
await veToken.mock_setVotingPower(owner.address, PROPOSAL_THRESHOLD);
// Create proposal
const tx = await governance
.connect(owner)
.propose(
[testTarget.target],
[0],
[testTarget.interface.encodeFunctionData("setValue", [42])],
"Quorum Test",
0
);
const receipt = await tx.wait();
const event = receipt.logs.find((l) => l.fragment.name === "ProposalCreated");
const proposalId = event.args.proposalId;
// Advance to voting period
await time.increase(VOTING_DELAY);
// Cast votes totaling 700k (below initial 800k quorum)
await veToken.mock_setVotingPower(user1.address, ethers.parseEther("700000"));
await governance.connect(user1).castVote(proposalId, true);
// DECREASE total supply to 10M before voting ends (new quorum = 400k)
await veToken.mock_setTotalSupply(ethers.parseEther("10000000"));
await time.increase(VOTING_PERIOD);
// Now 700k votes > 400k quorum (should succeed)
expect(await governance.state(proposalId)).to.equal(ProposalState.Succeeded);
});

Impact

High Severity - Allows attackers to:

  • Force through proposals that should have failed by manipulating total supply

  • Invalidate voter intentions through post-creation quorum changes

  • Destabilize governance outcomes with timing-based attacks

Recommendation

  • Snapshot quorum at proposal creation:

contracts/core/governance/proposals/Governance.sol
struct ProposalCore {
// ... existing fields ...
+ uint256 quorumAtCreation;
}
function propose(...) external {
// ... existing code ...
_proposals[proposalId] = ProposalCore({
// ... existing fields ...
+ quorumAtCreation: quorum()
});
}
function state(uint256 proposalId) public view {
// ... existing code ...
- uint256 requiredQuorum = quorum();
+ uint256 requiredQuorum = proposal.quorumAtCreation;
}
  • Store historical total supply per proposal

  • Use block-based lookups for quorum:

function quorum(uint256 proposalId) public view {
return _veToken.getPastTotalSupply(proposal.startTime);
}
Updates

Lead Judging Commences

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

Give us feedback!