FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: medium
Likelihood: low

`sweepUnclaimedBonus()` Can Remove Bonus Before Good-Faith CORRUPTED Re-flag, Reducing Legitimate Attacker Bounty when `totalEligibleStake == 0 || riskWindowStart == 0`

Author Revealed upon completion

Summary

sweepUnclaimedBonus() transfers bonus funds to recoveryAddress but does not set claimsStarted = true when totalEligibleStake == 0 || riskWindowStart == 0. Because of this, the moderator can still re-flag the pool from SURVIVED to good-faith CORRUPTED afterward.

If the re-flag happens before the sweep, the named attacker receives the full bounty including bonus.
If the sweep happens first, the bonus is already gone, so the attacker receives a reduced bounty (only stake amount if riskWindowStart == 0 and totalEligibleStake > 0 and 0 if totalEligibleStake == 0).

Root Cause

sweepUnclaimedBonus() can remove funds from the pool in these cases but does not set claimsStarted = true :

if (totalEligibleStake == 0 || riskWindowStart == 0) {
totalBonus -= amount <= totalBonus ? amount : totalBonus;
}

So moderator can still call flagOutcome() later and re-flag to good-faith CORRUPTED.

Code Snippet-
https://github.com/CodeHawks-Contests/2026-07-bc-confidence-pools/blob/58e8ba4ce3f3277866e4926f3140e597f9554a1e/src/ConfidencePool.sol#L499

So malicious moderator can call flagOutcome() with SURVIVED amd then call sweepUnclaimedBonus() and later re-flag to good-faith CORRUPTED. Leads to reducing legitimate attacker bounty.

Impact

A legitimate good-faith attacker can lose bounty they would.

  • If riskWindowStart == 0 and totalEligibleStake > 0:

    • bonus is swept first

    • attacker later receives only the remaining stake amount

  • If totalEligibleStake == 0:

    • sweep removes everything

    • attacker later receives 0

PoC

  1. If re-flag happens first:
    Example: totalEligibleStake = 10,000, totalBonus = 5,000
    Moderator re-flags to good-faith CORRUPTED before sweep.
    Attacker bounty = 10,000 + 5,000 = 15,000

  2. If sweep happens first:
    Example: totalEligibleStake = 10,000, totalBonus = 5,000, riskWindowStart = 0
    sweepUnclaimedBonus() sends 5,000 bonus to recoveryAddress first.
    Then moderator re-flags to good-faith CORRUPTED.
    Attacker bounty = 10,000 only

  3. If total stake is already zero:
    Example: totalEligibleStake = 0, totalBonus = 5,000
    sweepUnclaimedBonus() sends all 5,000 to recoveryAddress.
    Then moderator re-flags to good-faith CORRUPTED.
    Attacker bounty = 0

Mitigation

Set claimsStarted = true when (totalEligibleStake == 0 || riskWindowStart == 0) is true:

if (totalEligibleStake == 0 || riskWindowStart == 0) {
totalBonus -= amount <= totalBonus ? amount : totalBonus;
if (!claimsStarted) claimsStarted = true;
}

This closes the re-flag window once bonus is swept.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!