While voting for the ballot in Proposals.castVote, the functions use shares staked in PoolUtils.STAKED_SALT pool as quotas, those shares represent the amount of SALT staked in PoolUtils.STAKED_SALT pool.
The issue is that a malicious user can call Proposals.castVote to vote, and then calls Staking.unstake to retrieve SALT back(even there might some SALT lost becaues of expeditedUnstakeFee), and transfers the remaining SALT to another wallet, and restake again, by doing this, the second wallet can also calls Proposals.castVote to vote.
And similar to Proposals.castVote, Proposals.requiredQuorumForBallotType can also be manipulated. For example, a whale who owns a large amount of SALT can first Staking.stakeSALT into PoolUtils.STAKED_SALT, and calls Proposals.castVote to vote, then, calls Staking.unstake to withdraw his SALT back. After Staking.unstake, the staking.totalShares( PoolUtils.STAKED_SALT) will be a smaller number, and require less quorum to finalized the ballot.
// Get the user's shares for a specified pool.
function userShareForPool( address wallet, bytes32 poolID ) public view returns (uint256)
{
return _userShareInfo[wallet][poolID].userShare;
}
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/Proposals.sol#L98 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/Proposals.sol#L276 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/Proposals.sol#L320 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L266-L269 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L89 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L122
Vulnerability details
Impact
While voting for the ballot in Proposals.castVote, the functions use
shares
staked inPoolUtils.STAKED_SALT
pool as quotas, thoseshares
represent the amount of SALT staked inPoolUtils.STAKED_SALT
pool. The issue is that a malicious user can callProposals.castVote
to vote, and then calls Staking.unstake to retrieve SALT back(even there might some SALT lost becaues ofexpeditedUnstakeFee
), and transfers the remaining SALT to another wallet, and restake again, by doing this, the second wallet can also callsProposals.castVote
to vote.By using simliar method, Proposals._possiblyCreateProposal can use this methods to bypass the limitation one user can only have one active proposal
And similar to
Proposals.castVote
, Proposals.requiredQuorumForBallotType can also be manipulated. For example, a whale who owns a large amount of SALT can first Staking.stakeSALT intoPoolUtils.STAKED_SALT
, and callsProposals.castVote
to vote, then, calls Staking.unstake to withdraw his SALT back. AfterStaking.unstake
, thestaking.totalShares( PoolUtils.STAKED_SALT)
will be a smaller number, and require less quorum to finalized the ballot.Proof of Concept
staking.userShareForPool
is used in Proposals._possiblyCreateProposal and Proposals.castVote, which is a spot value and defined as:staking.totalShares
is used in Proposals.requiredQuorumForBallotType,which is also a spot valueTools Used
VS
Recommended Mitigation Steps
Assessed type
Other