29012 - [SC - High] Votes manipulation in PoolVoter
Submitted on Mar 4th 2024 at 18:33:40 UTC by @DuckAstronomer for Boost | ZeroLend
Report ID: #29012
Report type: Smart Contract
Report severity: High
Target: https://github.com/zerolend/governance
Impacts:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Vulnerability Details
Affected asset: https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol
PoolVoter contract allows to vote for the Gauge for anyone who has voting power by staking in the VestedZeroNFT.
The vote() function allows to specify pools associated with gauges and their respective weights.
function vote(
address[] calldata _poolVote,
uint256[] calldata _weights
) external {}However, a crucial flaw exists as the function fails to check for repeated pools in the _poolVote array. Moreover, it only considers the last weight if the same pool is utilized, as seen here - https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol#L117.
When a voter calls the reset() function to retrieve their voting power back and vote for another gauge, only the last weight is taken into account in case of repeated pool voting.
As a result, the voter recovers the full voting power. Yet totalWeight and weights[_pool] are decreased only by the last element from the _weights array passed to the vote().
Attack scenario:
The attacker possesses a voting power of
19.01 ether.They invoke
vote()to vote for the same gauge with the following parameters:_poolVote = [gauge, gauge]
_weights = [19 ether, 1e8]
Further they call
reset(), butweights[_pool]andtotalWeightare only decreased by1e8.The attacker retains the full
19.01 ethervoting power. Butweights[_pool]andtotalWeightare now increased by19 ether.By repeating steps 2-3 in a loop many times, the attacker can consolidate the majority of votes for their chosen gauge, and all rewards will be distributed to it.
By repeating steps 2-3 for 100 times, it's tantamount to having
1900 ethervoting power.
Proof of Concept
To run the Poc put it's code to the governance-main/test/PoolVoter.poc.ts file, generate random private key, and issue the following command:
Last updated
Was this helpful?