Skip to content

Latest commit

 

History

History
125 lines (88 loc) · 6.21 KB

093.md

File metadata and controls

125 lines (88 loc) · 6.21 KB

Lively Arctic Robin

Medium

Lack of Delegatee Eligibility Check May Result in Successful Staking but Immediately No Rewards due to no Update in Earning Power

Summary

The current implementation of the _stake and _stakeMorecfunction in the smart contract does not verify whether the delegatee is eligible for staking before proceeding with the staking operation. This oversight can lead to users being unaware that their stake has been processed without earning any rewards if the delegatee is ineligible. Additionally, the bumpEarningPower function, which is used to update the earning power of deposits, checks delegatee eligibility and prevents updates for unqualified delegatees. This creates a mismatch between the two functions—staking is allowed without validation, but earning power cannot be updated if the delegatee is unqualified. The report recommends separating the getEarningPower (used for querying) and getNewEarningPower (used for staking and earning power updates) to improve clarity and functionality.

Vulnerability Detail

The existing code has two main issues:

  1. Staking without Delegatee Eligibility Check:

    • The _stake and _stakeMore function does not validate whether the delegatee is eligible before proceeding with the staking operation. This can result in successful staking but without earning any rewards if the delegatee’s earning power is zero.
  2. Mismatch Between Staking and Earning Power Update Logic:

    • The bumpEarningPower function checks if the delegatee is eligible using the getNewEarningPower function and requires the delegatee to be qualified before updating the earning power. However, the staking function does not perform this check. This discrepancy means that while staking can be performed with an unqualified delegatee, updates to the earning power via bumpEarningPower will be blocked if the delegatee is unqualified.

Impact

  • Users may stake their tokens with an ineligible delegatee and later find that they are not earning rewards. This leads to confusion as the transaction appears successful, but no rewards are generated.
  • There is a mismatch in the system’s behavior. While the bumpEarningPower function prevents updates for unqualified delegatees, staking does not. This inconsistency can create frustration for users who attempt to update their earning power after staking with an invalid delegatee.

Code Snippet

Current implementation:

staker/src/GovernanceStaker.sol:_stake#L571-L73

function _stake(...) {
    // ... other checks

    // @audit Just calculates earning power without checking delegatee eligibility
    uint256 _earningPower = earningPowerCalculator.getEarningPower(
        _amount,
        _depositor,
        _delegatee
    );

    // @audit Directly proceed with staking operation
totalStaked += _amount;
    // ... more logic
}

staker/src/GovernanceStaker.sol:_stakeMore#L605-L610

function _stakeMore(...) {

 		// @audit Just calculates earning power without checking delegatee eligibility
        uint256 _newEarningPower = earningPowerCalculator.getEarningPower(
            _newBalance,
            deposit.owner,
            deposit.delegatee
        );

        totalEarningPower = _calculateTotalEarningPower(
            deposit.earningPower,
            _newEarningPower,
            totalEarningPower
        );
// @audit Directly proceed with staking operation and earningPower without 
        totalStaked += _amount;

The following code snippet shows the current behavior of the bumpEarningPower function, which checks delegatee eligibility before updating earning power:

staker/src/GovernanceStaker.sol:bumpEarningPower#L485-L490

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
        );

// @audit check Earning power could not be updated due to unqualified delegatee 
    if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
        revert GovernanceStaker__Unqualified(_newEarningPower);
    }

    // Further checks and updates for earning power...
}

The above code prevents updates if the delegatee is not qualified (_isQualifiedForBump), which causes an issue when the delegatee is unqualified during staking, especially stakers just finish their staking .

Tool Used

Manual Review

Recommendation

To address these issues and enhance the functionality of the staking system, we recommend the following changes:

  1. Delegatee Eligibility Check on Staking:

    • Implement a delegatee eligibility check within the _stake function before proceeding with the staking operation. This will ensure that only qualified delegatees can receive stakes and avoid situations where the earning power is zero due to an unqualified delegatee.
  2. Clarify Usage of getEarningPower and getNewEarningPower:

    • getEarningPower should be used for querying the current earning power of a deposit, as it does not modify the state and provides a simple snapshot of the rewards a user can earn.
    • getNewEarningPower should be used for staking and earning power update operations. This function already checks delegatee eligibility and should be called only after ensuring the delegatee is eligible. This will prevent inconsistencies in how earning power is updated.