51961 sc high attackers can deny commission rewards to validators by repeatedly calling forcesettlevalidatorcommission
Report ID: #51961
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/lib/PlumeRewardLogic.sol
Submitted on: Aug 6th 2025 at 21:02:21 UTC by @KlosMitSoss
Context: Attackathon | Plume Network — https://immunefi.com/audit-competition/plume-network-attackathon
#51961 [SC-High] Attackers can deny commission rewards to validators by repeatedly calling forceSettleValidatorCommission()
Brief / Intro
An attacker can repeatedly call ValidatorFacet::forceSettleValidatorCommission() with a low timeDelta to cause the commission accrual to always round down to zero in certain scenarios. As a result, the affected validators will never accrue any commission.
Vulnerability Details
When ValidatorFacet::forceSettleValidatorCommission() is called, it triggers settlement of accrued commission for a specific validator. This updates the validator's cumulative reward-per-token indices (for all reward tokens) and their accrued commission storage. It uses the validator's current commission rate for settlement.
Within this function, PlumeRewardLogic::_settleCommissionForValidatorUpToNow() is called. This calls PlumeRewardLogic::updateRewardPerTokenForValidator() for every reward token. Here, grossRewardForValidatorThisSegment is calculated using the following formula:
totalStaked * timeDelta * effectiveRewardRate / PlumeStakingStorage.REWARD_PRECISIONThis value is then used for calculating the commissionDeltaForValidator:
grossRewardForValidatorThisSegment * commissionRateForSegment / PlumeStakingStorage.REWARD_PRECISIONThis leads to the following formula for the accrued commission:
totalStaked * timeDelta * effectiveRewardRate / PlumeStakingStorage.REWARD_PRECISION
* commissionRateForSegment / PlumeStakingStorage.REWARD_PRECISIONThe validatorAccruedCommission[validatorId][token] (the commission that can later be claimed) is then increased by commissionDeltaForValidator.
Example scenario:
totalStaked = 1e9
effectiveRewardRate = 1_587_301_587 (value used in tests)
commissionRateForSegment = 5e16 (5%)
When a user calls ValidatorFacet::forceSettleValidatorCommission() every 12 seconds, the calculation rounds down to zero:
1e9 * 12 * 1_587_301_587 / 1e18 * 5e16 / 1e18 = 0.95As a result, anyone can exploit this by repeatedly calling ValidatorFacet::forceSettleValidatorCommission(). This prevents the validator from ever accruing any commission as long as the configuration remains the same. This also works for different rate values and different totalStaked amounts; this is just one example.
Impact Details
Validators will never accrue any commission if their different rates and totalStaked amounts produce values that round down to zero for a low timeDelta.
Proof of Concept
Step
Within the function, PlumeRewardLogic::_settleCommissionForValidatorUpToNow() is called, which calls PlumeRewardLogic::updateRewardPerTokenForValidator().
Reference: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L794-L802
Step
The accrued commission is calculated and rounded down. In certain scenarios (as described above), depending on totalStaked, the reward rate, and commission rate, this rounds down to zero.
Reference: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L175-L187
References
ValidatorFacet::forceSettleValidatorCommission(): https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/ValidatorFacet.sol#L745-L756
PlumeRewardLogic::_settleCommissionForValidatorUpToNow() / updateRewardPerTokenForValidator(): https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L794-L802
Accrued commission calculation: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/lib/PlumeRewardLogic.sol#L175-L187
Was this helpful?