expiry is sponsor-mutable via setExpiry() only until the first stake: the first stake() sets the one-way expiryLocked latch to protect staker reliance on the deadline that feeds the k=2 weighting. Per docs/DESIGN.md §10 the latch intentionally does not reset on withdraw, to stop the sponsor from moving expiry during an all-stakers-exited moment and harming the next cohort.
Because the latch is set by any stake and never resets, a permissionless actor can stake the minStake dust and immediately withdraw() — leaving the pool with zero eligible stake but expiryLocked == true forever. The sponsor can then never call setExpiry() (it reverts ExpiryLocked) even though no staker relies on the deadline, so a misconfigured expiry can never be corrected on that pool.
Likelihood:
Immediately after pool creation, a permissionless griefer stakes minStake and withdraws it in the same or a later block, latching expiryLocked while leaving totalEligibleStake == 0.
The sponsor later needs to correct a mistyped/suboptimal expiry (the reason setExpiry exists pre-lock) and finds it permanently blocked.
Impact:
No funds are lost. The sponsor's setExpiry capability is permanently griefed on that pool even with zero stakers relying on the deadline; the sponsor's only recourse is to abandon the pool and deploy a fresh one.
Reset the latch when the pool returns to zero eligible stake (no staker relies on the deadline), or gate setExpiry on totalEligibleStake == 0 instead of on a permanent latch — this preserves §10's protection while no cohort exists.
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.