An unresolved pool keeps reading the registry after expiry. Registry states first reached after the covered term are capped backward to expiry, allowing a later CORRUPTED resolution to confiscate already-matured stake.
At expiry, an agreement that is still active-risk—or has not entered active risk—has survived the covered term and should resolve EXPIRED, returning principal and bonus. The contract does not checkpoint this entitlement and remains UNRESOLVED until someone submits a resolution transaction.
pokeRiskWindow() remains callable after expiry. A post-expiry active-risk observation creates riskWindowStart = expiry; a later terminal observation similarly creates riskWindowEnd = expiry. claimExpired() then treats the nonzero marker and the registry's live CORRUPTED state as covered corruption. After the expiry-anchored grace period, anyone can finalize CORRUPTED and call claimCorrupted() to transfer the entire pool to recoveryAddress.
A late corruption is sufficient even when riskWindowStart was validly recorded during the term. The post-expiry poke worsens the issue by allowing both predicates to be created after maturity. flagOutcome(CORRUPTED, ...) is also affected because it validates only the live enum value, without proving corruption occurred by expiry.
Likelihood:
The pool remains unresolved beyond expiry; there is no automatic maturity checkpoint or claim deadline.
The agreement enters active risk and becomes CORRUPTED after maturity. These are valid registry transitions, while pool finalization and sweeping are permissionless once they occur.
Impact:
Every remaining staker can lose 100% of matured principal and bonus because of events outside the advertised coverage term.
Corruption first observed after expiry + MODERATOR_CORRUPTED_GRACE receives no moderator-only review period before finalization.
Create test/audit/CP001PostExpiryRegistryChanges.t.sol with the following contents:
Run the PoC from the repository root:
Execute forge test --offline --match-path test/audit/CP001PostExpiryRegistryChanges.t.sol.
Confirm that test_PostExpiryRegistryChangesRetroactivelyForfeitMaturedPool() passes and reports that one test passed with zero failures.
Extend the trusted registry interface to expose authoritative active-risk and corruption transition timestamps. Require both transitions to have occurred no later than pool expiry in claimExpired() and flagOutcome(). Capped timestamps may remain bonus-accounting bounds, but must not serve as historical evidence.
When registry history cannot be added, use separate pool-local riskObservedByExpiry and corruptionObservedByExpiry witnesses and default unprovable post-expiry corruption to EXPIRED. Do not derive either witness from capped riskWindowStart or riskWindowEnd values.
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.