_bonusShare computes each staker's share of the bonus pool from a k=2 confidence score. The score is evaluated in an algebraically expanded moment form — T²·stake + Σamount·entry² − 2T·Σamount·entry — that is mathematically equal to the compact Σ amount·(T − entry)². The global score used as the shared denominator is snapshotted at flagOutcome and reused by every claimant.
Because the expanded intermediate T²·snapshotTotalStaked + snapshotSumStakeTimeSq is evaluated with Solidity 0.8 checked arithmetic, a single large-magnitude stake makes that term exceed 2²⁵⁶ and revert Panic(0x11), even though the equivalent compact score fits in uint256 with enormous headroom. The poisoned value lives in the frozen global snapshot, so every claimSurvived() / claimExpired() reverts permanently. Nothing caps aggregate stake magnitude at deposit time.
The raw moments are recorded at stake time with checked multiplication (src/ConfidencePool.sol:252-260); recording stays representable (A·S² fits for the witness, so the deposit is accepted), and the overflow surfaces only later, in the expanded claim-time score.
Likelihood:
When one pool accumulates an aggregate eligible stake of roughly 1.89e58 base units, so that T²·snapshotTotalStaked + snapshotSumStakeTimeSq = (A+V)·(2S² + 2S + 1) crosses 2²⁵⁶ − 1 at timestamp-scale entry times (S ≈ 1.75e9).
When the pool's token is allowlisted with high decimals — the factory gates only on a boolean allowlist and imposes no decimals ceiling (ConfidencePoolFactory.sol:77,155). Trust-model caveat: at 18 decimals this needs ~1.9e40 whole tokens (economically unreachable); it becomes reachable only when the trusted factory owner allowlists an exotic >54-decimal ERC20 (1.9e4 tokens at 54 decimals, ~0.019 token at 60), the same token-curation responsibility the allowlist already carries for fee-on-transfer / rebasing assets. This is why the finding is filed as Low rather than Medium.
Impact:
All honest principal + bonus in a SURVIVED or EXPIRED pool become permanently unclaimable: withdraw() is closed once outcome is set (ConfidencePool.sol:289), and sweepUnclaimedBonus reserves totalEligibleStake + (snapshotTotalBonus − claimedBonus) (483-487) → amount == 0 → NothingToSweep. The clone is non-upgradeable, so funds are permanently stuck.
Pure griefing / DoS, not extraction: the attacker locks its own outsized stake too, so the poisoned denominator reverts even honest stakers whose own score is tiny (the correct compact global score (A+V)·(T−S)² = A+V ≈ 1.89e58 fits trivially).
The control test test_M01_Control_NormalMagnitude_ClaimsSucceed runs the identical pool without the outsized stake and pays the honest staker V + bonus normally, isolating the overflow to the expanded representation of the large position.
Bound aggregate eligible stake at deposit time so the expanded terms cannot overflow for any T ≤ expiry (a cheap, conservative alternative to evaluating the score in exact 512-bit arithmetic or storing time moments relative to a bounded origin):
The factor of two bounds both positive terms and the doubled first-moment term for all T and entry times ≤ expiry; add boundary tests before adoption.
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.