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:
If the _amount
passed for withdrawal is greater than the _deposited
amount of the given user, the value of _amount
is overwritten with _deposited
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
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
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.
Manual Review
Add a emergencyWithdraw
function for user whose funds are stuck to be able to withdraw whatever is left in the system.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.