Closed code423n4 closed 1 year ago
0xSorryNotSorry marked the issue as primary issue
eladmallel requested judge review
eladmallel marked the issue as sponsor disputed
this is by design - if you had voting power at the snapshot block you should be able to vote.
perhaps worth nothing that many other DAOs work the same way.
gzeon-c4 marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L70-L76 https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L184-L200 https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L210-L264
Vulnerability details
When casting a vote, an address is limited to a certain amount of
votes
derived fromds.nouns.getPriorVotes
. However, due to the nature ofds.nouns.getPriorVotes
, the amount of votes available to an address solely depends on the amount of tokens they held when a proposal becomes of Active state. Consequently, if the token is transferred to another address during this Active state, then the new token holder will not be able to vote on the current proposal, as they were not included in theds.nouns.getPriorVotes
snapshot. However, if the original owner did not vote on the active proposal when they owned the token, they will still be able to vote on the proposal if it is still active, even though they are technically no longer a token holder.Impact
The likelihood of this bug occurring is high, as tokens are traded on secondary marketplaces all the time, many of which will probably happen during active proposals. However, the impact of this bug is low, as the total number of votes remains unchanged throughout the proposal process, all of which result in a medium severity. The issue with this bug lies in the fact that addresses who are no longer token holders are still able to cast a valid vote, and being as how they are no longer members of the DAO, they could vote against the majority to make the succeeding of the proposal more difficult.
Proof of Concept
When calling the
castVote
function https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L70-L76an internal call is made to
castVoteInternal
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L184-L200which checks to see if the vote is Active or in the Objection period. If it isn't, then the vote is reverted with
'NounsDAO::castVoteInternal: voting is closed'
. However, if it is Active, then it makes another internal call to https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L210-L264The issue lies with this line: https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Votes.sol#L222
If the call to this function makes it passed all of the checks, then the
forVotes
,againstVotes
, orabstainVotes
are increased by the amount ofvotes
returned from theds.nouns.getPriorVotes
call. And because this is determined at the start of the proposal becoming Active, it is a lagging indicator of how many votes an address should actually have.With what has been described above, the following test proves that a non-token holder can still cast votes on Active proposals under certain circumstances:
and here are the logs:
As you can see, the token that was originally minted by the
ogOwner
was transferred to thevoter
during the Active state. However, even after no longer being a token owner,ogOwner
was still able to cast a vote and have it contribute to the succeeding of the proposal. Furthermore, the vote cast by thevoter
did not count.Tools Used
Foundry
Recommended Mitigation Steps
Using the
ds.nouns.balanceOf
along with theds.nouns.getPriorVotes
will prevent ex-token holders from casting votes.Assessed type
Token-Transfer