Core Contracts

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

If quorum is lowered expired proposals can be executed

Summary

3 years ago a critical bug was found OZ governor regarding quorums. The vulnerability was quickly fixed by OZ.

The same exact issue is present in our codebase too.

Vulnerability Details

For a vote to be queued or executed he needs to have more FOR than AGAINST votes, but before that it would need to first pass the quorum. Bellow we can see that quorum is veTotalSupply * quorumNumerator / 100, where quorumNumerator can be 2 and up to 20, with 4 as default.

function state(uint256 proposalId) public view override returns (ProposalState) {
// ...
uint256 requiredQuorum = quorum();
if (currentQuorum < requiredQuorum || proposalVote.forVotes <= proposalVote.againstVotes) {
return ProposalState.Defeated;
}
bytes32 id = _timelock.hashOperationBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
proposal.descriptionHash
);
if (_timelock.isOperationPending(id)) {
return ProposalState.Queued;
}
return ProposalState.Succeeded;
}
function quorum() public view override returns (uint256) {
return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
}

In order for votes to pass we first need 4% (by default) of voting power to have been voted on this proposal.

However, if proposals don't pass this threshold nothing happens to them, they just sit. They are not active and cannot be voted FOR or AGAINST, but with a move in quorumNumerator they can "pass" and be queued.

Example:

  1. Current quorum threshold is 10%

  2. There is not that much activity and a vote reaches 9% with 60% FOR and 40 AGAINST

  3. Since it didn't reach quorum it didn't get queued or executed

  4. After one year the activity has increased drastically

  5. A new proposal passes for the quorum to be lowered to 8%

  6. The first proposal gets executed by a malicious user and the funds get send to a contract that is long dead

Impact

Old, long gone proposals, that have not reached their quorum can be queued and executed.
This is of course dangerous as 1 or 2 or 3 yaer old proposals might send funds to contracts that aren't active anymore.

Tools Used

Manual review

Recommendations

Add an expiry to proposals, for example after they have finished they have 1 month to be queued or they will be expired.

if (block.timestamp < proposal.startTime) return ProposalState.Pending;
if (block.timestamp < proposal.endTime) return ProposalState.Active;
+ if (block.timestamp > proposal.endTime + 1 months) return ProposalState.Defeated; // it can be also called expired, just that this is already used
Updates

Lead Judging Commences

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

Governance allows execution of previously-defeated proposals if quorum requirements are later lowered, enabling unexpected resurrection of old proposals

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

Governance allows execution of previously-defeated proposals if quorum requirements are later lowered, enabling unexpected resurrection of old proposals

Support

FAQs

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

Give us feedback!