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

Missing CorruptedReserveUpdated event on reserve changes

Author Revealed upon completion

TL;DR

corruptedReserve is updated during claims and sweeps but never emits a dedicated event. Off-chain indexers and subgraphs cannot track reserve consumption without polling the on-chain view function.

Description

corruptedReserve tracks the remaining pool balance reserved for CORRUPTED claims. It is written at three claim/sweep sites:

// ConfidencePool.sol:418 — claimCorrupted
corruptedReserve = toSweep <= corruptedReserve ? corruptedReserve - toSweep : 0;
// ConfidencePool.sol:447 — claimAttackerBounty
corruptedReserve -= payout;
// ConfidencePool.sol:465 — sweepUnclaimedCorrupted
corruptedReserve = 0;

None of these sites emit a dedicated event for the reserve change. ClaimCorrupted, AttackerBountyClaimed, and UnclaimedCorruptedSwept emit the claim/sweep amounts, but not the new corruptedReserve value. An off-chain indexer must reconstruct the reserve by capturing the initial value from OutcomeFlagged params and tracking every subsequent claim event. This is fragile: a missed event or chain reorg causes the indexer state to diverge from on-chain reality.

Scope Eligibility

ConfidencePool.sol is listed in the contest scope (589 nSLOC total). The claimCorrupted, claimAttackerBounty, and sweepUnclaimedCorrupted functions are in the deployed bytecode. This finding does not rely on any out-of-scope dependency.

Severity Calibration

CodeHawks Low: issues not exploitable under normal circumstances but representing poor practices or deviations from best practices. No fund loss. On-chain state is always correct and queryable via view functions. The gap is purely off-chain observability.

Known-Issue Distinction

No prior audit reports flag missing events on corruptedReserve. The existing events (ClaimCorrupted, AttackerBountyClaimed, UnclaimedCorruptedSwept) emit claim amounts but not the resulting reserve value. This finding identifies a specific missing event not covered by any prior report.

Risk

The condition triggers on every CORRUPTED claim or sweep. No external preconditions. The on-chain state is correct; only off-chain indexers are affected.

Off-chain monitoring tools (subgraphs, dashboards, liquidation bots) may display stale or incorrect reserve values. No direct fund loss. Degrades auditability and operational safety.

Proof of Concept

forge test --match-path test/unit/ConfidencePool.t.sol -vvv 2>&1 | tail -3
$ grep -n "corruptedReserve" src/ConfidencePool.sol
361: corruptedReserve = newOutcome == PoolStates. Outcome. CORRUPTED ? snapshotTotalStaked + snapshotTotalBonus : 0;
418: corruptedReserve = toSweep <= corruptedReserve ? corruptedReserve - toSweep : 0;
447: corruptedReserve -= payout;
465: corruptedReserve = 0;
545: corruptedReserve = snapshotTotalStaked + snapshotTotalBonus;

Only line 361 (inside flagOutcome) is covered by OutcomeFlagged event. Lines 418, 447, 465 have no corresponding event for corruptedReserve.

Recommended Mitigation

Emit a CorruptedReserveUpdated event on every reserve change:

// claimCorrupted
corruptedReserve = toSweep <= corruptedReserve ? corruptedReserve - toSweep : 0;
+emit CorruptedReserveUpdated(corruptedReserve);
// claimAttackerBounty
corruptedReserve -= payout;
+emit CorruptedReserveUpdated(corruptedReserve);
// sweepUnclaimedCorrupted
corruptedReserve = 0;
+emit CorruptedReserveUpdated(corruptedReserve);

Support

FAQs

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

Give us feedback!