Root cause — flagOutcome computes corruptedClaimDeadline = uint32(_firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW) (src/ConfidencePool.sol:372). The addition widens to uint256, but the explicit uint32(...) cast truncates silently rather than reverting, so any good-faith CORRUPTED flag raised within 180 days of the uint32 ceiling (2106-02-07) wraps the deadline back to 1970.
Impact — claimAttackerBounty gates on block.timestamp > corruptedClaimDeadline (:437), so the named whitehat's 180-day window is expired on arrival and their claim reverts ClaimWindowExpired. sweepUnclaimedCorrupted then transfers the entire pool to recoveryAddress. The whitehat's full entitlement is redirected to the sponsor.
CORRUPTED_CLAIM_WINDOW is 180 days, and _firstGoodFaithCorruptedAt anchors the deadline once so the moderator cannot mint a fresh window by toggling the outcome. Every other uint32 timestamp in the contract is bounded by a guarded setter — expiry is range-checked at :625 (newExpiry > type(uint32).max reverts ExpiryTooFar) and the risk-window marks are capped at expiry.
The CORRUPTED-path deadline has no such bound. The contract-level natspec claims CORRUPTED-path timestamps are "bounded by block.timestamp + 180 days" — but that sum is precisely the expression that overflows, and the cast that consumes it truncates instead of reverting.
The in-code comment at :370 declares this "out of scope" on the grounds that it only fires "within 180 days of the 2106 ceiling." The PoC below defeats that reasoning: it constructs a pool whose 31-day expiry sits exactly at the ceiling and which passes every guard the contract enforces — the _MIN_EXPIRY_LEAD 30-day check and the ExpiryTooFar ceiling check both succeed. The contract therefore accepts a configuration whose bounty it structurally cannot pay. The window is not merely shortened; it lands ~4.28 billion seconds in the past.
Likelihood:
Fires only when a good-faith CORRUPTED flag is raised within 180 days of 2106-02-07. _firstGoodFaithCorruptedAt is real block.timestamp, so there is no way to reach the condition early — this is ~80 years out, and is the sole reason the rating is Low rather than High.
Within that window it is not an edge case but the only possible outcome: every good-faith CORRUPTED flag wraps, with no configuration that avoids it.
The pool that triggers it is valid by the contract's own rules, so nothing at construction or configuration time warns the sponsor, the contributor, or the whitehat.
Impact:
The named whitehat loses 100% of bountyEntitlement (snapshotTotalStaked + snapshotTotalBonus — the entire pool per docs/DESIGN.md §12). Their claim reverts on the first attempt.
Those funds do not stay locked; sweepUnclaimedCorrupted routes the whole pool to recoveryAddress. The good-faith disclosure incentive inverts: the sponsor whose contracts were breached collects the bounty owed to the researcher who disclosed it.
Bounded by the fact that the contract's uint32 timestamp scheme is inherently unusable past 2106 regardless, so this is a sharp edge on an already-declared horizon rather than an independent lifetime limit.
Executed against the repo's harness. Passes on unmodified source:
Saturate the deadline at the uint32 ceiling rather than truncating past it, so the window shortens gracefully instead of inverting. This keeps the "never extendable" property the existing comment protects, since the clamp can only ever lower the value.
Alternatively, reject the unpayable configuration at the boundary require expiry + CORRUPTED_CLAIM_WINDOW <= type(uint32).max in initialize / setExpiry so a pool that cannot honor its own bounty is never created.
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.