When a pool's term ends, claimExpired (src/ConfidencePool.sol:530) settles it. If the agreement's in-scope contracts were breached, so the registry reports CORRUPTED, principal must not go back to the staker: it routes to recoveryAddress for a bad-faith outcome or to the whitehat for a good-faith one. The auto-CORRUPTED backstop in claimExpired is what enforces that after a grace period.
The problem is that this backstop is gated on riskWindowStart != 0 (src/ConfidencePool.sol:549). riskWindowStart is sealed lazily, only when some pool entrypoint (or a pokeRiskWindow call) observes the pool during its active-risk pass. If nothing observed it, riskWindowStart stays at its default 0. In that state a post-expiry claimExpired on a genuinely CORRUPTED, in-scope agreement skips the CORRUPTED branch (the riskWindowStart != 0 guard is false), falls through to the else at L582, resolves the outcome to EXPIRED, and refunds the staker's full principal. It also latches claimsStarted = true (L592), which makes flagOutcome revert afterwards, so the moderator can no longer correct the outcome to CORRUPTED. The in-scope breach is foreclosed and recoveryAddress/the whitehat get nothing. Unlike the riskWindowStart != 0 path, this branch has no grace wait and no backstop, so the escape is immediate the moment block.timestamp >= expiry.
Likelihood: Low
The agreement's in-scope contracts must be terminally CORRUPTED while riskWindowStart == 0, meaning no pool entrypoint and no pokeRiskWindow sealed the window during the active-risk pass.
The moderator must not flag the breach before the staker's post-expiry claimExpired. A present moderator flags CORRUPTED directly, latching claimsStarted, after which claimExpired reverts, so the escape needs an absent or slow moderator across the whole term into post-expiry.
Any party can seal riskWindowStart with the permissionless pokeRiskWindow, so an attentive honest party defuses the case, which is what keeps the realizable likelihood Low.
Impact: Medium
Staker principal on an in-scope breach, which the design routes to recoveryAddress or the whitehat, is instead refunded to the staker.
The outcome latches and the moderator is permanently locked out of correcting it, so the misdirection is irreversible.
With riskWindowStart == 0 there is no grace period and no backstop at all, so a merely slow moderator past a near-expiry breach loses the entire in-scope resolution with no recovery window, not just the observed-window case.
Staking 100 tokens, driving the agreement to a terminal CORRUPTED with the risk window never sealed, then claiming after expiry shows the staker walking away with full principal while recoveryAddress gets nothing and the moderator can no longer intervene.
Run with:
Key the auto-CORRUPTED backstop off the registry state, not off whether the risk window happened to be observed. Drop the riskWindowStart != 0 condition so an in-scope CORRUPTED agreement can never fall through to the EXPIRED refund:
If a grace period is still wanted for the unobserved case, apply the same grace inside this branch rather than skipping it, so staker principal on an in-scope breach is never refunded before the moderator has had a chance to resolve it.
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.