50402 sc low single rate assumption ignores checkpoints in slashed case

Submitted on Jul 24th 2025 at 09:10:11 UTC by @BeastBoy for Attackathon | Plume Network

  • Report ID: #50402

  • 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

In the slashed‑validator branch of calculateRewardsWithCheckpoints, the code reads a single rate at the moment of the last global update and applies it across the entire interval without honoring any intermediate checkpoints:

PlumeStakingStorage.RateCheckpoint memory effectiveRewardRateChk =
    getEffectiveRewardRateAt($, token, validatorId, validatorLastUpdateTime);
uint256 effectiveRewardRate = effectiveRewardRateChk.rate;
uint256 rewardPerTokenIncrease = timeSinceLastUpdate * effectiveRewardRate;
currentCumulativeRewardPerToken += rewardPerTokenIncrease;

Because it never segments the window by the array of validatorRewardRateCheckpoints, any rate changes that occurred between validatorLastUpdateTime and the slash (or token removal) are completely ignored. In contrast, the unslashed path builds a distinctTimestamps array of every checkpoint and correctly applies each piecewise rate.

Impact

Rewards are under- or over-paid whenever rates changed during the paused interval, leading to incorrect payouts and economic imbalance (theft or loss of unclaimed yield).

Recommendation

Replace the single‑rate bump with the same segmented loop used in _calculateRewardsCore, or invoke a corrected updateRewardPerTokenForValidator up to the slash timestamp so that all stored checkpoints are applied and global state advances before per‑user calculations.

Proof of Concept

Detailed PoC and test cases (expand to view)

Was this helpful?