Normal behavior: riskWindowStart should only be set before or during the observed risk window. Once riskWindowEnd has been sealed from a terminal state, the pool should not later backfill a new active-risk start after that terminal end.
I analyzed _observePoolState() and observed that it marks riskWindowStart and riskWindowEnd independently. If the pool first observes terminal CORRUPTED, riskWindowEnd is sealed while riskWindowStart remains zero. Later, after a registry replacement / stale migration / rewind reports UNDER_ATTACK, _observePoolState() can set riskWindowStart after the already-sealed riskWindowEnd.
There is no guard enforcing:
This breaks the no-risk-window corrupted fallback. A pool that should fall through to EXPIRED because no active-risk window was observed can later be forced into the bad-faith CORRUPTED auto-resolution path because riskWindowStart was backfilled after terminal observation.
Likelihood:
This occurs when a terminal state such as CORRUPTED is observed first, and a later registry replacement / stale migration / rewind reports an active-risk state such as UNDER_ATTACK.
The codebase already treats registry replacement/rewind as a relevant integration risk. This issue is another missing one-way lifecycle invariant around riskWindowStart and riskWindowEnd.
Impact:
A no-risk-window corrupted case that should resolve as EXPIRED can instead auto-resolve as bad-faith CORRUPTED after the moderator grace period.
Existing staker principal and bonus can be swept to recoveryAddress even though the active-risk window was only observed after terminal finality.
Create this file:
Run:
Observed output:
The control test shows the intended no-risk-window behavior: terminal CORRUPTED without an observed active-risk window falls through to EXPIRED, returning Alice's principal.
The exploit test shows that after riskWindowEnd is sealed first, a later stale UNDER_ATTACK observation can set riskWindowStart > riskWindowEnd. This changes the later expiry path into bad-faith CORRUPTED, sweeping Alice's principal and the bonus to recoveryAddress.
Prevent riskWindowStart from being set after riskWindowEnd has already been sealed.
As defense in depth, also validate the ordering before auto-CORRUPTED expiry resolution:
This preserves the intended no-risk-window fallback and prevents stale active-risk observations from being backfilled after terminal finality.
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.