Closed code423n4 closed 2 years ago
thank you for the suggestion. at most this can be labeled as low risk
My understanding that this is in between medium and high risk as proposal creation and proper livecycle is a core feature of the protocol. If it is taken away this will yield massive fund loss for all the token holders. The attack surface described mostly disables the ability to add a functional proposal.
The only reason it is Medium is that admin
actions are required for that. It is not improbable as disabling the outside control of the protocol can have tangible benefits for the protocol administration.
This would require governance to pass a proposal to set such value, which I think make this issue low risk.
It can be set by admin
directly, without the voting.
Lines of code
https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L726-L736 https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L771-L776
Vulnerability details
There is no restrictions for how big
quorumCoefficient
can be. I.e. it now can be set to, say1e18*1e6
, yielding1e18x
multiplier for against votes, meaning that of there is any against vote of any size, the required quorum becomesparams.maxQuorumVotesBPS
, i.e. up toMAX_QUORUM_VOTES_BPS_UPPER_BOUND
or60%
of the total supply, which will become more and more restrictive along with future emissions.This way admin can effectively block almost all proposals as the required quorum will render them as Defeated. This will de facto disable the key functionality of the protocol. Without the ability to implement proposals the protocol tokens become less valuable, which leads to monetary loss for all the token holders.
Proof of Concept
admin can use _setQuorumCoefficient() and _setDynamicQuorumParams() to set any
params.quorumCoefficient
, it's not restricted anyhow:https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L726-L736
https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L771-L776
Anyone with at least minimal voting power can vote against the proposal and, given bloated
params.quorumCoefficient
it doesn't matter how big that against vote was, the quorum required can be put to beparams.maxQuorumVotesBPS
, i.e. up toMAX_QUORUM_VOTES_BPS_UPPER_BOUND
or60%
:https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L903-L913
https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L85-L86
This can put even overly sucessful proposal to become
ProposalState.Defeated
:https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L877-L889
https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L441-L444
In order to be queued a proposal have to be in
state(proposalId) == ProposalState.Succeeded
:https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L285-L303
Recommended Mitigation Steps
Consider putting an upper bound on
params.maxQuorumVotesBPS
:https://github.com/code-423n4/2022-08-nounsdao/blob/452695d4764ba9d5e1d3eef0d5ecca3d004f215a/contracts/governance/NounsDAOLogicV2.sol#L726-L736
As an example,
MAX_QUORUM_COEFFICIENT
can be set to3e6
, which is3x
weight for against votes, that can cover the majority of cases.