withdraw() calls _clampUserSums(msg.sender) at L305. However, the gate at L293-300 ensures that execution only reaches L305 when riskWindowStart == 0. The _clampUserSums function immediately returns when start == 0 (L680), making it an unconditional no-op in this code path.
The subsequent lines (L309-310) subtract the user's raw (unclamped) per-user sums from the global accumulators, which are also raw when riskWindowStart == 0. This is arithmetically correct today.
Likelihood:
This is a code quality issue. The dead call is harmless today because the gate prevents reaching _clampUserSums when riskWindowStart != 0.
Impact:
The dead call is misleading: it suggests clamping occurs during withdrawal, which it never does. If the gate is ever relaxed in a future upgrade, _clampUserSums would update per-user sums to stake * riskWindowStart while the global was already eagerly reset in _markRiskWindowStart. The subtraction at L309-310 would then remove the clamped value from a global that doesn't account for it, corrupting sumStakeTime and sumStakeTimeSq for all remaining stakers and breaking the k=2 bonus distribution formula.
Run: forge test --match-contract Finding3_DeadClampInWithdraw -vvv
Terminal Output:
To prevent latent accumulator corruption risks, the dead _clampUserSums call should be completely removed from withdraw(). A detailed inline comment is added to explain that the preceding execution gate guarantees riskWindowStart == 0, making clamping unnecessary and safely relying on the raw unclamped global accumulators.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.