DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: high
Valid

Function `unstake` and `unstakeVested` resets the `unredeemedEpoch`, which causes the user to suffer reward loss.

Summary

Function unstake and unstakeVested resets the unredeemedEpoch, which causes the user to suffer reward loss.

Vulnerability Details

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordStaking.sol#L449-L494

The unstake function is used to withdraw the tokens staked by the user during the _epoch period. However, in the contract, when all the tokens staked by the user during this period are withdrawn, the unredeemedEpoch will be reset. This operation will cause the user to suffer reward losses.

Here is a specific example:

  1. Bob stakes 1000 tokens when epoch=1.

  2. Next, Bob stakes 1000 token when epoch=10. At the same time, he gets 1000 reward tokens. And we know his unredeemedEpoch = 10. That is to say, if Bob performs an operation (such as staking or unstaking) when epoch is greater than 10, the tokens he staked at epoch=10 will be rewarded.

  3. Bob unstake his all tokens with _epoch =1. So the unredeemedEpoch will be 0.

  4. After a period of time, for example epoch=20, Bob want to claimReward. However, his reward is still 1000 reward tokens(no growth) as unredeemedEpoch = 0. The following logic does not work:

if (ud.unredeemedEpoch > 0 && ud.unredeemedEpoch < currentEpoch) {
// 2. Calculate rewards for all deposits since last redeemed, there will be only 1 pending unredeemed epoch
DepositReceipt memory deposit = deposits[sender][ud.unredeemedEpoch];
// 3. Update last redeemed and pending rewards
ud.unclaimedRewards += calculateReward(
deposit.staked + deposit.vestedStaked, ud.unredeemedEpoch, currentEpoch - 1
);
ud.unredeemedEpoch = 0;
ud.totalStaked += (deposit.staked + deposit.vestedStaked);
}

The same issue occurs in the function unstakeVested.

Impact

Users lose their rewards.

Tools Used

Vscode

Recommendations

Don’t reset the unclaimedRewards.

OR

Check if the _epoch is equal to currentEpoch.

if (userData[msg.sender].unredeemedEpoch == currentEpoch && _epoch = currentEpoch) {
userData[msg.sender].unredeemedEpoch = 0;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

`unredeemedEpoch` is set to zero due to a false if condition in `unstake` and `_unstakeVested` function

Users that try to unstake tokens from an earlier epoch after they staked in the current epoch will have their ` unredeemedEpoch` set to 0, leading to them being unable to access the newly staked tokens. Impact: High – Tokens are lost Likelihood: Medium – It happens every time a user performs the respective sequence of calls. It’s not always but it’s also not a low likelihood scenario. It’s normal usage of the protocol, that doesn’t necessarily require special conditions.

Support

FAQs

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