_bonusShare() expands the k=2 reward formula into a quadratic polynomial over absolute Unix timestamps. An extremely large, but interface-permitted, aggregate stake can overflow an intermediate term even though the final mathematical result would fit in uint256. Every staker claim reverts before transfer, leaving both principal and bonus inaccessible; sweepUnclaimedBonus() cannot free the funds because the unclaimed stake remains reserved.
This is Medium severity. All staker funds in one Pool can be permanently frozen, which is High impact. At the current timestamp, the trigger requires roughly 10^58 token base units of a standard ERC20 stake, making likelihood Low. Under Severity.md, High impact plus Low likelihood maps to Medium.
Normally, claimSurvived() and claimExpired() return a user's principal and pay bonus according to the k=2 time weight inside the risk window. To calculate this in O(1) time without iterating deposits, the Pool accumulates amount * entryTime and amount * entryTime^2.
The implementation expands the square using absolute time T. Solidity 0.8 checks every multiplication and addition for overflow. Math.mulDiv protects only the final ratio; it cannot protect intermediate arithmetic that has already reverted. An overflow in the global denominator makes every staker fail at the same point.
Let S = snapshotTotalStaked. In a valid ledger, snapshotSumStakeTimeSq can approach S * T^2, so plus can approach 2 * S * T^2. When it exceeds type(uint256).max, every staker reverts at the same plus calculation. The EXPIRED path sets T = expiry; the SURVIVED path uses the recorded riskWindowEnd, so both are affected.
A non-staker can first call claimExpired() and take its soft-success branch to set the outcome to EXPIRED without invoking _bonusShare. That does not solve the asset issue. Every staker still invokes _bonusShare() and reverts afterward; no function lets a staker withdraw principal while forfeiting bonus, and totalEligibleStake never decreases.
Likelihood:
The Pool observed a risk window and has nonzero bonus, causing its claims to execute _bonusShare().
An allowlisted standard ERC20 has no Factory-enforced supply cap, decimals cap, per-stake cap, or per-Pool stake cap; an attacker can provide an exceptionally large stake during the risk window.
Around the current Unix timestamp, a conservative trigger is about 10^58 base units, far above ordinary token supply. It is therefore unlikely, but it is not prohibited by the Solidity types or the exposed interface.
Impact:
After EXPIRED or SURVIVED, every staker is unable to withdraw principal and bonus, permanently freezing the affected Pool balance.
The unclaimed stake forces sweepUnclaimedBonus() to reserve principal and remaining bonus, so it cannot act as an escape hatch. CORRUPTED does not call _bonusShare() and is unaffected.
Limit total stake according to the worst-case timestamp before writing any quadratic time accumulator, or redesign accounting around relative time to avoid expanding absolute timestamp squares.
The bound must cover sumStakeTimeSq, T * T * snapshotTotalStaked, and their addition. Checking only the final Math.mulDiv is insufficient. A more robust long-term solution records times relative to the risk-window start rather than as Unix timestamps.
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.