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

Penalties can be bypassed from malicious users because of rounding error

Summary

The current implementation of the penalty logic in the claimReward function allows users to bypass the intended penalty when claiming small rewards or get a discount from the penalty for bigger rewards when rewards%2!=0. This issue occurs because the division operation in Solidity truncates decimal places, resulting in penalties being rounded down to zero for small reward amounts. This allows users to claim the reward without any penalty or discounted penalty (in case reward%2!=0). The penalty logic is invariant so bypassing it or abusing it should be prevented.

Vulnerability Details

The vulnerability arises from the following code in the claimReward function:

penaltyAmount = rewardAmount / 2;
rewardAmount -= penaltyAmount;

In Solidity, division between integers results in truncation towards zero, meaning that if the rewardAmount is 1 token, the penalty becomes 0:

penaltyAmount = 1 / 2 = 0;

Thus, when claiming a small reward, the user can receive the entire rewardAmount without paying any penalty. This can lead to abuse if a malicious users claims small rewards, avoiding penalties altogether.

Impact

The impact of this vulnerability is that users can avoid paying penalties on small reward claims or not paying the full penalties in case rewards%2!=0. This can lead to the depletion of the reward pool over time, as users can claim rewards without incurring the full intended penalties. This could result in a loss of funds for the contract and unfair distribution of rewards.

Code Snippet

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);
}

POC

  1. The user accumulates a small reward, such as 1 token.

  2. The user claims the reward prematurely, and instead of incurring a 50% penalty, the penalty is rounded down to 0 due to integer division.

  3. The user receives the full reward of 1 token with no penalty.

  4. This process can be repeated to exploit the contract and avoid paying penalties on small reward claims.

Tools used

Manual review

Recommendations

Make sure that the penalty is rounded up. For example:

// Calculate 50% penalty based on reward amount
uint256 penaltyAmount = rewardAmount / 2;
// Check for rounding error and adjust penalty amount if necessary
if (penaltyAmount * 2 != rewardAmount) {
penaltyAmount += 1; // Adjust penalty if there was a rounding error
}
// Deduct penalty from reward amount
rewardAmount = rewardAmount - penaltyAmount;
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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