Skip to content

Latest commit

 

History

History
67 lines (51 loc) · 2.18 KB

068.md

File metadata and controls

67 lines (51 loc) · 2.18 KB

Skinny Mocha Dragon

High

Rewards Can Be Permanently Locked If No Active Stakers

Summary

Reward tokens can become permanently locked in the GovernanceStaker contract if rewards are notified when there are no active stakers (totalEarningPower == 0). The protocol lacks a mechanism to recover these tokens, leading to a permanent loss of funds.

Vulnerability Detail

The vulnerability exists in the reward notification flow:

  1. The notifier transfers reward tokens to the contract
  2. Then calls notifyRewardAmount()
  3. If totalEarningPower == 0 (no active stakers):
function notifyRewardAmount(uint256 _amount) external virtual {
    if (!isRewardNotifier[msg.sender]) {
      revert GovernanceStaker__Unauthorized("not notifier", msg.sender);
    }

    // No check for totalEarningPower > 0
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();

    if (block.timestamp >= rewardEndTime) {
      scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION;
    }
}

The issue arises because:

  • The contract requires rewards to be transferred before notification
  • No validation of totalEarningPower > 0 before accepting rewards
  • No mechanism to recover tokens if they get stuck

Impact

HIGH. If rewards are notified when no one is staking:

  • Reward tokens become permanently locked in the contract
  • No users can claim these rewards (even future stakers)
  • No recovery mechanism exists
  • Loss of funds for the protocol

Code Snippet

function rewardPerTokenAccumulated() public view virtual returns (uint256) {
    if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;  // @audit rewards lost

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
}

function notifyRewardAmount(uint256 _amount) external virtual {
    if (!isRewardNotifier[msg.sender]) {
      revert GovernanceStaker__Unauthorized("not notifier", msg.sender);
    }
    // @audit no check for totalEarningPower > 0
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
}

Tool used

Manual Review

Recommendation

implement a recovery mechanism