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

Users unable to cancel or change reward claim after selecting `_isClaimEarly = false` option

Summary

When users claim rewards, they have two options: they can either claim early, incurring a 50% penalty on their rewards, or they can choose to wait 3 epochs to receive the full amount. However, if users change their mind after deciding to wait for their full rewards, or if they accidentally select the option to wait, they are unable to reverse their decision and cannot cancel their claim receipt to access their funds immediately.

Vulnerability Details

https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordStaking.sol#L616
https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordStaking.sol#L662

The claimReward() function allows users to either claim their rewards early, incurring a 50% penalty, or choose to wait 3 epochs to receive the full reward amount. When a user opts to wait, a ClaimReceipt is created with the currentEpoch and the amount of unclaimed rewards. However, once this ClaimReceipt is issued, users cannot reverse their decision. If a user later decides they want to claim their rewards early or if they mistakenly select the option to wait, there is no mechanism to cancel or modify the ClaimReceipt.

function claimReward(bool _isClaimEarly)
external
checkEpochRollover
redeemPendingRewards
returns (uint256 rewardAmount, uint256 penaltyAmount)
{
//CHECK
UserData storage ud = userData[msg.sender];
// do not allow to claimReward while user have pending claimReceipt
// or user have claimed from the last epoch
if (
claimReceipts[msg.sender].requestEpoch > 0
|| claimReceipts[msg.sender].requestEpoch >= currentEpoch - 1
) revert ClaimTooEarly();
if (ud.unclaimedRewards == 0) revert NothingToClaim();
//EFFECT
if (!_isClaimEarly) {
claimReceipts[msg.sender] =
ClaimReceipt({ requestEpoch: currentEpoch, amount: ud.unclaimedRewards });
emit ClaimReceiptCreated(msg.sender, currentEpoch);
return (0, 0);
}
rewardAmount = ud.unclaimedRewards;
penaltyAmount = rewardAmount / 2;
rewardAmount -= penaltyAmount;
if (rewardAmount == 0) return (0, 0);
totalRewards -= (rewardAmount + penaltyAmount);
userData[msg.sender].unclaimedRewards -= (rewardAmount + penaltyAmount);
//INTERACT
fjordToken.safeTransfer(msg.sender, rewardAmount);
emit EarlyRewardClaimed(msg.sender, rewardAmount, penaltyAmount);
}

The completeClaimRequest() function strictly enforces this waiting period by requiring that the currentEpoch is at least 3 epochs greater than the requestEpoch in the ClaimReceipt before allowing a claim to be completed. This rigid structure locks users into their initial decision without any flexibility to alter it. As a result, users are effectively unable to access their rewards immediately once they have committed to the waiting period, even if they change their mind. This inflexibility can lead to user dissatisfaction and potential disruption in accessing funds when needed.

function completeClaimRequest()
external
checkEpochRollover
redeemPendingRewards
returns (uint256 rewardAmount)
{
ClaimReceipt memory cr = claimReceipts[msg.sender];
//CHECK
if (cr.requestEpoch < 1) revert ClaimReceiptNotFound();
// to complete claim receipt, user must wait for at least 3 epochs
if (currentEpoch - cr.requestEpoch <= claimCycle) revert CompleteRequestTooEarly();
//EFFECT
rewardAmount = cr.amount;
userData[msg.sender].unclaimedRewards -= rewardAmount;
totalRewards -= rewardAmount;
delete claimReceipts[msg.sender];
//INTERACT
fjordToken.safeTransfer(msg.sender, rewardAmount);
emit RewardClaimed(msg.sender, rewardAmount);
}

Impact

The impact of this issue is medium, as it restricts users' flexibility to access their funds immediately after choosing to wait for full rewards, potentially leading to user frustration. The likelihood of this issue occurring is also medium, given that users may frequently change their mind or accidentally select the wrong option when claiming rewards.

Tools Used

Manual review.

Recommendations

Consider adding an option for users to clear their claim receipts.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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