53061 sc high asymmetric rounding in commission ceil for users floor for validators enables per segment rounding loss validators can amplify via frequent commission checkpoints
Submitted on Aug 14th 2025 at 18:31:09 UTC by @tansegv for Attackathon | Plume Network
Report ID: #53061
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/lib/PlumeRewardLogic.sol
Impacts:
Theft of unclaimed yield
Contract fails to deliver promised returns, but doesn't lose value
Description
Users are charged commission with ceiling rounding per time segment while validators accrue commission with floor rounding on aggregated math. By creating many commission checkpoints (more segments), a validator can increase rounding events so that small segments yield zero user net rewards more often, causing economic leakage from users.
The reward/commission pipeline divides time into segments at reward/commission checkpoints. In each segment, user commission is computed with ceil, validator accrual with floor. Increasing the number of segments (by updating commission frequently) increases the number of ceiling events on users, leading to consistent, compounding rounding loss for stakers.
Vulnerability Details
User commission uses ceil (bias up):
// In _calculateRewardsCore(...) for the user
uint256 grossRewardForSegment =
(userStakedAmount * rewardPerTokenDeltaForUserInSegment) / REWARD_PRECISION;
uint256 commissionForThisSegment =
_ceilDiv(grossRewardForSegment * effectiveCommissionRate, REWARD_PRECISION);
if (grossRewardForSegment >= commissionForThisSegment) {
totalUserRewardDelta += grossRewardForSegment - commissionForThisSegment;
} else {
// net reward becomes 0 for this segment
}Validator commission accrual uses floor (bias down vs exact):
// In updateRewardPerTokenForValidator(...) for the validator
uint256 grossRewardForValidatorThisSegment =
(totalStaked * rewardPerTokenIncrease) / REWARD_PRECISION;
uint256 commissionDeltaForValidator =
(grossRewardForValidatorThisSegment * commissionRateForSegment) / REWARD_PRECISION;Segmentation multiplies rounding events:
getDistinctTimestamps(...)merges reward-rate and commission-rate checkpoint times. Each commission update creates another segment. With many short segments,grossRewardForSegmenttends to small integers; with any nonzero commission rate,ceilfrequently rounds the per-segment user commission to1, making net0for that segment.Who can drive it: The validator admin (not protocol admin) can call
setValidatorCommission(...)repeatedly (bounded only bymaxCommissionCheckpoints, min allowed ≥10). Same-block updates are coalesced, but across blocks they can push many checkpoints.
Impact: Economic leakage from users to validators/protocol due to asymmetric rounding and “zeroed” segments. Especially harmful for small stakers or when reward rates are low.
Impact Details
Impact: Economic leakage from users to validators / protocol (due to asymmetric rounding and “zeroed” segments). Over many periods and many users, this can be material—especially for small stakers or when reward rates are low.
Exploitability: No privileged roles required beyond a validator admin of their own validator; they can create many commission checkpoints (until hitting the configured cap).
Bounded but amplifiable: Loss per user is roughly bounded by O(#segments) × 1 unit per relevant segment while reward is small; increasing segment count amplifies loss. Even if validator accrual floors globally, users are still charged via ceil per segment, so users can be systematically shorted on small segments.
Proof of Concept
Was this helpful?