51653 sc high permanent loss of staker rewards after slashing when validator records are cleared

Submitted on Aug 4th 2025 at 16:42:22 UTC by @wellbyt3 for Attackathon | Plume Network

  • Report ID: #51653

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

Clearing a slashed validator’s records sets each user’s stake to 0, so when the user later calls claim(), updateRewardsForValidatorAndToken exits early and marks rewards as paid without incrementing the user's reward related state. This results in a permanent loss of rewards earned between the last time the user's rewards state was updated and the slash.

Vulnerability Details

Staker's rewards are claimable after slashing, which is a design choice (only staked funds and commissions are impacted by slashing).

After a slash occurs, to cleanup state, the admin calls adminBatchClearValidatorRecords or adminClearValidatorRecord, which removes the amount slashed from the user's staking related state. Importantly, now:

$.userValidatorStakes[user][validatorId].staked == 0

When a staker calls claim() to claim their rewards from the slashed validator, _processValidatorRewards calls updateRewardsForValidatorAndToken, which should update the user's reward state, but because their stake amount was set to 0, this is skipped.

Key snippet (from report):

Because the early return happens before adding the calculated reward delta into the user's rewards, any accumulated rewards between the last reward-state update and the slash are permanently lost for that user.

Impact Details

Both adminClearValidatorRecord and adminBatchClearValidatorRecords are intended to be called after slashing occurs. Even though these are admin functions, there's no danger noted in the code or documentation on when these can be called (like many of the other admin functions intended to be used during updates), making it perfectly reasonable to assume this would have been called before all users have claimed their rewards.

It's also important to consider that often stakers are passive. It's reasonable to assume a long time can go by before a staker realizes the validator they've staked with has been slashed and that they should claim their rewards.

Because this is permanent freezing of user rewards, the scope suggests this is critical severity.

The mitigation for this is to ensure user reward related state is updated before allowing the calls to adminClearValidatorRecord and adminBatchClearValidatorRecords to succeed.

Also note, all 3 claim flows are impacted by this bug.

References

https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/RewardsFacet.sol#L316

Proof of Concept

Summary of the PoC steps:

1

Step: Setup and environment

Create a new .t.sol file in /plume/test/ and paste the provided solidity test file (below). The test deploys the diamond, facets, treasury, reward tokens, and validators.

2

Step: Stake and accrue rewards

  • user1 stakes 1e18 PLUME with validatorId = 1.

  • Advance time by 1 day to accumulate 1 day's worth of rewards.

3

Step: Slash validator

  • validatorId 1 is slashed using voteToSlashValidator.

4

Step: Admin clears validator record

  • Admin calls adminClearValidatorRecord(user1, 1) to clean up state after slashing.

  • This sets user1's staked amount for validator 1 to 0.

5

Step: Claim and observe loss

  • user1 calls claim(PLUME_NATIVE, 1).

  • Due to the early return in updateRewardsForValidatorAndToken (userStakedAmount == 0), the user's pending rewards are not added to their rewards state.

  • The claim returns 0 instead of the expected 1 day's worth of rewards.

Full test file used in the PoC:

Logs from the test run:

Test logs

Note: The recommended mitigation in the original report is to ensure user reward-related state is updated before allowing adminClearValidatorRecord / adminBatchClearValidatorRecords to succeed (so the user's earned rewards are captured before clearing stake amounts).

Was this helpful?