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

The `stake` fn doesn't handle the case where a user stakes multiple times within the same epoch

Vulnerability Details

function stake(uint256 _amount) external checkEpochRollover redeemPendingRewards {
//CHECK
if (_amount == 0) revert InvalidAmount();
//EFFECT
userData[msg.sender].unredeemedEpoch = currentEpoch;
DepositReceipt storage dr = deposits[msg.sender][currentEpoch];
if (dr.epoch == 0) {
dr.staked = _amount;
dr.epoch = currentEpoch;
_activeDeposits[msg.sender].add(currentEpoch);
} else {
dr.staked += _amount;
}
newStaked += _amount;
//INTERACT
fjordToken.safeTransferFrom(msg.sender, address(this), _amount);
points.onStaked(msg.sender, _amount);
emit Staked(msg.sender, currentEpoch, _amount);
}

in the stake function in FjordStaking.sol

The stake function doesn't properly handle the case where a user stakes multiple times within the same epoch. It simply adds the new stake amount to the existing dr.staked value without updating other important state variables like userData[msg.sender].totalStaked or totalStaked.

This could lead to inconsistencies in reward calculations and overall stake tracking.

For example, if a user stakes twice in the same epoch, only their last stake would be counted for reward calculations in future epochs, but they'd still have locked up the full amount of tokens.

The stake function correctly adds new stakes to newStaked, which is then added to totalStaked at the start of a new epoch in _checkEpochRollover(). However, userData[msg.sender].totalStaked is never updated for stakes within the current epoch.

Impact

  1. Reward calculation inaccuracy: The _redeem function uses userData[msg.sender].totalStaked to calculate rewards. This leads to undercalculation of rewards for users who stake multiple times in an epoch.

  2. Inconsistent state: totalStaked will eventually be correct, but userData[msg.sender].totalStaked will be incorrect until the user stakes in a new epoch.

  3. Unstaking issues: The unstake function checks against userData[msg.sender].totalStaked, which could prevent users from unstaking their full amount if they've staked multiple times in an epoch.

PoC:

  1. User stakes 100 tokens in epoch 1

  2. User stakes another 100 tokens in epoch 1

  3. Epoch 2 starts, totalStaked is now 200, but userData[user].totalStaked is still 0

  4. User tries to unstake 200 tokens, but fails due to UnstakeMoreThanDeposit error

  5. User's rewards are calculated based on 0 stake instead of 200

Tools Used

Manual review

Recommendations

To fix this, the function should update userData[msg.sender].totalStaked and totalStaked when adding to an existing stake within the same epoch. It should also consider how this impacts the reward calculation logic in other parts of the contract.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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