Core Contracts

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

No Cooldown Mechanism for Proposal Creation Allows Spamming of Governance System

Summary

The governance contract allows users to create proposals without any restrictions or cooldown periods after a proposal fails (eg. if proposal have more false vote). This means that if a user's proposal is rejected, they can immediately create another proposal, and this process can be repeated indefinitely. This lack of a cooldown mechanism can lead to spam, governance fatigue, and unnecessary consumption of blockchain resources (e.g., gas and storage).

Vulnerability Details

The issue is present in the propose function of the governance contract. Specifically, there is no mechanism to enforce a cooldown period or restrict users from repeatedly creating proposals after a failure (Proposal has more false vote).

Affected Code:

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
});
// Store the proposal data separately
_proposalData[proposalId] = ProposalData(targets, values, calldatas, description);
emit ProposalCreated(proposalId, msg.sender, targets, values, calldatas, description, proposalType, startTime, endTime, proposerVotes);
return proposalId;
}

Impact

  1. Spam Proposals: Malicious or overly enthusiastic users can flood the governance system with proposals, overwhelming the process and making it difficult for legitimate proposals to gain attention.

  2. Wasted Resources: Each proposal creation consumes gas and storage on the blockchain, leading to unnecessary costs for the network and its participants.

  3. Governance Fatigue: Continuous spam proposals may discourage other users from participating in governance, reducing the effectiveness of the system.

  4. Potential Abuse: A single user could repeatedly submit proposals to disrupt governance or manipulate the system.

    Expected Behavior:

    Users should be restricted from creating new proposals immediately after a failed proposal. A cooldown period, staking requirement, or other mechanism should be in place to prevent spam and abuse.


    Actual Behavior:

    Users can create proposals repeatedly without any restrictions, even if their previous proposals fail.

Tools Used

Manuel Review

Recommendations

To address this issue, consider implementing one or more of the following mechanisms:

  1. Proposal Creation Cooldown:
    Introduce a cooldown period after a user's proposal fails, during which they cannot create another proposal.
    Example:

    mapping(address => uint256) public lastProposalFailureTime;
    uint256 public constant COOLDOWN_PERIOD = 1 days;
    function propose(...) external override returns (uint256) {
    if (lastProposalFailureTime[msg.sender] + COOLDOWN_PERIOD > block.timestamp) {
    revert CooldownPeriodActive(lastProposalFailureTime[msg.sender] + COOLDOWN_PERIOD, block.timestamp);
    }
    // Rest of the propose logic...
    }
    function _handleProposalFailure(uint256 proposalId) internal {
    lastProposalFailureTime[_proposals[proposalId].proposer] = block.timestamp;
    }
  2. Proposal Deposit:
    Require users to lock a deposit when creating a proposal. If the proposal fails, the deposit is forfeited or burned.
    Example:

    uint256 public constant PROPOSAL_DEPOSIT = 100 ether;
    function propose(...) external payable override returns (uint256) {
    if (msg.value < PROPOSAL_DEPOSIT) {
    revert InsufficientDeposit(msg.value, PROPOSAL_DEPOSIT);
    }
    // Rest of the propose logic...
    }
    function _handleProposalFailure(uint256 proposalId) internal {
    address proposer = _proposals[proposalId].proposer;
    payable(address(0)).transfer(PROPOSAL_DEPOSIT); // Burn the deposit
    }
  3. Reputation System:
    Track the success rate of proposals by each user. If a user's proposals consistently fail, restrict them from creating new proposals.
    Example:

    mapping(address => uint256) public failedProposals;
    uint256 public constant FAILURE_THRESHOLD = 3;
    function propose(...) external override returns (uint256) {
    if (failedProposals[msg.sender] >= FAILURE_THRESHOLD) {
    revert TooManyFailedProposals(msg.sender, failedProposals[msg.sender]);
    }
    // Rest of the propose logic...
    }
    function _handleProposalFailure(uint256 proposalId) internal {
    failedProposals[_proposals[proposalId].proposer]++;
    }
  4. Minimum Time Between Proposals:
    Enforce a minimum time interval between proposals from the same user.
    Example:

    mapping(address => uint256) public lastProposalTime;
    uint256 public constant MIN_PROPOSAL_INTERVAL = 1 days;
    function propose(...) external override returns (uint256) {
    if (lastProposalTime[msg.sender] + MIN_PROPOSAL_INTERVAL > block.timestamp) {
    revert ProposalIntervalNotMet(lastProposalTime[msg.sender] + MIN_PROPOSAL_INTERVAL, block.timestamp);
    }
    lastProposalTime[msg.sender] = block.timestamp;
    // Rest of the propose logic...
    }
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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

Give us feedback!