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

Malicious users can manipulate reward distribution in `FjordStaking::_checkEpochRollover()`

Summary

The _checkEpochRollover() function in the FjordStaking contract incorrectly calculates and distributes rewards during epoch transitions, allowing malicious users to manipulate the reward distribution and gain an unfair advantage.

Vulnerability Details

The FjordStaking contract manages staking and reward distribution across multiple epochs. The _checkEpochRollover() function is responsible for handling epoch transitions and distributing rewards. It calculates pendingRewards based on various factors including the current contract balance, total staked amounts, and existing rewards. It then calculates pendingRewardsPerToken using the current totalStaked value and distributes these rewards across all epochs from lastEpochRewarded + 1 to currentEpoch.

The root cause of the vulnerability lies in the calculation of pendingRewardsPerToken:

uint256 pendingRewardsPerToken = (pendingRewards * PRECISION_18) / totalStaked;
for (uint16 i = lastEpochRewarded + 1; i < currentEpoch; i++) {
rewardPerToken[i] = rewardPerToken[lastEpochRewarded] + pendingRewardsPerToken;
emit RewardPerTokenChanged(i, rewardPerToken[i]);
}

This calculation uses the current totalStaked value, which may not accurately reflect the total staked amount for each specific epoch. This leads to several issues:

  1. Incorrect reward distribution: Users who staked in earlier epochs may receive disproportionate rewards.

  2. Vulnerability to manipulation: A malicious user can exploit this by staking a large amount just before an epoch rollover and unstaking immediately after.

  3. Inconsistent reward rates: The reward rate per token can vary depending on when the rollover occurs, rather than being consistent within an epoch.

Impact

This can lead to a significant imbalance in reward distribution among stakers. Malicious users who are aware of this issue can exploit it to gain a disproportionate share of rewards, effectively stealing value from other stakers.

While the total amount of rewards distributed is still bounded by the protocol's limits, the ability to manipulate the per-epoch distribution severely compromises the intended incentive structure.

Proof of Concept

  1. Alice, an attacker, observes the current epoch and total staked amount.

  2. Just before an epoch rollover, Alice calls stake() with a large amount of tokens:

    function stake(uint256 _amount) external checkEpochRollover redeemPendingRewards {
    // Function logic
    }

    This updates totalStaked to include Alice's large stake.

  3. The epoch rolls over, triggering _checkEpochRollover():

    • pendingRewardsPerToken is calculated using the inflated totalStaked value.

    • Rewards for previous epochs are distributed based on this inflated value.

  4. Immediately after the rollover, Alice calls unstake():

    function unstake(uint16 _epoch, uint256 _amount) external checkEpochRollover redeemPendingRewards returns (uint256 total) {
    // Function logic
    }

    Alice removes her large stake, but the rewards for previous epochs have already been distributed using the inflated totalStaked value.

  5. Result: Alice receives a disproportionate amount of rewards for the previous epochs, while other stakers receive less than their fair share.

Tools Used

Manual review

Recommendation

To address this vulnerability, the contract should be modified to:

  1. Track the total staked amount for each epoch separately.

  2. Calculate rewards on a per-epoch basis, using the correct total staked amount for that specific epoch.

  3. Accumulate rewards properly across multiple epoch rollovers.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
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.