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.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.