ConfidencePool.setExpiry() lets the pool sponsor change expiry freely until the very first stake(), and stake() accepts no expected-expiry parameter. A sponsor can therefore front-run a staker's stake() and move expiry to an arbitrarily far future in the same block. The staker's deposit locks that value in permanently (expiryLocked), and once the risk window opens withdraw() is disabled — trapping their principal until the manipulated (potentially ~year-2106) expiry.
Normally a staker reads the pool's expiry (the term they underwrite), decides it is acceptable, and stakes. On the EXPIRED backstop path their principal is returned once block.timestamp >= expiry.
The sponsor controls expiry via setExpiry() while !expiryLocked, and expiryLocked only flips to true on the first stake() (contributeBonus does NOT flip it). stake() has no expectedExpiry argument, so a staker cannot make their deposit conditional on the term they saw. A sponsor observing a pending stake() front-runs it with setExpiry(farFuture); the staker's tx then mines against the manipulated expiry and permanently locks it in.
Likelihood:
The sponsor holds other stakers' capital and gains by extending the lock-up beyond the agreed term; front-running is free and deterministic on a public mempool, so a sponsor simply watches for the first stake() into a pool they own.
Applies to the first staker of every pool, since expiryLocked is false until that exact deposit lands.
Impact:
The first staker's principal is locked until the manipulated expiry (up to type(uint32).max ~ year 2106) once the risk window opens and withdraw() is disabled. On the EXPIRED path this is an ~80-year lock — economically equivalent to a loss of the funds (time value ~ 100%).
Earlier recovery depends on a moderator resolving the pool (SURVIVED / CORRUPTED), which is not guaranteed and is outside the staker's control.
test/fuzz/ExpiryFrontRunPoC.t.sol — passes:
Bind the staker's deposit to the term they observed by adding an expectedExpiry (slippage-style) parameter to stake():
Alternatively (or in addition), lock expiry on the first value-bearing interaction of any kind — including contributeBonus — so a pool that has attracted any capital can no longer have its term moved.
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.