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.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.