Closed c4-bot-3 closed 8 months ago
othernet-global (sponsor) disputed
Users cannot double vote. If a user stakes, votes, unstakes for two weeks, votes again then they only achieve an additional 20% of their original vote amount - but at an 80% penalty in terms of their owned SALT which is acceptable.
In the POC there are only three users who are unstaking 33% of the staked SALT to affect required quorum. This is not realistic and the default unstaking and quorum behavior is acceptable.
Considering the fact that:
minUnstakeWeeks
and other parameters could be changedI think Medium severity is appropriate here both for the fact that user can double vote and that you can still vote after the voting phase if the quorum hasn't been reached. Taken individually I understand the sponsor's reasoning, but accumulating these features could lead to users creating stealth proposals, double-voting, and trying to lower the quorum to get them to the finish line.
Picodes marked the issue as satisfactory
Picodes changed the severity to QA (Quality Assurance)
This previously downgraded issue has been upgraded by Picodes
Picodes marked the issue as duplicate of #844
ballotMaximumDuration added https://github.com/othernet-global/salty-io/commit/758349850a994c305a0ab9a151d00e738a5a45a0
minUnstakeWeeks now a minimum of 2 https://github.com/othernet-global/salty-io/commit/1eaf1e3a9060028e669092012a30436a9a8bdfa4
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L385 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L259 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L342 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L317
Vulnerability details
Description
The DAO Governance voting system implements the following measures to prevent an attack via a sudden increase of the user's voting power (e.g. borrowing voting tokens with a flash-loan):
minUnstakeWeeks
: The minimum number of weeks for an unstake request at which pointminUnstakePercent
of the original staked SALT is reclaimable. Default is 2 days, range is from 1 to 12 days.maxUnstakeWeeks
: The maximum number of weeks for an unstake request at which point 100% of the original staked SALT is reclaimable. Default is 52 days, range is from 20 to 108 days.minUnstakePercent
: The percentage of the original staked SALT that is reclaimable when unstaking the minimum number of weeks. Default is 20%, range is from 10% to 50%.modificationCooldown
: The minimum time between increasing and decreasing user shares. Default is 1 hour, range is from 15 minutes to 6 hours.Also, the proposals to be voted (aka
Ballot
) have these relevant properties:ballotIsLive
: Whether the ballot accepts votes or has ended.ballotMinimumEndTime
: The earliest timestamp at which a ballot can end. Can be open longer if the quorum has not yet been reached for instance or if no EOA has finalized the ballot (after reaching quorum). Default is 10 days, range is from 3 to 14 days.Finally, a ballot can finalize when (from DAO.canFinalizeBallot):
Ballot.ballotIsLive
).Ballot.ballotMinimumEndTime
).However, the DAO Governance voting system does not implement any kind of voting power checkpoint (aka. snapshot) per account and proposal as per the functions related with voting, votes counting and quorum calculation demonstrate:
Proposals.castVote
function only checks that the ballot is live and that the user has voting power.Proposals.totalVotesCastForBallot
function just sums all the votes in the ballot.Proposals.requiredQuorumForBallotType
uses the current total amount of SALT staked to calculate the required quorum (or defaults to 0.5% of SALT total supply). This amount of xSALT is not fixed, as it fluctuates along with the users that stake and unstake (which it does not matter if they do not claim the SALT).The voting system is flawed and can be exploited.
Impact
One or multiple malicious users can decide a proposal by following these scenarios:
Attempting this attack is likely given the fact that:
minUnstakeWeeks
can be as low as 1 week.minUnstakePercent
can be high as 50%.ballotMinimumEndTime
can be as high as 14 days (2 weeks).modificationCooldown
value (from 15 minutes to 6 hours) is irrelevant in both A and B scenarios described above.Proof Of Concept
For simplicity sake the
DAOConfig
settings related with the voting system and theSakingConfig
settings related with the SALT staking/unstaking are set as default (viaDeployment.sol
). On a real attack scenario these values would be set in a more advantageous state for the attacker regarding unstaking-SALT.Scenario A
Add the following test in
src/dao/tests/Proposals.t.sol
:Scenario B
Add the following test in
src/dao/tests/Proposals.t.sol
:Tools Used
Forge tests and manually reviewed.
Recommended Mitigation Steps
First, take a snapshot of each user voting power per proposal and only allow them to vote with the balance they hold at that time. Then, consider whether unstaking wihtout claiming to decide a proposal should be allowed by the system and amend the code if necessary.
Assessed type
Governance