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