The pool owner can change expiry until the first successful stake. Bonus contributions do not engage the same lock even though they transfer non-refundable tokens into pool custody. In a bonus-only pool, the owner can repeatedly move expiry forward after every prior deadline, preventing mechanical resolution and keeping the bonus locked.
Separately, the only upper bound on a pre-stake expiry is type(uint32).max. The owner can select the year-2106 ceiling and a later staker permanently latches that value. If the moderator never resolves the pool, claimExpired() remains unavailable for decades and the staker has no early withdrawal once risk begins.
expiryLocked is set only inside stake():
The owner setter checks only that the stake latch is false and that the new timestamp is at least 30 days in the future but no greater than the uint32 ceiling:
Bonus contributors cannot withdraw their donation. sweepUnclaimedBonus() requires a SURVIVED or EXPIRED outcome, and the owner can keep block.timestamp < expiry by extending just before or even after the previous deadline because setExpiry() does not require the old expiry still to be in the future.
For principal, an owner can call setExpiry(type(uint32).max) before the first stake. The stake succeeds because the deadline is in the future and then sets expiryLocked = true. The owner cannot correct the horizon afterward, and the staker cannot call claimExpired() until the selected timestamp:
The repository documents sponsor control before first stake, so this is primarily a custody-commitment and configuration-footgun issue. Likelihood depends on a malicious or severely mistaken sponsor and on users funding a visibly extreme pool, but the affected funds can remain inaccessible for the practical lifetime of the protocol.
Likelihood:
The owner must intentionally or mistakenly keep rolling a bonus-only deadline or publish an extreme pre-stake deadline.
Expiry is public, so careful contributors and stakers can detect a far-future value before transacting; front-running the first funding transaction remains possible.
Once the edge configuration exists, the lockup is deterministic and requires no further attacker capability.
Impact:
All bonus deposited before the first stake can remain locked indefinitely because the contributor has no withdrawal path and cannot freeze the promised horizon.
Staked principal can be committed until 2106 if the moderator does not provide an earlier terminal outcome.
The extreme expiry also stretches every lifecycle operation keyed to expiry and delays the permissionless liveness backstop.
Create test/audit/CP055SponsorControlledExpiry.t.sol with the following contents:
Run the PoC from the repository root:
Execute forge test --offline --match-path test/audit/CP055SponsorControlledExpiry.t.sol -vv.
Confirm that both tests pass. One rolls a bonus-only expiry forward twice while the 50-token contribution remains held. The other locks uint32.max with a 100-token stake and shows that both deadline correction and expiry claiming remain unavailable decades later.
Freeze expiry when any accounted user value first enters the pool, not only when stake enters, and impose a protocol-level maximum duration measured from the current time or pool creation. Validate the same duration bound during initialization and later updates.
Because bonus contribution is permissionless, freezing on the first bonus allows a third party to commit the advertised deadline with a small donation. If that is undesirable, require a meaningful minimum bonus, add a sponsor-controlled funding-open phase before public contributions, or make the immutable expiry part of a signed contribution intent. Do not retain mutable expiry after accepting non-refundable custody merely to avoid that tradeoff.
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.