Immunefi Audit Competitions
Plume | Attackathon

52719 sc medium inactive validators blocked from claiming commissions despite passed timelock

Brief / Intro

When a commission claim for a validator and token is requested, a timelock is started, after which the request can be finalized. However, both requesting and finalizing are disallowed for inactive validators, even if the timelock had already passed before the validator became inactive. This restriction is stricter than for slashed validators, which is inconsistent and does not make sense.

Vulnerability Details

Validators can be either deactivated or slashed. Both events set the slashedAtTimestamp to the current block.timestamp. However, slashing a validator is far more severe: it means that all other validators voted to slash it due to malicious behavior. By contrast, deactivation simply means the validator is no longer active, without implying any wrongdoing.

Therefore, it is illogical to completely block commission claims for deactivated validators while still allowing certain commission claims for slashed validators.

Relevant code excerpts:

When a validator is slashed, the commission claim can still be finalized if the timelock had already passed before the validator was slashed:

        if (validator.slashed && readyTimestamp >= validator.slashedAtTimestamp) {
            revert ValidatorInactive(validatorId);
        }

In contrast, deactivated validators cannot finalize their requests at all, regardless of whether the timelock had already passed:

        // For a non-slashed validator, simply require it to be active to finalize a claim.
        if (!validator.slashed && !validator.active) {
            revert ValidatorInactive(validatorId);
        }

This creates an inconsistent and stricter rule for deactivated (inactive) validators compared to slashed validators.

Impact Details

Inactive validators are permanently unable to claim their commissions, even if the timelock had already passed while they were active. This restriction is stricter than the rules for slashed validators, which is inconsistent and unfair. This can lead to loss/theft of unclaimed yield.

References

Code references are provided throughout the report (links retained as-is):

Proof of Concept

Step

Validator A accrues commissions.

Reference: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L189-L191

Step

The admin of validator A requests a commission claim by calling ValidatorFacet::requestCommissionClaim().

Reference: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L500-L539

This sets the requestTimestamp to the current block.timestamp: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L531

Note: Requesting a claim is not possible for slashed or inactive validators: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L513-L515

Step

After the timelock has passed, the validator is deactivated by calling ValidatorFacet::setValidatorStatus().

Reference: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L252-L308

Deactivation sets the slashedAtTimestamp to the current block.timestamp: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L277

Step

When the validator admin tries to finalize the commission claim by calling ValidatorFacet::finalizeCommissionClaim(), the call reverts even though the timelock had already passed while the validator was still active.

Reference (finalize checks): https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L566-L575

If the validator had been slashed instead, the call would not revert: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L566-L570

This demonstrates the inconsistency: deactivated validators cannot finalize even if the timelock expired before deactivation, while slashed validators may finalize depending on timestamps.