Normal behavior. Stakers lock capital to signal confidence that an agreement's in-scope contracts will survive their attackable term, and are compensated from the sponsor's bonus for bearing that exposure. The bonus is only earned once a risk window is observed (riskWindowStart != 0); DESIGN.md ## 9 states that "no observed risk → no bonus is fair: a staker only forfeits the exit option once risk has actually materialized," and that "a sponsor cannot grief stakers by keeping the agreement out of attackable mode — stakers can freely exit until risk materializes."
The issue. ## 9's defense assumes the only way to avoid the risk window is to linger in a pre-attack state, during which stakers retain the withdraw exit. But the sponsor is the agreement owner, and the upstream goToProduction() lets the agreement owner jump directly and instantly to terminal PRODUCTION, skipping the attack phase entirely — no DAO approval, no delay. This is neither lingering (it is terminal) nor exit-preserving (stakers cannot withdraw from PRODUCTION). Because no active-risk state was ever observed, riskWindowStart stays 0, so _bonusShare returns 0 for every staker and sweepUnclaimedBonus treats the entire bonus as unowed and sends it to the sponsor-controlled recoveryAddress. The sponsor can therefore seed a bonus to attract stakers and the confidence signal it produces, let them deposit and lock in, and then reclaim the whole bonus post-commitment — obtaining the signal for free while stakers earn nothing and have capital frozen until expiry.
Why this is different from DESIGN.md ## 9. ## 9 frames "no risk → no bonus" as a fair, benign outcome of an agreement that happened to stay safe, and relies on "stakers can freely exit until risk materializes" as the staker's protection. Neither holds here: the sponsor can force the no-risk outcome instantly and after stakers are already committed, and the goToProduction path gives stakers no exit window to react (it moves pre-attack → terminal in one call). The staker's premium is thus not a return earned by bearing risk — it is an option the sponsor grants (honest survival path) or withholds (goToProduction), decided after observing how much capital the bonus attracted.
Likelihood:
The sponsor is the agreement owner (createPool requires IAgreement(agreement).owner() == msg.sender) and holds the authorization to call goToProduction, so the action is available on every pool the sponsor creates with no external cooperation, no DAO step, and no delay.
The sponsor executes it after watching deposits accumulate: seed the bonus, let stakers deposit and lock in, then call goToProduction in a single transaction the sponsor times — stakers get no block-level window to exit ahead of it.
Nothing about the outcome is gated on a third party (unlike the CORRUPTED-attestation variant, which needs a DAO-moderator lapse); the premium denial and bonus reclaim complete purely from sponsor actions plus the pool's own expiry deadline.
Impact:
Stakers earn zero premium despite committing capital, and are locked from goToProduction until expiry: withdraw reverts (state is terminal PRODUCTION), claimSurvived needs a flag the sponsor need not provide, and claimExpired/claimSurvived only pay after expiry.
The sponsor reclaims 100% of the seeded bonus to recoveryAddress, having paid nothing for the third-party confidence the pool advertised.
The mechanism's economic premise — that the premium is a reliable reward for bearing risk — is broken: the reward is revocable by the party the confidence signal is about.
Verified end-to-end against the real BattleChain registry stack (no mocks). Principal is returned in full; the loss is the denied premium plus a full term of locked capital.
How to reproduce the test file:
The upstream concretes are solc 0.8.34, the pool is solc 0.8.26, so this needs a multi-version build, which the repo isn't set up for by default:
Unpin solc — remove solc = "0.8.26" from foundry.toml (the pool never imports the concrete upstream, so the repo normally only ever compiles 0.8.26).
Add a remapping — solady/=lib/battlechain-safe-harbor-contracts/lib/solady/src/, and recursively init the solady submodule (git submodule update --init --recursive), which the repo's flat clone omits.
solc 0.8.34 must be installed, and the E2E file is pragma 0.8.34 — it never imports the 0.8.26 pool/token sources (would be an incompatible unit); it drives them via local IPool/IToken interfaces + deployCode("ConfidencePool.sol:ConfidencePool").
The root issue is that the staker's bonus entitlement depends on a risk-window observation the sponsor can nullify at will, after stakers are locked in. Two options, in order of directness:
Accrue a bonus floor once stakers lose the exit. If a staker can be locked (no withdraw) for a term, they should earn at least a minimum premium regardless of whether the sponsor routes around the risk window. Tie bonus eligibility to the withdraw-lock/commitment, not solely to riskWindowStart != 0.
Do not let a sponsor-controlled terminal transition nullify an already-committed bonus. Where the agreement reaches PRODUCTION via a sponsor-only instant path (goToProduction / instantPromote) after stakers have committed, resolve to a bonus-paying SURVIVED (e.g. amount-weighted) rather than a zero-bonus one, so the sponsor cannot both attract stakers with a bonus and reclaim it.
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.