The Governance contract allows users with sufficient voting power (meeting the proposalThreshold
) to propose new initiatives using the Governance::propose
function. Once a proposal is submitted, voters are permitted to cast their votes (either in favor or against) after a specified votingDelay
. In an ideal scenario, once a proposal is cancelled, no further votes should be accepted.
While the contract includes a state
function that correctly returns a ProposalState.Canceled
status if a proposal has been cancelled, the castVote
function fails to consult this state before recording votes. Consequently, voters can still cast votes on proposals that have been cancelled. This oversight enables malicious actors to manipulate the governance process by artificially recording votes on cancelled proposals, potentially distorting voting results, skewing quorum calculations, and forcing genuine voters to waste gas on transactions that ultimately have no effect.
state
FunctionThe proposal lifecycle is managed through the Governance::propose
function, which initializes proposals with a start time, end time, and cancellation flag. The state
function is designed to determine the current status of a proposal based on various conditions, including whether it has been cancelled. Below is an excerpt from the state
function:
This function correctly identifies cancelled proposals by returning ProposalState.Canceled
when proposal.canceled
is true. However, the critical vulnerability lies in the castVote
function.
castVote
FunctionThe castVote
function is responsible for recording votes on proposals. Although it performs checks for the existence of the proposal and whether the voting period is active, it does not verify whether the proposal has been cancelled. As a result, even if the state
function would indicate that a proposal is cancelled, the castVote
function still allows votes to be cast. Below is the relevant code snippet:
Because castVote
does not incorporate a check for proposal.canceled
or use the state
function to determine the proposal’s status, voters can continue to cast votes on proposals that have already been cancelled. This loophole not only permits manipulation of voting results but also forces legitimate voters to expend gas on transactions that are ultimately invalid.
To demonstrate this vulnerability, the following Proof of Concept (PoC) is provided. The PoC is written using the Foundry tool.
Step 1: Create a Foundry project and place all the contracts in the src
directory.
Step 2: Create a test
directory and a mocks
folder within the src
directory (or use an existing mocks folder).
Step 3: Create all necessary mock contracts, if required.
Step 4: Create a test file (with any name) in the test
directory.
Step 5: Add the following test PoC in the test file, after the setUp
-> private helper function.
Step 6: To run the test, execute the following commands in your terminal:
Step 7: Review the output. The expected output should indicate that voters can vote on a cancelled Proposal.
As demonstrated, the test confirms that voters are allowed to vote on a cancelled Proposal.
Governance Manipulation:
The absence of a cancellation check in castVote
allows malicious actors to cast votes on proposals that have been cancelled. This undermines the governance process by enabling vote rigging and skewing the overall results.
Distorted Voting Quorum:
Voting on cancelled proposals may lead to an inaccurate count of votes, which can distort quorum calculations. This inaccuracy affects the decision-making process, potentially causing valid proposals to be improperly approved or rejected.
Denial of Service for Legitimate Voters:
When voters attempt to cast votes on proposals that are no longer active, they incur unnecessary gas costs. This not only wastes resources but also creates a denial of service (DoS) condition for genuine participants, as their votes may never be counted if the proposal is cancelled.
Erosion of User Trust:
The ability to vote on cancelled proposals can lead to significant mistrust in the governance mechanism. Stakeholders may lose confidence in the protocol’s ability to maintain a fair and transparent decision-making process, which could reduce overall participation and harm the protocol's reputation.
Potential for Systematic Abuse:
Malicious actors could exploit this vulnerability on a large scale, systematically manipulating governance outcomes to favor their interests. This abuse would compromise the democratic integrity of the system and could have far-reaching negative consequences for protocol governance.
Here are some reasons why it might be considered Medium severity:
Governance Manipulation:
Governance is a critical component of many decentralized protocols. If an attacker can manipulate the voting process—even on cancelled proposals—it can significantly distort decision-making. This could lead to proposals being passed or rejected based on manipulated vote counts, thereby undermining the democratic process.
Erosion of Trust:
The integrity of the governance process is vital for maintaining stakeholder trust. Even if funds remain safe, a compromised voting system can erode confidence among users and investors, leading to reduced participation and potential market instability. Trust is paramount in decentralized systems, and any vulnerability that undermines governance can have long-lasting consequences.
Indirect Financial Impact:
Although the vulnerability does not directly drain funds, manipulating governance could result in decisions that negatively affect the protocol's economic model.
Systematic Abuse:
The ability to vote on cancelled proposals can be exploited repeatedly, allowing malicious actors to create a pattern of abuse that systematically undermines the governance framework. This can pave the way for more significant attacks or the gradual erosion of decentralization and fairness within the protocol.
Manual Review
Foundry
AI (chatGPT: for grammatic errors)
Integrate a Cancellation Check in castVote
:
Modify the castVote
function to incorporate a verification step that ensures votes cannot be cast on proposals that have been cancelled. For instance, add the following condition at the beginning of the function:
This check will ensure that the function adheres to the proposal’s current state as determined by the state
function.
Refactor the Voting Workflow:
Ensure that the governance workflow leverages the state
function consistently across all functions that interact with proposals. This will help maintain consistency in how proposals are evaluated and prevent any discrepancies between the proposal state and allowed actions.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.