setRecoveryAddress() allows the pool owner to change the CORRUPTED sweep destination at any time. claimCorrupted() and sweepUnclaimedCorrupted() read the live recoveryAddress variable at the moment of transfer, not a value captured at resolution time.
setRecoveryAddress() is gated only by onlyOwner and a non-zero address check. It has no guard preventing calls after the pool outcome has been finalized. Both claimCorrupted() and sweepUnclaimedCorrupted() unconditionally transfer the full sweepable balance to recoveryAddress, reading the live storage variable rather than a snapshot taken when the outcome was flagged.
docs/DESIGN.md §10 describes recoveryAddress as the "CORRUPTED sweep destination" and notes that "stakers should verify pool parameters before depositing", but does not document that the owner can change this address after the outcome has been set and stakers can no longer exit their position (withdraw is permanently disabled from UNDER_ATTACK onward).
Likelihood: LOW
The owner (pool sponsor) must have an incentive to redirect funds away from the originally-committed recovery party. A solo sponsor with full control over the pool direction can execute this at any time after a CORRUPTED outcome.
The attack requires no external conditions: the owner calls setRecoveryAddress(), then anyone calls claimCorrupted() or sweepUnclaimedCorrupted().
Only the onlyOwner modifier gates access — the function is callable regardless of outcome state.
Impact: HIGH
Under a bad-faith CORRUPTED outcome: the entire pool balance (all staker principal + all contributed bonus) is redirected to the owner-controlled address instead of the original recovery address.
Under a good-faith CORRUPTED outcome: post-resolution donations or the post-bounty remainder that should flow to the recovery party are redirected to the owner.
Stakers have no recourse: withdrawal was permanently disabled once the registry reached an active-risk state (UNDER_ATTACK onward), and the pool outcome is already finalized.
The PoC forks BattleChain testnet (block 17,095, RPC https://testnet.battlechain.com), deploys the actual ConfidencePool and mock dependencies, and demonstrates both attack paths:
Results:
| Test | Gas | Status |
|---|---|---|
test_BadFaithCorrupted_FundsRedirected |
601,235 | ✅ PASS |
test_GoodFaithCorrupted_RemainderRedirected |
704,126 | ✅ PASS |
Run with:
Snapshot the recoveryAddress at resolution time and use the snapshot in all CORRUPTED sweep functions, or guard setRecoveryAddress to revert when the outcome has been finalized:
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.