Skip to content

Latest commit

 

History

History
82 lines (59 loc) · 3.6 KB

087.md

File metadata and controls

82 lines (59 loc) · 3.6 KB

Able Pastel Lion

Medium

The bumpEarningPower function can't change the depositId's earning power

Summary

Vulnerability Detail

In the GovernanceStaker contract, users can stake their tokens and set a _delegatee address that earns rewards based on that. However, after some time, the _delegatee may become ineligible for earning power. When a user claims their rewards, the _delegatee may become eligible again. The problem is that the bumpEarningPower function cannot change this depositId's earning power because deposit.scaledUnclaimedRewardCheckpoint will always be zero. This means that the delegatee has the voting power, but the staker cannot earn rewards. This breaks the invariant of bumpEarningPower that states that when a qualifying change in the earning power is returned by the earning power calculator, the deposit's earning power should be updated.

  /// @notice A function that a bumper can call to update a deposit's earning power when a
  /// qualifying change in the earning power is returned by the earning power calculator. A
  /// deposit's earning power may change as determined by the algorithm of the current earning power
  /// calculator. In order to incentivize bumpers to trigger these updates a portion of deposit's
  /// unclaimed rewards are sent to the bumper.
  /// @param _depositId The identifier for the deposit that needs an updated earning power.
  /// @param _tipReceiver The receiver of the reward for updating a deposit's earning power.
  /// @param _requestedTip The amount of tip requested by the third-party.
  function bumpEarningPower(
    DepositIdentifier _depositId,
    address _tipReceiver,
    uint256 _requestedTip
  ) external virtual {
    if (_requestedTip > maxBumpTip) revert GovernanceStaker__InvalidTip();

    Deposit storage deposit = deposits[_depositId];

    _checkpointGlobalReward();
    _checkpointReward(deposit);

@>>    uint256 _unclaimedRewards = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;

    (uint256 _newEarningPower, bool _isQualifiedForBump) = earningPowerCalculator.getNewEarningPower(
      deposit.balance, deposit.owner, deposit.delegatee, deposit.earningPower
    );
    if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
      revert GovernanceStaker__Unqualified(_newEarningPower);
    }

@>>    if (_newEarningPower > deposit.earningPower && _unclaimedRewards < _requestedTip) {
      revert GovernanceStaker__InsufficientUnclaimedRewards();
    }

    // Note: underflow causes a revert if the requested  tip is more than unclaimed rewards
    if (_newEarningPower < deposit.earningPower && (_unclaimedRewards - _requestedTip) < maxBumpTip)
    {
      revert GovernanceStaker__InsufficientUnclaimedRewards();
    }

    // Update global earning power & deposit earning power based on this bump
    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );
    deposit.earningPower = _newEarningPower.toUint96();

    // Send tip to the receiver
    SafeERC20.safeTransfer(REWARD_TOKEN, _tipReceiver, _requestedTip);
    deposit.scaledUnclaimedRewardCheckpoint =
      deposit.scaledUnclaimedRewardCheckpoint - (_requestedTip * SCALE_FACTOR);
  }

Impact

The delegatee has the voting power, but the staker cannot earn rewards.

Code Snippet

https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L462-L514

Tool used

Manual Review

Recommendation