1. Summary
A severity vulnerability exists in the governance contract’s parameter update mechanism. While the contract enforces basic bounds checking, it does not implement any rate limiting, allowing malicious actors to rapidly modify governance parameters. This could lead to:
Governance instability due to frequent parameter changes
Voting process manipulation by adjusting thresholds and timing
Potential flash loan governance attacks
2. Vulnerability Details
Severity: Medium
Likelihood: High
Impact: Governance Manipulation
The root cause is the absence of a rate-limiting mechanism in the setParameter
function:
function setParameter(GovernanceParameter param, uint256 newValue) external override onlyOwner {
ParameterConfig storage config = _parameters[param];
if (newValue < config.minValue || newValue > config.maxValue) {
if (param == GovernanceParameter.VotingDelay)
revert InvalidVotingDelay(newValue, config.minValue, config.maxValue);
if (param == GovernanceParameter.VotingPeriod)
revert InvalidVotingPeriod(newValue, config.minValue, config.maxValue);
if (param == GovernanceParameter.ProposalThreshold)
revert InvalidProposalThreshold(newValue, config.minValue, config.maxValue);
if (param == GovernanceParameter.QuorumNumerator)
revert InvalidQuorumNumerator(newValue, config.minValue, config.maxValue);
}
uint256 oldValue = config.value;
config.value = newValue;
if (param == GovernanceParameter.VotingDelay) {
votingDelay = newValue;
emit VotingDelaySet(oldValue, newValue, msg.sender);
} else if (param == GovernanceParameter.VotingPeriod) {
votingPeriod = newValue;
emit VotingPeriodSet(oldValue, newValue, msg.sender);
} else if (param == GovernanceParameter.ProposalThreshold) {
proposalThreshold = newValue;
emit ProposalThresholdSet(oldValue, newValue, msg.sender);
} else if (param == GovernanceParameter.QuorumNumerator) {
quorumNumerator = newValue;
emit QuorumNumeratorSet(oldValue, newValue, msg.sender);
}
}
Root Cause
No cooldown period between updates
No tracking of recent changes to prevent frequent modifications
No governance vote required before changing key parameters
Potential Exploit Scenarios
Lowering the ProposalThreshold
multiple times → Allows an attacker to spam proposals.
Shortening the VotingPeriod
→ Enables an attacker to rush votes before defenders react.
Increasing the QuorumNumerator
→ Makes it harder for legitimate proposals to pass.
Rapid cycling of VotingDelay
→ Causes confusion and unpredictability in governance.
3. Proof of Concept (PoC)
I write a Hardhat test demonstrates this vulnerability:
const { ethers } = require('hardhat');
const { expect } = require('chai');
describe('Parameter Gaming Vulnerability Test', function () {
let governance, owner, attacker;
const INITIAL_VOTING_DELAY = 86400;
beforeEach(async function () {
[owner, attacker] = await ethers.getSigners();
const Governance = await ethers.getContractFactory('Governance');
governance = await Governance.deploy(
ethers.constants.AddressZero,
ethers.constants.AddressZero
);
await governance.setParameter(0, INITIAL_VOTING_DELAY);
});
it('should demonstrate rapid parameter changes', async function () {
const initialDelay = await governance.votingDelay();
console.log('Initial voting delay:', initialDelay.toString());
await governance.setParameter(0, INITIAL_VOTING_DELAY / 2);
const delayAfterFirstChange = await governance.votingDelay();
console.log('Delay after first change:', delayAfterFirstChange.toString());
await governance.setParameter(0, INITIAL_VOTING_DELAY / 4);
const delayAfterSecondChange = await governance.votingDelay();
console.log('Delay after second change:', delayAfterSecondChange.toString());
expect(delayAfterFirstChange).to.not.equal(initialDelay);
expect(delayAfterSecondChange).to.not.equal(delayAfterFirstChange);
});
});
Test Output
Parameter Gaming Vulnerability Test
should demonstrate rapid parameter changes (145ms)
--- Logs ---
Initial voting delay: 86400
Delay after first change: 43200
Delay after second change: 21600
This confirms that an attacker can rapidly adjust governance parameters, enabling governance manipulation.
4. Recommended Mitigations
To secure the governance system, this solution should be implemented:
1. Rate Limiting on Parameter Updates
Enforce a minimum delay between consecutive updates:
mapping(GovernanceParameter => uint256) public lastParameterUpdate;
uint256 public constant MIN_UPDATE_DELAY = 1 days;
modifier rateLimited(GovernanceParameter param) {
require(
block.timestamp >= lastParameterUpdate[param] + MIN_UPDATE_DELAY,
"Parameter update too soon"
);
lastParameterUpdate[param] = block.timestamp;
_;
}
function setParameter(GovernanceParameter param, uint256 newValue)
external
override
onlyOwner
rateLimited(param)
{
}