DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Based on The developers flow a user should claim his rewards immediately he unstakeVested but we Fail to Claim Rewards Upon Unstaking Vested in Staking Contract

Summary

The current implementation of the staking contract allows users to unstake their Vested tokens without automatically claiming their pending rewards. This issue can lead to a scenario where users, who would normally incur a penalty for early unstaking and reward claiming, can bypass the penalty. As a result, users may unintentionally or deliberately avoid paying the penalty

Vulnerability Details

The unstaking process in the contract does not automatically trigger a reward claim, even though the unstake action should ideally be coupled with an immediate reward claim. The relevant part of the unstakeVested function is as follows:

/// @notice Unstake vested FJORD tokens from the contract.
/// @dev This function allows users to unstake vested FJORD tokens,
@audit issue>>>> /// while also claiming all the pending rewards. If _isClaimEarly is true then the ///NOTE
@audit issue>>>> /// user will be able to bypass rewards cooldown of 3 epochs and claim early,
@audit issue>>>> /// but will incur early claim penalty.
/// @param _streamID The sablier streamID that the user staked.
function unstakeVested(uint256 _streamID) external checkEpochRollover redeemPendingRewards {
//CHECK
NFTData memory data = _streamIDs[msg.sender][_streamID];
DepositReceipt memory dr = deposits[msg.sender][data.epoch];
if (data.epoch == 0 || data.amount == 0 || dr.vestedStaked == 0 || dr.epoch == 0) {
revert DepositNotFound();
}
// If epoch is same as current epoch then user can unstake immediately
if (currentEpoch != data.epoch) {
// If epoch less than current epoch then user can unstake after at complete lockCycle
if (currentEpoch - data.epoch <= lockCycle) revert UnstakeEarly();
}
_unstakeVested(msg.sender, _streamID, data.amount);
}

The function is designed to transfer the unstaked tokens back to the user and record the unstake event. However, there is no direct call to claim the user's rewards immediately after unstaking. This omission allows users to unstake their tokens without claiming their pending rewards, effectively evading the early claim penalty.

Additionally, the `claimReward` function checks whether the user wants to claim early and applies a penalty accordingly:

/// @notice Claim reward from specific epoch.
/// @dev This function allows users to claim rewards from an epoch,
/// if the user chooses to bypass the reward cooldown of 3 epochs,
/// then reward penalty will be levied.
/// @param \_isClaimEarly Whether user wants to claim early and incur penalty.
/// @return rewardAmount The reward amount that has been distributed.
/// @return penaltyAmount The penalty incurred by the user for early claim.
function claimReward(bool \_isClaimEarly)
external
checkEpochRollover
redeemPendingRewards
returns (uint256 rewardAmount, uint256 penaltyAmount)
{
// (Logic for claiming rewards)
}

Since the `claimReward` function is separate from the `unstakeVested` function, a user who unstakes his vested NFT but does not call `claimReward` immedaitely can avoid paying the early claim penalty.

Impact

This vulnerability enables users to bypass the penalty for early reward claiming by unstaking their tokens without immediately claiming their rewards.

Tools Used

Manual Code Review

Recommendations

1. **Enforce Immediate Reward Claim on Unstaking**: Modify the `_unstakeVested` internal function to include an automatic call to the `claimReward` function, ensuring that users cannot unstake vested tokens without claiming their rewards. This will enforce the early claim penalty as intended.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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