Medium
The effect is permanent claim denial for a resolved pool under a supported-token flow: both principal and bonus can remain stuck in the pool, while sweepUnclaimedBonus cannot recover the funds because eligible stake remains reserved. Likelihood is reduced because the attacker must control an extremely large amount of the standard stake token and their own stake is also locked, but the path does not rely on unsupported token behavior, privileged roles, or dishonest dependencies.
ConfidencePool.stake accepts deposits without bounding the arithmetic domain later used by _bonusShare. During settlement, _bonusShare computes k=2 scores through expanded checked-uint256 intermediates:
For a sufficiently large but accepted standard-ERC20 stake, those expanded terms overflow before cancellation, even though the literal score
is finite. As a result, claimSurvived / claimExpired revert with Panic(0x11) and the pool becomes permanently unclaimable for affected stakers.
src/ConfidencePool.sol
stake
_bonusShare
Same-root supporting symptom: _markRiskWindowStart can also overflow for huge accepted stake amounts.
Relevant lines:
The protocol uses O(1) accumulated moments to represent the k=2 score:
That literal score is expanded into:
The final mathematical result may still fit, but the expanded intermediate terms can overflow uint256 first. Because the overflow happens before the final ratio calculation, Math.mulDiv does not protect this path.
There is no live stake cap or full-width intermediate arithmetic enforcing a safe domain for:
received * entry^2 during stake-time moment accumulation,
totalEligibleStake * T^2 during _markRiskWindowStart,
snapshotTotalStaked * T^2 and userEligible * T^2 during _bonusShare.
The pool uses a standard, amount-conserving ERC20.
Registry and moderator behave honestly.
Risk is observed, then the product later resolves SURVIVED (or an equivalent path that reaches _bonusShare).
At least one positive bonus unit exists so _bonusShare is exercised.
The attacker can provide a very large standard-token stake during UNDER_ATTACK, which is explicitly allowed.
At the repository test base timestamp, the minimized accepted stake used in the PoC is:
base units.
An honest staker deposits a normal amount.
The registry enters UNDER_ATTACK and the pool records riskWindowStart.
The attacker stakes a huge but accepted amount during the allowed UNDER_ATTACK phase.
A bonus contribution is made.
One second later, the registry reports PRODUCTION and the moderator honestly flags SURVIVED.
A staker tries to claim. _bonusShare overflows while computing the expanded global/user score and reverts with Panic(0x11).
Other claims also revert, eligibleStake remains nonzero, and sweepUnclaimedBonus cannot drain the pool because the stake reserve is still live.
This is not just a bonus-accounting inaccuracy. The pool can become permanently unclaimable for the affected settlement path:
honest stakers cannot recover principal,
bonus cannot be distributed,
hasClaimed is never set,
funds remain trapped in the pool,
the attacker’s own stake also remains trapped, so the issue is a liveness/funds-stuck flaw rather than a profit extraction.
This makes Medium severity appropriate: exploitability is constrained by the extreme capital requirement, but the impact is real and affects principal recovery under supported-token, honest-dependency conditions.
This issue is distinct from the documented/accepted design choices:
It is not disagreement with quadratic weighting; the literal quadratic score is finite.
It is not the accepted pre-risk clamp policy in the main PoC; risk is already observed.
It is not the no-risk / zero-bonus branch; riskWindowStart != 0.
It is not unsupported token behavior; the PoC uses a normal MockERC20.
It is not a trusted-role issue; the registry and moderator follow the honest path.
It is not merely “large late stake earns tiny bonus”; the implementation reverts before any valid payout can occur.
Command:
Actual output:
Command:
Actual output:
Command:
Actual output:
The same missing arithmetic bound also appears earlier in the lifecycle: a huge accepted pre-risk stake can make _markRiskWindowStart overflow on totalEligibleStake * t * t, causing risk observation to revert atomically. That is best treated as the same underlying arithmetic-domain bug, not a separate submission.
Either:
compute the moment/score expressions with full-width intermediate arithmetic, or
reject stakes that would move the pool outside the safe arithmetic domain.
A conservative cap can be enforced with division-first arithmetic:
The factor of 2 conservatively covers both T^2 * S and M2 inside the expanded plus term because effective entries and T are bounded by expiry.
Boundary tests should be added for:
accepted stake exactly at the cap,
rejected stake at cap + 1,
claim after a one-second observed-risk window,
_markRiskWindowStart at the cap.
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.