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

Precision Loss in Reward Distribution for Tokens with Fewer Decimals

Summary:

The FjordStaking contract uses a fixed precision of 18 decimals for reward calculations, However, this approach can lead to precision loss when distributing rewards in tokens with fewer decimals, such as USDC, potentially resulting in zero rewards being distributed to stakers. An attacker can exploit this precision mismatch by frequently updating the reward calculation, preventing any meaningful reward distribution. As a result, stakers receive zero USDC

Vulnerability Details:

Precision Loss: The use of PRECISION_18 in calculations could lead to significant precision loss when dealing with tokens like USDC. This could result in reward-per-token calculations yielding zero, effectively preventing the distribution of rewards.

Frequent Updates: Frequent updates to the reward calculations, especially in a high-activity environment, could exacerbate the issue by continually resetting the reward-per-token to zero due to insufficient precision.

Root Cause:

The root cause is the use of a fixed precision constant, PRECISION_18 (1e18), for calculating rewards, assuming all tokens have 18 decimals. When USDC, which has only 6 decimals, is used, the reward calculation leads to significant precision loss, particularly when rewards are updated frequently.

PoC Scenario:

  1. Contract Setup:

    • Staked Token: USDC (6 decimals).

    • Staked Amount: 100 USDC.

    • Epoch Reward Per Token Values:

      • rewardPerToken[_fromEpoch] = 1e18 (equivalent to 1.0 USDC).

      • rewardPerToken[_toEpoch] = 1e18 + 1e12 (equivalent to 1.000001 USDC).

    • Precision Constant:

      • PRECISION_18 = 1e18.

  2. Function in Question:

    function calculateReward(
    uint256 _amount,
    uint16 _fromEpoch,
    uint16 _toEpoch
    ) internal view returns (uint256 rewardAmount) {
    rewardAmount = (_amount * (rewardPerToken[_toEpoch] - rewardPerToken[_fromEpoch])) / PRECISION_18;
    }
  3. Step-by-Step Calculation:

    • Step 1: Calculate the difference in rewardPerToken values between the two epochs.

      rewardPerTokenDifference = rewardPerToken[_toEpoch] - rewardPerToken[_fromEpoch];
      // rewardPerTokenDifference = (1e18 + 1e12) - 1e18 = 1e12

      The difference is 1e12, which is equivalent to 0.000001 USDC.

    • Step 2: Calculate the reward amount using the calculateReward function.

      rewardAmount = (_amount * rewardPerTokenDifference) / PRECISION_18;
      // rewardAmount = (100 * 1e6 * 1e12) / 1e18 = 1e20 / 1e18 = 1e2

      The calculated rewardAmount is 1e2, which represents 100 in the token's smallest unit. For USDC, this corresponds to 0.0001 USDC.

    • Step 3: Interpret the result.

      The calculated reward of 0.0001 USDC is extremely small, potentially rounded down to zero, or simply too small to be considered a valid reward. This loss of precision results in the staker receiving either a very small reward or nothing at all.

  4. Expected Outcome:

    • Small Stakers: If the staker's amount is relatively small, the reward-per-token difference becomes negligible, leading to rewards that are effectively zero.

    • Impact on Users: The staker may not receive any meaningful reward for staking, leading to frustration and potentially driving them away from using the contract.

Conclusion:

The fixed PRECISION_18 constant causes significant precision loss when working with tokens that have fewer than 18 decimals, such as USDC. This precision mismatch leads to rewards being calculated as zero or near-zero, especially for smaller stakers, which disrupts the intended functionality of the contract.

Impact:

Incorrect Reward Distribution
Small or Zero Rewards for Stakers: Due to the precision mismatch, rewards calculated for stakers might be rounded down to zero or be so small that they are negligible. This particularly affects users who stake smaller amounts, as their calculated rewards might fall below the threshold that the contract can recognize or distribute.

Tools Used:

Manual review

Recommendations:

To avoid this issue, the contract should be modified to dynamically adjust the precision based on the token's actual decimals, ensuring accurate reward distribution.

function calculateReward(
uint256 _amount,
uint16 _fromEpoch,
uint16 _toEpoch
) internal view returns (uint256 rewardAmount) {
uint256 precision = 10**tokenDecimals; // Dynamically adjust precision
rewardAmount = (_amount * (rewardPerToken[_toEpoch] - rewardPerToken[_fromEpoch])) / precision;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Low decimal tokens or super small bids can lead to 0 claims

Support

FAQs

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