Beginner FriendlyFoundryDeFi
100 EXP
View results
Submission Details
Severity: low
Valid

Incorrect staked ETH amount after withdrawal

Summary

Withdrawing of a correspondingly smaller amount of funds than previously staked by the user results in staking an amount below the required minimum (less than 0.5 ETH).

Vulnerability Details

The Steaking::unstake function allows to withdraw staked assets of the user during staking period. Users can use it to adjust their staked ETH amount, or withdraw it completely. The additional requirement is that minimum staked amount is 0.5 ether. It is possible to broke this invariant when user first stake specific amount of ether and then withdraw a little less, where (staked amount - withdrawn amount) < 0.5 ether.

Impact

The minimum staked amount requirement could be broken.

Proof of Concepts

  1. user stakes the specific amount of assets, greater or equal to the minimum requirement.

  2. user withdraws a little less than the staked amount before the staking period ends.

Proof of code

Add the following code to the Steaking.t.sol file within the SteakingTest contract.

function testUnstakeBelowMinimumStakedAmount() public {
uint256 user1StakedAmount;
uint256 dealAmount = 1 ether;
uint256 unstakeAmount = 0.8 ether;
_stake(user1, dealAmount, user1);
user1StakedAmount = steaking.usersToStakes(user1);
assertEq(user1StakedAmount, dealAmount);
_unstake(user1, unstakeAmount, user1);
user1StakedAmount = steaking.usersToStakes(user1);
assertEq(user1StakedAmount, dealAmount - unstakeAmount);
assertLt(user1StakedAmount, 0.5 ether);
}

Tools Used

  • Manual Review

  • Foundry

Recommended Mitigation

The Steaking::unstake should check that the remaining amount (staked amount - withdrawn amount) is either greater than the minimum staked value (0.5 ETH) or equal to 0.

Possible solution (changes in Steaking.vy file):

+STEAK__STEAK_AMOUNT_LESS_THAN_MINIMUM: constant(String[33]) = "Steak__SteakAmountLessThanMinimum"
def unstake(_amount: uint256, _to: address):
"""
@notice Allows users to unstake their staked ETH before the staking period ends. Users
can adjust their staking amounts to their liking.
@param _amount The amount of staked ETH to withdraw.
@param _to The address to send the withdrawn ETH to.
"""
assert not self._hasStakingPeriodEnded(), STEAK__STAKING_PERIOD_ENDED
assert _to != ADDRESS_ZERO, STEAK__ADDRESS_ZERO
stakedAmount: uint256 = self.usersToStakes[msg.sender]
assert stakedAmount > 0 and _amount > 0, STEAK__AMOUNT_ZERO
+ remainingAmount: uint256 = stakedAmount - _amount
+ assert remainingAmount == 0 or remainingAmount >= MIN_STAKE_AMOUNT, STEAK__STEAK_AMOUNT_LESS_THAN_MINIMUM
assert _amount <= stakedAmount, STEAK__INSUFFICIENT_STAKE_AMOUNT
self.usersToStakes[msg.sender] -= _amount
self.totalAmountStaked -= _amount
send(_to, _amount)
log Unstaked(msg.sender, _amount, _to)
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Minimum Stake Amount Can Be Circumvented

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.