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

Misleading event emission

Author Revealed upon completion

claimAttackerBounty allows a zero-value call to succeed and emit a misleading event leading to confusing on-chain history / event log noise

Description

  • If freeBalance == 0 (e.g. called again immediately after a prior call already swept the full entitlement out, or in some griefing sequence where the pool balance is transiently empty), payout computes to 0. The function does not revert in this case — it falls through, skips the payout > 0 block (so no state-corrupting side effect occurs), and still emits AttackerBountyClaimed(attacker, 0, bountyClaimed, bountyEntitlement).

function claimAttackerBounty() external nonReentrant {
if (outcome != PoolStates.Outcome.CORRUPTED) revert OutcomeNotSet();
if (bountyClaimed == bountyEntitlement) revert BountyAlreadyClaimed();
if (!goodFaith) revert InvalidGoodFaithParams();
if (msg.sender != attacker) revert NotAttacker();
if (block.timestamp > corruptedClaimDeadline) revert ClaimWindowExpired();
uint256 remaining = bountyEntitlement - bountyClaimed;
uint256 freeBalance = stakeToken.balanceOf(address(this));
uint256 payout = remaining <= freeBalance ? remaining : freeBalance;
uint256 newBountyClaimed = bountyClaimed + payout;
bountyClaimed = newBountyClaimed;
if (payout > 0) {
corruptedReserve -= payout;
if (!claimsStarted) claimsStarted = true;
stakeToken.safeTransfer(attacker, payout);
}
emit AttackerBountyClaimed(attacker, payout, newBountyClaimed, bountyEntitlement);
}

Risk

Likelihood:

  • Low. Requires a specific balance/timing condition (attacker calling again after already fully or partially claiming and the pool balance being at or near zero).

Impact:

  • Low / informational. No funds move, no accounting error

Proof of Concept

Simple PoC showing the minor issue.

function test_claimAttackerBounty_zeroPayoutNoOp() public {
// ... pool resolved as good-faith CORRUPTED, attacker fully claims once so
// bountyClaimed == bountyEntitlement is NOT yet true only if entitlement > claimed,
// e.g. pool balance was drained by contributeBonus->claimCorrupted race, leaving
// freeBalance == 0 while bountyClaimed < bountyEntitlement.
vm.prank(attacker);
vm.expectEmit(true, false, false, true);
emit IConfidencePool.AttackerBountyClaimed(attacker, 0, pool.bountyClaimed(), pool.bountyEntitlement());
pool.claimAttackerBounty(); // succeeds, transfers nothing, emits a 0-amount event
}

Recommended Mitigation

Add an explicit revert when there is nothing to pay out

uint256 payout = remaining <= freeBalance ? remaining : freeBalance;
if (payout == 0) revert NothingToSweep();

Support

FAQs

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

Give us feedback!