51510 sc low bypass of maxvalidatorpercentage allows a validator to exceed the decentralisation cap
Submitted on Aug 3rd 2025 at 14:14:53 UTC by @Rhaydden for Attackathon | Plume Network
Report ID: #51510
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/facets/StakingFacet.sol
Impacts: Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
StakingFacet::_validateValidatorPercentage is meant to stop any single validator from controlling more than maxValidatorPercentage of total stake. Because the check is calculated against the post-stake totalStaked and is only executed on staking, an attacker can momentarily inflate the denominator with a dummy stake to another validator, pass the check, then remove the dummy stake. The target validator’s share subsequently exceeds the cap without any further validation, breaking Plume's decentralisation guarantee.
Vulnerability Details
Relevant code:
uint256 previousTotalStaked = $.totalStaked - stakeAmount; // correct pre-stake total (unused)
if (previousTotalStaked > 0 && $.maxValidatorPercentage > 0) {
uint256 newDelegatedAmount = $.validators[validatorId].delegatedAmount; // already includes stake
uint256 validatorPercentage = (newDelegatedAmount * 10_000) / $.totalStaked; // **post-stake** total
if (validatorPercentage > $.maxValidatorPercentage) {
revert ValidatorPercentageExceeded();
}
}validatorPercentage is computed using the post-stake $.totalStaked rather than the intended previousTotalStaked. Percentage enforcement runs only in staking / restaking paths. No check is done on unstake, withdraw, or slashing.
Because unstaking decreases $.totalStaked without rechecking percentages, an attacker can:
stake a large temporary amount to another validator (inflate denominator),
stake additional funds to the target validator (check passes),
immediately unstake the temporary amount (denominator shrinks), causing the target validator’s share to silently exceed
maxValidatorPercentage.
Impact Details
Low — Contract fails to deliver promised decentralisation guarantees (a single validator can exceed the configured limit), but no direct loss of protocol value. Users delegating to that validator face higher correlated risk (slashing/downtime).
Fix
Two suggested fixes (either is acceptable):
Call
_validateValidatorPercentagewhenever$.totalStakeddecreases (e.g., inside_updateUnstakeAmounts,_removeParkedAmounts, slash handlers, etc.) to ensure the invariant always holds.
OR
Make the check monotonic by using the pre-stake total:
uint256 validatorPercentage =
(newDelegatedAmount * 10_000) / previousTotalStaked; // use pre-stake totalReferences
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/facets/StakingFacet.sol#L156-L162
Proof of Concept
Assume maxValidatorPercentage = 3 000 bp (30 %).
Was this helpful?