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

`unstakeAll()` does not allow immediate unstaking

Summary

According to spec:

/// @dev This function allows users to unstake from all the epochs at once,
/// while also claiming all the pending rewards.

However, the unstakeAll() function does not handle stakings made in the currentEpoch as done in unstake(). Therefore, not all epochs are unstaked from as should be done.

Vulnerability Details

Lock Cycle Check:

if (dr.epoch == 0 || currentEpoch - epoch <= lockCycle) continue;

Epoch Handling:

  • dr.epoch == 0: This condition checks if the deposit record exists. If not, it skips processing. This is a standard check to ensure that the function only processes valid deposits.

  • currentEpoch - epoch <= lockCycle: This condition is intended to enforce the lock period, ensuring that users cannot unstake their tokens until the lock cycle has passed.

Current Epoch Deposits:

  • For deposits made in the currentEpoch, currentEpoch - epoch equals 0.
    The condition currentEpoch - epoch <= lockCycle evaluates to true for current epoch deposits, causing the function to skip processing these deposits.

Impact

This logic inadvertently prevents users from unstaking tokens that were staked in the current epoch. This is because the condition is designed to enforce the lock cycle, but it does not account for the immediate unstaking scenario that should be allowed for deposits made in the current epoch.

Tools Used

Manual Review

Recommendations

Modify the unstakeAll() function to handle current epoch deposits separately, allowing them to be unstaked immediately without the lock cycle restriction.

---SNIP---
for (uint16 i = 0; i < activeDeposits.length; i++) {
uint16 epoch = uint16(activeDeposits[i]);
DepositReceipt storage dr = deposits[msg.sender][epoch];
- if (dr.epoch == 0 || currentEpoch - epoch <= lockCycle) continue;
+ if (dr.epoch == 0) continue;
+ // Allow unstaking for current epoch deposits
+ if (currentEpoch != epoch && currentEpoch - epoch <= lockCycle) continue;
+ uint256 userNewStaked;
+ if (currentEpoch == epoch) {
+ // @audit Sum the total staked in the current Epoch
+ userNewStaked += dr.staked;
+ } else {
+ // @audit Sum all stakes from previous epochs
+ totalStakedAmount += dr.staked;
+ }
- totalStakedAmount += dr.staked;
+ // @audit Handle deposit deletion logic...
// no vested staked and stake is 0 then delete the deposit
if (dr.vestedStaked == 0) {
+ // @audit instant unstake
+ if (userData[msg.sender].unredeemedEpoch == currentEpoch) {
+ userData[msg.sender].unredeemedEpoch = 0;
+ }
delete deposits[msg.sender][epoch];
_activeDeposits[msg.sender].remove(epoch);
} else {
// still have vested staked, then only delete the staked
dr.staked = 0;
}
}
+ // @audit Deduct user's stake in the current epoch from newStaked
+ newStaked -= userNewStaked;
+ // @audit Only deduct stakes from previous epochs here
totalStaked -= totalStakedAmount;
userData[msg.sender].totalStaked -= totalStakedAmount;
+ // @audit Sum all stakes from current and previous epochs
+ totalStakedAmount += userNewStaked;
+ // @audit Transfer all stakes from current and previous epochs to the user and update
fjordToken.transfer(msg.sender, totalStakedAmount);
points.onUnstaked(msg.sender, totalStakedAmount);
+ // @audit Emit event
Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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