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