code-423n4/2024-07-loopfi-validation

bug in `claim` allows users who are disqualified to claim their previously earned emissions

Opened this issue · 0 comments

Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/reward/ChefIncentivesController.sol#L520-L521

Vulnerability details

Bug Description

In ChefIncentivesController.sol:

function claim(address _user, address[] memory _tokens) public whenNotPaused {
  if (eligibilityMode != EligibilityModes.DISABLED) {
->    if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
->    checkAndProcessEligibility(_user, true, true);
  }
  ........
}

The function calls isEligibleForRewards without calling refresh, hence things like disqualification resulting from a change in price will not be accounted for, and this transaction will go through without reverting, allowing the user to claim even though his total value locked is now below 5% of his debt due to the price change of the token.

The checkAndProcessEligibility(_user, true, true); function however does include refresh, which will update the user's status. Hence that line should be called first, so that the transaction will revert in the if statement, to prevent malicious lockers from calling this function even when they are not eligible.

Recommended Mitigation Steps

function claim(address _user, address[] memory _tokens) public whenNotPaused {
  if (eligibilityMode != EligibilityModes.DISABLED) {
-     if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
-     checkAndProcessEligibility(_user, true, true);
      
      // swap the order !!

+     checkAndProcessEligibility(_user, true, true);
+     if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
  }

  _updateEmissions();

  uint256 currentTimestamp = block.timestamp;

  uint256 pending = userBaseClaimable[_user];
  userBaseClaimable[_user] = 0;
  uint256 _totalAllocPoint = totalAllocPoint;
  uint256 length = _tokens.length;
  for (uint256 i; i < length; ) {
      if (!validRTokens[_tokens[i]]) revert InvalidRToken();
      VaultInfo storage pool = vaultInfo[_tokens[i]];
      if (pool.lastRewardTime == 0) revert UnknownPool();
      _updatePool(pool, _totalAllocPoint);
      UserInfo storage user = userInfo[_tokens[i]][_user];
      uint256 rewardDebt = (user.amount * pool.accRewardPerShare) / ACC_REWARD_PRECISION;
      pending = pending + rewardDebt - user.rewardDebt;
      user.rewardDebt = rewardDebt;
      user.lastClaimTime = currentTimestamp;
      unchecked {
          i++;
      }
  }

  _vestTokens(_user, pending);

  eligibleDataProvider.updatePrice();
}

Tools Used

Manual Review

Assessed type

Invalid Validation