You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This creates a discrepancy: even though votes are locked-in at the Voting phase end, the effective quorum requirement can shift due to the total stake amount changes happening after the voting concludes but before the proposal is finalized.
Impact:
As a result:
Validators who initially voted "Yes" could find their proposal failing if other participants increase their stake after voting ends, thereby raising the quorum threshold.
Validators who initially voted "Yes" are discouraged from increasing their stake until finalize() is called. And this can happen each time they vote Yes for a proposal.
Participants who voted "No" are incentivized to increase their stake post-vote to push the proposal below the required quorum.
Even abstainers who increase their stake inadvertently raise the quorum target and may cause a proposal to fail.
This will encourage strategic stake manipulation.
Proof of Concept:
You can execute the following test by running forge test --mt testQuorumStakeManipulationBetweenVotingAndSnapshot -vv:
functiontestQuorumStakeManipulationBetweenVotingAndSnapshot()public{addressproposer=users[2];addressvoter=users[3];// Set initial stakemockValidatorSet.add(voter,voter,true);mockValidatorSet.add(users[4],users[4],true);mockStaking.setStake(voter,10ether);assertEq(mockStaking.totalStakedAmount(),10ether);// Create a proposaladdress[]memorytargets=newaddress[](1);targets[0]=users[1];uint256[]memoryvalues=newuint256[](1);values[0]=100ether;bytes[]memorycallDatas=newbytes[](1);callDatas[0]="";uint256proposalId=createProposal(proposer,"Test Proposal",targets,values,callDatas);// Switch to Voting phaseswitchPhase();// Voter casts a votevm.prank(voter);dao.vote(proposalId,Vote.Yes);// Switch phase to end VotingswitchPhase();//@note users stake snapshot is set here but not the total staked amountVotingResultmemoryres=dao.countVotes(proposalId);uint256requiredExceeding=mockStaking.totalStakedAmount()*(33*100)/10000;assertGt(res.stakeYes,res.stakeNo+requiredExceeding);// Proposal is passing at this point// A user massively increases their stake before finalizationmockStaking.setStake(users[4],1000ether);// Get the vote countsVotingResultmemoryresult=dao.countVotes(proposalId);requiredExceeding=mockStaking.totalStakedAmount()*(33*100)/10000;assertLt(result.stakeYes,result.stakeNo+requiredExceeding);// Now the proposal is not passing// Finalize proposaldao.finalize(proposalId);// Check if the increased stake affected voting powerassertEq(uint256(dao.getProposal(proposalId).state),uint256(ProposalState.Declined));// not passing}
Recommendation
The total stake amount has to be snapshotted in _snapshotStakes during the phase switch. This will make sure that the quorum calculation is fair and coherent. You can follow OZ implementation.
The text was updated successfully, but these errors were encountered:
softstackio
changed the title
[M-2] Finalization uses a vote snapshot but dynamically recalculates quorum with the current total stake
[M-02] Finalization uses a vote snapshot but dynamically recalculates quorum with the current total stake
Dec 10, 2024
MSalman6
added a commit
to MSalman6/diamond-contracts-dao
that referenced
this issue
Jan 2, 2025
Severity: Medium
Likelihood: Medium
Description:
During the finalize step of a proposal, votes are counted from a fixed user stakeAmount snapshot taken at the end of the Voting phase.
However, the quorum and threshold checks are performed using the current totalStakingAmount rather than the snapshot value.
This creates a discrepancy: even though votes are locked-in at the Voting phase end, the effective quorum requirement can shift due to the total stake amount changes happening after the voting concludes but before the proposal is finalized.
Impact:
As a result:
finalize()
is called. And this can happen each time they vote Yes for a proposal.This will encourage strategic stake manipulation.
Proof of Concept:
You can execute the following test by running
forge test --mt testQuorumStakeManipulationBetweenVotingAndSnapshot -vv
:Recommendation
The total stake amount has to be snapshotted in
_snapshotStakes
during the phase switch. This will make sure that the quorum calculation is fair and coherent. You can follow OZ implementation.The text was updated successfully, but these errors were encountered: