31355 - [SC - Low] Past Defeated Proposals Can Be Executed in the ...
Submitted on May 17th 2024 at 16:06:05 UTC by @Breeje for Boost | Alchemix
Report ID: #31355
Report type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/AlchemixGovernor.sol
Impacts:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Brief/Intro
The AlchemixGovernor
contract uses an implementation of L2GovernorVotesQuorumFraction
, which has a known vulnerability. If a proposal passes to lower the quorum requirement, all past proposals that were defeated solely due to a lack of quorum may become executable if the votes they received now meet the new, lower quorum requirement.
Vulnerability Details
The AlchemixGovernor
contract inherits from L2GovernorVotesQuorumFraction
, whose implementation is similar to OpenZeppelin's GovernorVotesQuorumFraction
contract version 4.5.0
.
Proposal Execution Process
To understand the issue, we need to examine how a proposal is executed:
The
execute
function identifies the state of the proposal by calling thestate
function.
The
state
function, after validation, checks two things: whether quorum was reached and whether the vote succeeded.
The
_quorumReached
function calls thequorum
function withproposalSnapshot(proposalId)
as the timestamp.
The
quorum
function ofL2GovernorVotesQuorumFraction
is implemented as shown below:
Notice the following critical point:
While the function gets the total supply value based on the timestamp, it uses the
quorumNumerator()
function, which returns the currentquorumNumerator
value rather than the value at the time ofblockTimestamp
.
Consequences of the Issue
If
quorum
was not reached for a past proposal, it cannot be executed at that time and is in adefeated
state.If governance updates the
_quorumNumerator
to a lower value usingupdateQuorumNumerator
function:The past defeated proposal may now reach the quorum with the new lower requirement.
The proposal state can change to succeeded, making it executable again.
Impact Details
Past proposals that were defeated only due to a lack of quorum may become executable if the number of votes they received meets the new quorum requirement.
References and Mitigation
This vulnerability is recognized by OpenZeppelin with a high severity rating. They issued a security advisory for it, which can be found here.
OpenZeppelin mitigated this issue in version 4.7.2
by:
Deprecating the direct use of
_quorumNumerator
in thequorumNumerator()
function.Implementing
Checkpoints
for the new_quorumNumeratorHistory
variable to track past values of the quorum numerator relative to time and using the value as it was at the blockTimestamp.
The changes are detailed in this commit.
Proof of Concept
Adding Test
Add the following lines in the L2Governor
contract:
Add the following testPoC
function in AlchemixGovernorTest.t.sol
test:
Running Test
Use the following command to run the test:
Expected Test Result
Analysis of the Result
In the test logs, you can see the following key points:
Initial Voting Power vs Quorum:
Initial Voting Power:
63473899543378978660812
Initial Quorum:
92037551049771679068955
Can clearly see
Voting Power
<Quorum
.
Voting on Proposal:
Proposal created and voted on.
Initial attempt to execute fails due to not meeting quorum.
Updating Quorum:
Quorum numerator is updated from 20% to 11%.
Final Voting Power vs New Quorum:
Final Voting Power:
62597183155758481682946
Final Quorum:
49921468744534494597083
Can clearly see,
Voting Power
>Quorum
now for same past proposal.
Executing Past Proposal:
The past proposal, initially defeated due to lack of quorum, now meets the new quorum and is executed successfully.
This demonstrates the vulnerability where reducing the quorum can make previously defeated proposals executable, posing a significant governance risk.
Last updated