52186 sc low incorrect reward calculation for slashed validators due to single segment time handling

Submitted on Aug 8th 2025 at 15:20:58 UTC by @DSbeX for Attackathon | Plume Network

  • Report ID: #52186

  • Report Type: Smart Contract

  • Report severity: Low

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

  • Impacts:

    • Theft of unclaimed yield

Description

Brief/Intro

The calculateRewardsWithCheckpoints function contains a vulnerability in its slashed validator reward calculation logic. By using a single time segment with a static reward rate between the last update and slash time, it ignores intermediate reward rate changes. This results in reward miscalculations that would cause financial losses to users (through underpayment) or protocol drains (through overpayment) if exploited in production.

Vulnerability Details

The vulnerability is in the slashed validator handling within calculateRewardsWithCheckpoints. When a validator is slashed, rewards should be calculated up to the slash timestamp, accounting for all reward rate changes during this period. However, the current implementation uses a single continuous segment with a static rate:

} else {
    // Slashed validator case
    uint256 currentCumulativeRewardPerToken = ...;
    uint256 effectiveEndTime = validator.slashedAtTimestamp;
    
    if (effectiveEndTime > validatorLastUpdateTime) {
        uint256 timeSinceLastUpdate = effectiveEndTime - validatorLastUpdateTime;
        uint256 effectiveRewardRate = 
            getEffectiveRewardRateAt($, token, validatorId, validatorLastUpdateTime).rate;
        
        //Vulnerable single-segment calculation
        uint256 rewardPerTokenIncrease = timeSinceLastUpdate * effectiveRewardRate;
        currentCumulativeRewardPerToken += rewardPerTokenIncrease;
    }
    return _calculateRewardsCore(...);
}
  • Uses one continuous time segment from validatorLastUpdateTime to effectiveEndTime

  • Static rate application: Applies the initial reward rate at validatorLastUpdateTime for the entire duration

  • Checkpoint ignorance: Fails to account for intermediate reward rate changes captured in validatorRewardRateCheckpoints

Impact Details

References

Relevant code references
  • calculateRewardsWithCheckpoints - https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L374

  • getEffectiveRewardRateAt returns static rate instead of segmented rates - https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L544

Proof of Concept

1

Setup

  • Validator is active and earning rewards normally

  • Last reward update occurred at time T0

  • Reward rate at T0 is R1 (100 tokens/day)

2

Reward rate update (T1)

  • Protocol admin updates reward rate to R2 (200 tokens/day) at T1

  • New checkpoint created at T1 with R2

  • Validator continues operating but hasn't had its rewards updated since T0

3

Validator slashing (T2)

  • Validator is slashed at time T2

  • validator.slashed flag set to true

  • validator.slashedAtTimestamp set to T2

  • Validator stops participating after T2

4

Reward calculation trigger

  • User claims rewards after T2

  • System calls calculateRewardsWithCheckpoints()

  • Since validator is slashed, it enters the vulnerable code path

5

Flawed calculation

  • System calculates entire period (T0 to T2) at initial rate R1

  • effectiveRewardRate = getEffectiveRewardRateAt(validatorLastUpdateTime) → returns R1 (rate at T0)

  • rewardPerTokenIncrease = (T2 - T0) * R1

6

Incorrect reward distribution

  • User receives rewards for T0-T2 period calculated entirely at R1

Correct calculation should be:

  • T0-T1 segment: (T1 - T0) × R1

  • T1-T2 segment: (T2 - T1) × R2

Impact:

  • Protocol underpaying user by (T2-T1) × (R2 - R1) tokens

  • Validator commission also miscalculated based on wrong base rewards

Was this helpful?