Normal behavior: the bonus pool exists to compensate stakers for genuinely holding capital at risk while the underlying BattleChain agreement is attackable (UNDER_ATTACK/PROMOTION_REQUESTED). The k=2 time-weighted formula is supposed to pay stakers in proportion to how long they were exposed to that real risk before the term resolved.
The issue: riskWindowStart is not derived from the registry's actual state-transition history — it is set lazily, the first time any pool function happens to be called while _getAgreementState() currently reads an active-risk state:
If the registry genuinely passes through UNDER_ATTACK/PROMOTION_REQUESTED — real risk, for real — but nobody happens to call a pool function (stake, pokeRiskWindow(), anything) during that window before it resolves terminal, riskWindowStart never seals. Every staker's bonus share is then forced to zero, and the whole bonus becomes sweepable to recoveryAddress.
Critically, ConfidencePoolFactory.createPool requires the pool creator to be IAgreement(agreement).owner():
"
if (IAgreement(agreement).owner() != msg.sender) revert UnauthorizedCreator();
"
and BattleChain's AttackRegistry._registerAgreement assigns that same owner as the agreement's attackModerator:
"s_agreementInfo[agreementAddress] = AgreementInfo({
attackModerator: agreementOwner,
...
});"
attackModerator alone can call promote(), which advances UNDER_ATTACK → PROMOTION_REQUESTED. So the pool sponsor is structurally the same address that controls the pace of the underlying registry's risk-state transitions, and has direct financial motive (reclaiming the bonus they funded) to do nothing on their own pool while nudging the agreement through the active-risk window as fast as possible
Likelihood:
The sponsor is, by construction of createPool's ownership check plus the registry's attackModerator assignment, the same entity empowered to call promote() on the underlying agreement.
pokeRiskWindow() is opt-in, permissionless, and costs gas; most stakers in a given pool are passively waiting for the final outcome rather than actively monitoring and poking during the exact active-risk interval.
A pool with light day-to-day attention (the common case — this is a "confidence" instrument, not an actively-managed position) goes through its entire UNDER_ATTACK/PROMOTION_REQUESTED window without a single incidental pool interaction whenever nobody stakes, withdraws, or contributes bonus during that specific stretch.
Impact:
100% of the contributed bonus is redirected away from stakers who genuinely held capital at risk for the full covered term, to recoveryAddress — an address the sponsor controls.
Stakers recover only principal, receiving zero compensation for real risk taken, which defeats the stated economic purpose of the confidence pool (the bonus is the entire incentive to stake in the first place).
The sponsor bears no cost: they simply reclaim funds they already intended to distribute, with no on-chain evidence distinguishing "genuinely no risk occurred" from "risk occurred but was never observed."
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.