MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: high
Invalid

Last user funds could be stuck in case of negative rebase

Summary

The Distribution.sol contract allows users to stake stETH in order to receive MOR tokens as rewards. Users could withdraw their staked stETH anytime as long as the withdrawLockPeriod and withdrawLockPeriodAfterStake have passed. stETH is a rebasing token and a rebase occurs every day as long as the Oracle reaches quorum on the beacon chain. The rebase automatically adjusts the balances of each wallet / smart contract holding stETH.

There is also a minimalStake amount for each pool which is used to check if the user _stake covers that criteria.

Within the _withdraw() function we can see that there are a few checks:

  1. If the _amount passed for withdrawal is greater than the _deposited amount of the given user, the value of _amount is overwritten with _deposited

  2. Then for public pools we have a check if the contract balance is greater than that amount and if not, _amount is overwritten again with the _balance of the contract

Vulnerability Details

The vulnerability lies within the second overwrite mentioned above in the case of a negative rebase for stETH and would impact the last user who might be trying to withdraw their stETH.

Based on Lido official documentation: " In case Lido's validators get slashed or penalized, the stETH balances can decrease according to penalty sizes." (https://docs.lido.fi/guides/lido-tokens-integration-guide/#steth-internals-share-mechanics)
Although there hasn't been a negative rebalance it is not impossible to see one in the future.

Now with all the above information imagine the following scenario:

  • minimalStake is set to 1 stETH

  • userA has staked 1 stETH a while back

  • there is a negative rebase for stETH which lowers the contract balance from 1 stETH to 0.8 stETH

  • userA is the last user in the system and tries to perform a _withdraw by passing 1 stETH as amount

  • _amount is overridden with contractBalance which is 0.8 stETH due to the fact that passed _amount is higher than the actual balance of the contract

  • newDeposited for the given userA becomes 1 stETH - 0.8 stETH --> 0.2 stETH

  • this causes the following require to fail: require(newDeposited_ >= pool.minimalStake || newDeposited_ == 0, "DS: invalid withdraw amount");, because the newDeposited is not >= to minimalStake, but also it is not 0 and the transaction fails

Impact

The last user in the system might not be able to withdraw his/hers stETH in the case of a negative rebase for stETH and there is no other way to recover the funds.

Tools Used

Manual Review

Recommendations

Add a emergencyWithdraw function for user whose funds are stuck to be able to withdraw whatever is left in the system.

function emergencyWithdraw(uint256 poolId_) public poolPublic(poolId_) {
UserData storage userData = usersData[_msgSender()][poolId_];
uint256 _currentBalance = IERC20(depositToken).balanceOf(address(this))
require( _currentBalance < userData.deposited, "Contract not in state of emergency");
userData.deposited = 0; // set the deposited data to 0
totalDepositedInPublicPools = 0; // total deposited should be set to 0 as well
IERC20(depositToken).safeTransfer(_msgSender(), _currentBalance); // transfer any remaining balance to the user
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Neverland Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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