52931 sc high validators can not claim their commissions after the reward token removal
Submitted on Aug 14th 2025 at 11:49:48 UTC by @Slayer for Attackathon | Plume Network
- Report ID: #52931
- Report Type: Smart Contract
- Report severity: High
- Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/facets/ValidatorFacet.sol
- Impacts:
- Permanent freezing of funds
Description
Brief/Intro
The requestCommissionClaim function prevents validators from claiming earned commission after a reward token has been removed from the system. While the removeRewardToken function correctly updates accrued validator commissions, the commission claim process requires the token to still be active, creating a permanent lock on validator funds.
Vulnerability Details
When a reward token is removed:
// RewardsFacet.sol:removeRewardToken()
for (uint256 i = 0; i < $.validatorIds.length; i++) {
uint16 validatorId = $.validatorIds[i];
// Final update to current time to settle all rewards up to this point
PlumeRewardLogic.updateRewardPerTokenForValidator($, token, validatorId);
// Create a final checkpoint with a rate of 0 to stop further accrual definitively
PlumeRewardLogic.createRewardRateCheckpoint($, token, validatorId, 0);
}
// Update the mapping
$.isRewardToken[token] = false;
The function correctly:
- Settles all pending validator commission up to the removal timestamp
- Stores commission in
$.validatorAccruedCommission[validatorId][token] - Sets
$.isRewardToken[token] = false
However, when validators try to claim this commission:
function requestCommissionClaim(
uint16 validatorId,
address token
)
external
onlyValidatorAdmin(validatorId)
nonReentrant
_validateValidatorExists(validatorId)
_validateIsToken(token)
The _validateIsToken modifier implementation:
modifier _validateIsToken(address token) {
if (!PlumeStakingStorage.layout().isRewardToken[token]) {
revert TokenDoesNotExist(token);
}
_;
}
Problem flow:
Token removal and settling
- Token is removed →
$.isRewardToken[token] = false. - Validator commission is settled and stored in
$.validatorAccruedCommission[validatorId][token].
Claim attempt
- Validator calls
requestCommissionClaim(validatorId, removedToken). _validateIsToken(removedToken)checks$.isRewardToken[removedToken]→ returnsfalse.- Function reverts with
TokenDoesNotExist(token).
While users can claim rewards from removed tokens because RewardsFacet._validateTokenForClaim() includes fallback logic:
if (!isActive) {
// Check if there are previously earned/stored rewards
// ... fallback logic for removed tokens
if (!hasRewards) {
revert TokenDoesNotExist(token);
}
}
validators have no such mechanism.
Impact Details
Validators lose all accrued commission from removed reward tokens — effectively permanent freezing of those funds.
Proof of Concept
- Call
removeRewardToken(...)→$.isRewardToken[token] = false. - Validator commission is updated and stored in
$.validatorAccruedCommission[validatorId][token].
3. Validator calls requestCommissionClaim(validatorId, removedToken).
4. _validateIsToken(removedToken) checks $.isRewardToken[removedToken] → returns false.
5. Function reverts with TokenDoesNotExist(token), blocking the claim.
References
(Add any relevant links to documentation or code)