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

Missing TotalBonusUpdated event on sweepUnclaimedBonus

Author Revealed upon completion

Affected: ConfidencePool.sweepUnclaimedBonus at src/ConfidencePool.sol:474

TL;DR

sweepUnclaimedBonus decrements totalBonus but only emits BonusSwept with the swept amount, not the new total. Off-chain indexers tracking totalBonus must recompute it from prior events.

Description

sweepUnclaimedBonus at lines 499-501 decrements totalBonus when the bonus is unreserved:

// ConfidencePool.sol:499-501
if (totalEligibleStake == 0 || riskWindowStart == 0) {
totalBonus -= amount <= totalBonus ? amount : totalBonus;
}

The function emits BonusSwept(msg.sender, recoveryAddress, amount) at line 508. This event includes the swept amount but NOT the new totalBonus value. totalBonus is also increased by contributeBonus, which emits BonusContributed with the contributed amount, not the new total. An indexer tracking totalBonus must maintain running state across all BonusContributed and BonusSwept events, which is fragile across reorgs and missed events.

Compare: totalEligibleStake changes are accompanied by events that allow reconstruction (Staked, Withdrawn, ClaimSurvived, ClaimExpired each include the user's stake amount). totalBonus lacks equivalent coverage.

Scope Eligibility

ConfidencePool.sol is in contest scope. sweepUnclaimedBonus is in the deployed bytecode. No out-of-scope dependency required.

Severity Calibration

CodeHawks Low: off-chain observability gap. No fund loss. On-chain state is always correct and queryable.

Known-Issue Distinction

No prior audit reports flag missing events on totalBonus decrement in sweepUnclaimedBonus. The BonusSwept event exists but does not include the new total.

Risk

Every bonus sweep when riskWindowStart == 0 or totalEligibleStake == 0 triggers this. No external preconditions beyond normal pool operation.

Off-chain monitoring tools may display incorrect totalBonus values after sweeps. No direct fund loss. Degrades auditability of bonus pool accounting.

Proof of Concept

forge test --match-path test/unit/ConfidencePool.t.sol -vvv 2>&1 | tail -3
$ grep -n "totalBonus" src/ConfidencePool.sol
282: totalBonus += received; # BonusContributed event (no new total)
500: totalBonus -= amount <= totalBonus ? amount : totalBonus; # BonusSwept event (no new total)

Recommended Mitigation

Emit TotalBonusUpdated(newTotalBonus) before the sweep transfer:

+emit TotalBonusUpdated(totalBonus);
stakeToken.safeTransfer(recoveryAddress, amount);
emit BonusSwept(msg.sender, recoveryAddress, amount);

Alternatively, extend the BonusSwept event to include newTotalBonus as a parameter.

Support

FAQs

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

Give us feedback!