31211 - [SC - Critical] Inflation Of Total Votes and Potential Freeze o...
Submitted on May 15th 2024 at 01:14:14 UTC by @Limbooo for Boost | Alchemix
Report ID: #31211
Report type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol
Impacts:
Permanent freezing of unclaimed yield
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Intro
The identified vulnerability resides within the interaction between the Voter.sol
and Bribe.sol
contracts. Specifically, it involves the potential for inflating the vote totals by repeatedly "poking" (updating) a vote within a single epoch, which is not adequately safeguarded against in the current implementation. This issue can be exploited to manipulate the voting of current epoch outcomes and affect the distribution of protocol rewards.
Vulnerability Details
The vulnerability arises from the lack of checks against multiple updates within the same voting epoch in the Voter.sol
contract. A user can call the poke
function multiple times to artificially inflate pool influence within a voting period. This is due to the system's failure to verify whether the state of votes has significantly changed between updates, allowing for repeated increases in the recorded vote total without actual additional staking.
Functionality and Misuse of the poke
Function:
poke
Function:The poke
function is designed to adjust a voter's weight in ongoing votes either if they increase their locked tokens or when they want to maintain the same votes next epoch. Under normal operations, this function would recalibrate the allocated voting power in accordance with newly locked tokens to reflect the user's current stake accurately. However, this function can be repeatedly invoked with or without actual changes in the locked amount, allowing users to artificially inflate pool influence in the governance process.
Technical Breakdown of the Issue:
When poke
is called, it triggers the _vote
function, which in turn calls _reset
. This interaction leads to a call to Bribe::withdraw()
, which effectively removes the voter’s existing votes but crucially does not adjust the totalVoting
or votingCheckpoints
immediately. The absence of immediate adjustment in these metrics leaves a window where the integrity of vote tracking is compromised.
Subsequently, when _vote
continues, it calls Bribe::deposit()
to reallocate votes with the new weights. This process incorrectly increases the totalVoting
and updates votingCheckpoints
based on the recalculated but unverified weight. The critical flaw here is that these updates accrue cumulatively with each call to poke
, without a corresponding real increase in locked tokens, leading to inflated voting totals.
Consequences of the Flaw:
This flaw allows a user to repeatedly 'refresh' their vote weight by merely invoking poke
, each time erroneously accumulating more apparent influence in the total votes counted by the system. This not only distorts the actual representational voting power but also undermines the integrity and intended democratic nature of the governance system.
Recommendations for Mitigation:
Immediate measures should include implementing checks that verify actual changes in locked tokens before allowing updates to vote weights and recalculations of voting power. Additionally, mechanisms should be in place to ensure that the withdrawal and deposit functions cannot be exploited to manipulate vote totals outside of legitimate staking updates.
Impact Details
Freezing of Yield: Incorrect vote totals can lead to incorrect reward calculations, potentially leaving a significant portion of rewards unclaimed as the system believes more votes exist than actually staked.
Griefing and Disruption: Malicious actors can disrupt the fair distribution of rewards and the accurate representation of voter sentiment in governance decisions.
Governance Manipulation: The inflated votes can alter the outcome of governance decisions, leading to implementations that do not reflect the true consensus of the token holders, thus undermining the democratic process of the DAO.
Proof of concept
Test Case (Foundry)
The test can be added to a new file under the current test suite src/test/VotingPoC.t.sol
, then specify the file name in FILE
flag under Makefile
configuration. Run using make test_file
Test Output
Last updated