Sponsors are allowed to update expiry only until the first stake. This is meant to protect staker reliance: once anyone deposits against a deadline, the sponsor can no longer move it. Deposits during UNDER_ATTACK are also intentionally allowed; the depositor voluntarily accepts visible active risk, and the deposit self-locks into the resolution path.
The first active-risk staker cannot bind the expiry they inspected. While the pool has no prior stake, the sponsor can front-run the first pending stake() call with setExpiry() to a much later timestamp. The victim's stake() then observes UNDER_ATTACK, locks the sponsor-mutated expiry, sets riskWindowStart, and immediately disables withdrawal. At the originally inspected expiry, claimExpired() still reverts because the sponsor-selected later expiry is now binding.
This is not a finding that UNDER_ATTACK deposits are unsafe in general. The issue is a term-rebinding race against the first active-risk staker.
The mutable expiry setter is ConfidencePool.sol::setExpiry:
The first stake locks the currently stored expiry in ConfidencePool.sol::stake:
The deposit gate in ConfidencePool.sol::_assertDepositsAllowed still allows staking during UNDER_ATTACK:
The withdrawal gate in ConfidencePool.sol::withdraw disables exit after risk is observed:
Likelihood: Medium
The pool has no prior stake, so expiryLocked is still false.
The registry is already in UNDER_ATTACK, where staking is intentionally allowed.
A sponsor observes the first pending stake and submits setExpiry() first, changing the term before the victim's transaction executes.
Impact: Medium
The first active-risk staker is forced into a longer underwriting term than the one they inspected.
The same stake transaction seals riskWindowStart, so the staker cannot withdraw after execution.
The longer term increases the staker's exposure to later CORRUPTED settlement and withholds liquidity until the sponsor-selected expiry or moderator resolution.
The PoC file included with this report is:
To run it in a fresh contest checkout:
Copy ActiveRiskFirstStakeExpiryFrontRunPoC.t.sol into the repository's test/ directory.
Run:
Expected result:
The PoC demonstrates:
The exploit path: registry is UNDER_ATTACK, sponsor extends expiry before the first stake, the staker locks the changed expiry, cannot withdraw, and cannot claim at the original expiry.
Inline PoC source (ActiveRiskFirstStakeExpiryFrontRunPoC.t.sol):
Prevent expiry changes once active risk can be observed, even if no stake has landed yet. A local fix is to make setExpiry() observe registry state and reject outside pre-attack states.
For stronger user protection, add a stake entrypoint that binds expected terms, such as stakeWithExpectedExpiry(amount, expectedExpiry), so pending stake transactions revert if the sponsor changes the expiry before execution.
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.