The pool distributes bonus shares using a quadratic formula that measures each staker's (outcomeFlaggedAt − entryTime)² score. The implementation stores global and per-user sums of amount × entryTime and amount × entryTime², then evaluates T²·stake − 2T·Σ(stake·entryTime) + Σ(stake·entryTime²) at claim time, where T is outcomeFlaggedAt. Because T and entryTime are absolute Unix timestamps rather than elapsed durations, the intermediate products can overflow checked uint256 arithmetic when the allowlisted ERC20 has a sufficiently large raw-unit balance. The pool can successfully record risk-window accounting at an earlier timestamp riskWindowStart, but when outcomeFlaggedAt is later and larger, _bonusShare() reverts before transferring principal or bonus, permanently locking funds for every staker in an affected pool that has a positive bonus and resolves SURVIVED or EXPIRED.
Likelihood:
The factory allowlist must include a standard ERC20 whose total supply, decimals, or balances permit raw stake around type(uint256).max / T² to be reached; at production-scale timestamps this is approximately 3.7e58 raw units.
An attacker must control or borrow enough token units to push totalEligibleStake into the unsafe range, where type(uint256).max / T² < totalEligibleStake ≤ type(uint256).max / riskWindowStart².
Common 18-decimal tokens with ordinary supplies are far below this threshold, so the precondition depends on governance's token selection and is unusual.
Once such a token is allowlisted, any staker can deposit, any account can contribute a positive bonus, and favorable claims execute the overflowing math without additional privilege.
Impact:
Favorable principal and bonus recovery can be permanently bricked for all stakers once the global snapshot exceeds the safe arithmetic range.
The pool reserves remaining principal plus unclaimed bonus while stakers remain unclaimed, so sweepUnclaimedBonus() cannot rescue the locked funds.
The issue affects every staker in the pool, not only the attacker, and does not create theft but permanently locks the commingled pool balance.
test/poc/AbsoluteTimestampQuadraticOverflowPoC.t.sol:
Compute bonus scores using elapsed durations rather than absolute Unix timestamps. Rebase stored per-user and global sums around riskWindowStart by recording amount × (entryTime − riskWindowStart) and amount × (entryTime − riskWindowStart)², then use D = outcomeFlaggedAt − riskWindowStart in the quadratic formula. This bounds the squared term by pool duration rather than absolute Unix time. Enforce an explicit maximum totalEligibleStake derived from the pool's maximum possible duration so the worst-case score computation cannot overflow. If exact large-magnitude support is required, construct the entire score with 512-bit-safe arithmetic, not only the final Math.mulDiv() step. Consider a principal-only fallback or emergency migration path so principal remains recoverable if bonus calculation ever becomes impossible.
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.