stake() does not commit to scope or recoveryAddress, so a sponsor can expand coverage to a vulnerable contract and lock stakers before they exitStakers review scope and recovery destination before depositing. Scope is the pool's published insurance commitment: it is what the moderator will treat as the breach surface on CORRUPTED, and recoveryAddress receives corrupted or residual sweeps.
stake(uint256 amount) accepts only an amount. While the registry remains in pre-attack staging (NOT_DEPLOYED / NEW_DEPLOYMENT), the sponsor can still call setPoolScope(). setRecoveryAddress() is mutable at any time. There is no expected-scope / expected-recovery / config-hash check at deposit time, and no event-driven commitment that freezes the reviewed parameters for already-deposited stakers.
The worst practical path is not merely narrowing coverage before a pending stake. After honest stakers have already deposited against a reviewed, ostensibly safe scope:
The sponsor adds a known-vulnerable BattleChain account to the pool scope (still allowed while pre-attack).
Stakers may not notice the scope change in time — there is no on-chain stake binding to the prior scope, and watching every PoolScopeUpdated event is an off-chain operational burden.
The sponsor (or agreement flow) moves the registry to ATTACK_REQUESTED and then has the attack accepted into UNDER_ATTACK.
Withdrawals close permanently once risk is observed. Stakers who still believed they underwrote the old scope can no longer exit.
If the added vulnerable contract is breached and the pool is flagged (or mechanically resolved) CORRUPTED, principal and bonus are swept to the sponsor-controlled recoveryAddress.
A narrower pre-stake bait-and-switch (replace scope / redirect recovery before the first deposit lands) is the same root cause with a weaker impact path.
Likelihood:
Occurs whenever stakers have already deposited while the registry is still pre-attack and the sponsor can still mutate scope.
Requires the sponsor to expand (or replace) scope toward a vulnerable account, then have the attack path accepted before stakers observe the change and call withdraw().
Stakers that do not continuously monitor scope updates will miss the exit window even when withdraw is still technically open for a short period.
Impact:
Stakers can be bound to insurance coverage they never reviewed: a sponsor-added vulnerable contract becomes the pool's breach surface.
Once UNDER_ATTACK is observed, principal is locked. An in-scope corruption of that added contract can forfeit the full stake + bonus to recoveryAddress.
Separately, recoveryAddress can still be redirected after deposit, changing who receives any corrupted / residual sweep relative to what was reviewed.
Setup: append the function below to test/unit/ConfidencePool.t.sol (same ConfidencePoolTest harness / BaseConfidencePoolTest helpers already used by the suite), then run:
Alice reviews the default (safe) scope account and stakes 100 * ONE while the registry is still pre-attack; withdrawals remain open.
The sponsor expands pool scope to include a previously unseen vulnerable account (and can also redirect recoveryAddress).
Before alice notices and withdraws, the registry moves through ATTACK_REQUESTED into UNDER_ATTACK, sealing riskWindowStart.
Alice's withdraw() now reverts with WithdrawsDisabled — she is locked under coverage that includes the added vulnerable contract.
The agreement is marked CORRUPTED and the moderator flags bad-faith CORRUPTED; claimCorrupted() sweeps alice's full principal to the (possibly redirected) recovery address.
Require stake-time commitment to material configuration, and either freeze scope once any stake exists or emit / enforce a clear re-consent path before post-deposit scope expansion.
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.