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

Owner Can Pause Pool to Eliminate Staker Race During Outcome Re-Flag

Author Revealed upon completion

Description

The pause() function can be used by the owner to freeze all staker claims (claimSurvived, claimExpired, withdraw), creating a risk-free window for the owner to re-flag the outcome and claim funds without competition from stakers.

Vulnerability Details

The flagOutcome() function (line 327) allows the moderator to re-flag the outcome before claimsStarted. In a fair race, stakers can call claimSurvived() to lock in SURVIVED before the moderator re-flags to CORRUPTED. However, the owner can call pause() (line 662) to prevent all claims:

function pause() external onlyOwner whenPoolNotPaused {
_pause();
}
// All claim functions use the whenPoolNotPaused modifier
function claimSurvived() external nonReentrant whenPoolNotPaused { ... }
function claimExpired() external nonReentrant whenPoolNotPaused { ... }

The owner can also call flagOutcome() while paused (no whenPoolNotPaused modifier).

Impact

Owner eliminates the race with stakers:

  1. Flag SURVIVED

  2. pause() → all staker claims revert

  3. Re-flag to CORRUPTED without competition

  4. claimAttackerBounty() → full pool to owner

  5. unpause()

  6. Stakers can only watch as their funds are taken

Proof of Concept

function testPauseReFlagFrontRun() public {
// Owner flags SURVIVED
vm.prank(owner);
pool.flagOutcome(SURVIVED, false, address(0));
// Owner pauses pool — stakers cannot claim
vm.prank(owner);
pool.pause();
// Owner re-flags to CORRUPTED with no competition
vm.prank(owner);
pool.flagOutcome(CORRUPTED, true, owner);
// Owner claims bounty
uint256 balanceBefore = token.balanceOf(owner);
vm.prank(owner);
pool.claimAttackerBounty();
assertGt(token.balanceOf(owner), balanceBefore);
// Staker attempt to claimSurvived would revert
vm.prank(owner);
pool.unpause();
vm.prank(staker);
vm.expectRevert(OutcomeNotSet.selector);
pool.claimSurvived(); // Now CORRUPTED
}

Recommended Mitigation

Add whenPoolNotPaused modifier to flagOutcome():

function flagOutcome(...) external whenPoolNotPaused {
// existing logic
}

References

  • ConfidencePool.sol:662 — pause function

  • ConfidencePool.sol:327 — flagOutcome (no pause modifier)

  • ConfidencePool.sol:382 — claimSurvived (has whenPoolNotPaused)



Support

FAQs

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

Give us feedback!