For the complete documentation index, see llms.txt. This page is also available as Markdown.

52104 sc high removed reward tokens block validator commission claims

Submitted on Aug 8th 2025 at 00:57:27 UTC by @jovi for Attackathon | Plume Network

  • Report ID: #52104

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/facets/ValidatorFacet.sol

Impacts:

  • Temporary freezing of funds for at least 24 hours

  • Theft of unclaimed yield

Description

Both ValidatorFacet.requestCommissionClaim() and ValidatorFacet.finalizeCommissionClaim() require that the commission token is still active via the _validateIsToken modifier. When governance calls RewardsFacet.removeRewardToken(), the token’s isRewardToken flag is cleared while all previously-accrued commission remains recorded. Every future claim therefore reverts, freezing the funds and stripping validators of their earnings.

Details

How commissions are supposed to work

1

Each validator accrues commission

Each validator earns a slice of every active reward token; the amount is tracked to be claimed as commission.

2

Claim flow

When the validator admin is ready, they call requestCommissionClaim() and later finalizeCommissionClaim() to receive the payout.

Both claim functions enforce the same modifier:

modifier _validateIsToken(address token) {     
    if (!PlumeStakingStorage.layout().isRewardToken[token]) {         
        revert TokenDoesNotExist(token);     
    }     
    _; 
}

As long as isRewardToken[token] is true, claims succeed. If the flag is ever false, the call reverts before any business logic executes.

What happens during token removal

Governance (or the Reward Manager) can retire a reward token:

Once step ① runs:

  • The next call to requestCommissionClaim(..., token) or finalizeCommissionClaim(..., token) hits _validateIsToken and reverts with TokenDoesNotExist.

  • The commission becomes unclaimable.

Impact

Proof of Concept

1

Initial state

  • Deploy contracts.

  • Add reward token TOKEN_A; validators accrue commission in TOKEN_A.

  • Validator V has validatorAccruedCommission[V][TOKEN_A] = 1_000.

2

Governance action

  • Call RewardsFacet.removeRewardToken(TOKEN_A).

3

Attempted claim

  • V calls ValidatorFacet.requestCommissionClaim(V_ID, TOKEN_A) → reverts TokenDoesNotExist.

4

Observation

  • Storage still shows validatorAccruedCommission[V][TOKEN_A] == 1_000, but there is no callable function that will ever succeed, so the funds are frozen.

Was this helpful?