Closed sherlock-admin2 closed 4 months ago
We do not care to guarantee that from the moment we deploy this change all proposals get rewarded. In fact it might take some time until clients decide to register and add their client ID to contract interactions. Furthermore, we plan to first deploy the DAO upgrade and some time after that deploy the Rewards contract, by which point there might already be non-zero client ID data.
Either way, there is not requirement to "not dilute" any time period with non-zero client ID data.
This is not really an issue. It's by design.
Invalid cause it's intended behaviour.
Note that due to the check:
require(
>> votesInProposal == proposals[i].forVotes + proposals[i].againstVotes + proposals[i].abstainVotes,
'not all votes accounted'
);
nextProposalIdToReward
should have all votes recorded (maybe with zero clientId
):
ds._proposals[proposalId].voteClients[clientId] = NounsDAOTypes.ClientVoteData({
>> votes: uint32(voteData.votes + votes),
txs: voteData.txs + 1
});
Otherwise updateRewardsForProposalWritingAndVoting()
will be bricked if this first proposal be eligible.
I agree, we should deploy and initialize the Rewards
contract when we have a proposal ID to set nextProposalIdToReward
to where we know all votes will be recorded in the voteClients
mapping.
Such a proposal could already exist when we upgrade, it just needs to have had zero votes prior to executing the DAO upgrade that starts recording votes per client.
By the way, we don't have to wait for such a proposal to exist. We could simply use the next proposal ID after the DAO upgrade, since it's guaranteed to count all votes once it's proposed.
cc @davidbrai
hyh
medium
Setting
nextProposalIdToReward
to the current proposal artificially lowers first proposal and voting rewardsSummary
nextProposalIdToReward
is now initialized on Rewards deployment to be the last proposal id known to the system. Since the client id is being introducing with the current upgrade, all the actions of this proposal, i.e. proposal itself and its votes prior to upgrade, will guaranteed to have zero client id. This artificially dilutes the rewards for all proposals and votes of the first successfulupdateRewardsForProposalWritingAndVoting()
run.Vulnerability Detail
On the one hand votes after upgrade can have
clientId
. But it can be a small enough part of total votes for the proposal. In the same time rewards to be twisted rather substantially: the proposal is guaranteed not to haveclientId
and also lots of corresponding Nouns auctions will be missed asnextProposalRewardFirstAuctionId == currentNounId
, i.e. all in-between that proposal creation time and current nouns id. This differs from a typical situation after the upgrade when all the auctioned nouns during lifecycle of a proposal contribute to its reward.Impact
Having simultaneously the proposal and its votes adding to the denominator of the corresponding rewards calculations, while the numerator being reduced because the corresponding auctions will be missed, will lower the first distribution rewards. This is a loss for the corresponding client id owners with regard to the stated reward rules.
The only prerequisite is having the last proposal live for some time before upgrade, which is highly likely as tuning upgrade to minimize the distance from the last proposal creation time is burdensome. The loss of rewards is proportional to this time and can be big enough if the last proposal is already ended when upgrade happens (in this case all the actions linked to it will have zero client id).
Likelihood: High + Impact: Low = Severity: Medium.
Code Snippet
nextProposalIdToReward
is set to the last currently active proposal:https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/script/Rewards/DeployRewardsBase.s.sol#L28-L38
https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/governance/NounsDAOProposals.sol#L81-L97
This proposal doesn't have client id, have actual creation time in the past, but its creation time slot will be empty as it's introduced only during the upgrade:
https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/governance/NounsDAOProposals.sol#L871-L898
The main effect the inclusion of the current proposal brings in is rewards dilution for the subsequent proposals.
Having
t.lastProposal.creationTimestamp == 0
makes it not possible to immediately runupdateRewardsForProposalWritingAndVoting(dao.proposalCount(), ...)
to get rid of this old proposal as it will beauctionRevenue == 0
sincegetSettlementsFromIdtoTimestamp()
will return empty set:https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/client-incentives/Rewards.sol#L333-L339
https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/NounsAuctionHouseV2.sol#L452-L470
Tool used
Manual Review
Recommendation
Consider skipping the current proposal, e.g.:
https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/script/Rewards/DeployRewardsBase.s.sol#L28-L38