A confidence pool commits to a specific set of BattleChain contracts (its scope). Stakers read that scope, decide they believe those contracts will survive, and deposit. The docs are explicit about what this buys them. In the DESIGN.md section 8 says "stakers' exposure is bounded by what they signed up for at deposit time" and in section 10 tells stakers to "All staker-relevant state is on-chain; stakers should verify pool parameters before depositing."
The problem is that verifying at deposit time isn't enough. setPoolScope stays open until scopeLocked flips, and scopeLocked only flips once the registry leaves the pre-attack staging conditions. So there's a live window where a staker has already deposited and the scope can still be replaced. Once the registry moves to UNDER_ATTACK, the one-way riskWindowStart permanently disables withdraw(), so the staker is committed to whatever the scope was changed to, with no way out.
This does not require any bad intent from the sponsor. A regular, scope adjustment to narrow the agreement, swapping set of contracts still within the general scope etc. This silently rebinds every existing staker to the new scope and removes their exit. The trap is a property of the mechanism, not of the sponsor's motives; a malicious sponsor is only the worst case. Meaning the staker remains in conditions that they did not sign up for upon deposit. Which could eventually lead to loss of funds if any of the new swapped out scoped which they didn't intend to put confidence in gets corrupted.
Likelihood:
The scope mutable window and the deposit window overlap by design — both are open during NOT_DEPLOYED and NEW_DEPLOYMENT, this means that any scope changes made after a staker has already committed funds rebinds them, whether or not that was the intent.
setPoolScope runs _observePoolState() and then only checks scopeLocked. A scope change that occurs in the block or period before the UNDER_ATTACK state is reached (which is allowed) leaves the staker no time in which to react. Withdrawals are blocked once the state transitions.
Stakers cannot defend by monitoring, because the change and the withdraw-lock can fall in the same block. It is intended and allowed behavior for a moderator to be able to change scope until locked.
I go for a medium likelihood simply because for the loss of funds to occur, the eventual state has to be corrupted.
From codehawks docs
Medium: Requires specific conditions (unusual token parameters)
Impact:
The staker's risk profile changes without their consent. This means that they end up insuring or putting confidence in contracts they did not choose, and cannot exit.
When the new scope is breached, the pool resolves CORRUPTED and the staker's full principal is swept to recoveryAddress.
The protocol's own deisgn doc section 8 guarantee ("bounded by what they signed up for at deposit time") is broken.
I go for a medium impact as well.
From codehawks docs on severity
Medium Impact: Indirect fund risk or moderate functionality disruption.
Therefore a medium severity issue.
Drop the constant and the two test functions inside ConfidencePool.t.sol.
To run both tests:
forge test --match-contract ConfidencePoolTest --match-test "test_sponsorSwapsScopeAfterDepositAndTrapsStaker" -vv
forge test --match-contract ConfidencePoolTest --match-test "test_trapLeadsToPrincipalLossOnUnwantedScope" -vv
Both cases pass: the staker is trapped, and loss of principal on a scope they never put confidence to.
Some options would be:
Freeze the scope at the first stake, exactly as the deadline is frozen. expiryLocked already flips on the first deposit and never resets, so it is the natural "someone has committed funds" signal to gate scope changes on.
If the sponsor needs to keep adjusting scope while the pool is still filling, use a grace window instead: record the timestamp of the last setPoolScope and allow withdraw() for a fixed period after it even once the risk window has opened, so a staker always has time to exit a scope they didn't sign up for.
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.