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

Resolution events omit settlement snapshots needed to reconstruct payouts from logs

Author Revealed upon completion

Description

  • Outcome resolution is the protocol's settlement point. When an outcome is flagged or mechanically resolved, the pool snapshots stake, bonus, k=2 accumulator state, corrupted reserve, bounty entitlement, and the timestamp used for payout math.

  • OutcomeFlagged only emits the moderator, outcome, good-faith flag, and attacker address. It omits the settlement snapshot fields and the caller that triggered auto-resolution through claimExpired(). Log consumers cannot reconstruct the finalized payout state from events alone and must rely on historical state reads at the exact block.

// src/interfaces/IConfidencePool.sol
event OutcomeFlagged(address indexed moderator, PoolStates.Outcome outcome, bool goodFaith, address attacker);
// src/ConfidencePool.sol
function flagOutcome(PoolStates.Outcome newOutcome, bool goodFaith_, address attacker_) external onlyModerator {
...
// @> These values determine final payouts and corrupted reserve accounting.
snapshotTotalStaked = totalEligibleStake;
snapshotTotalBonus = totalBonus;
snapshotSumStakeTime = sumStakeTime;
snapshotSumStakeTimeSq = sumStakeTimeSq;
corruptedReserve = newOutcome == PoolStates.Outcome.CORRUPTED ? snapshotTotalStaked + snapshotTotalBonus : 0;
bountyEntitlement = willBeGoodFaithCorrupted ? snapshotTotalStaked + snapshotTotalBonus : 0;
...
outcomeFlaggedAt = riskWindowEnd;
// @> The event does not include any snapshot or payout-bound data.
emit OutcomeFlagged(msg.sender, newOutcome, goodFaith_, attacker_);
}
function claimExpired() external nonReentrant {
...
snapshotTotalStaked = totalEligibleStake;
snapshotTotalBonus = totalBonus;
snapshotSumStakeTime = sumStakeTime;
snapshotSumStakeTimeSq = sumStakeTimeSq;
...
// @> address(0) hides the auto-resolution caller and the event still omits snapshots.
emit OutcomeFlagged(address(0), PoolStates.Outcome.EXPIRED, false, address(0));
}

Risk

Likelihood:

  • Every pool resolution emits OutcomeFlagged without the snapshot fields used by settlement, but the practical issue materializes for consumers that reconstruct settlement state from logs instead of historical state reads.

  • Indexers, monitoring systems, and dispute tooling commonly consume events rather than historical state diffs.

Impact:

  • Off-chain consumers cannot reliably reconstruct finalized principal, bonus, reserve, bounty, and payout-bound values from logs.

  • Incident monitoring can miss or misreport the economic size of a resolution, especially for auto-resolutions where the event uses address(0) as the moderator and omits the actual caller.

Under the CodeHawks matrix, medium likelihood and low impact classify this issue as Low severity.

Recommended Mitigation

Emit the snapshot data at resolution. A separate event avoids changing the existing event ABI.

+ event OutcomeSnapshot(
+ uint256 snapshotTotalStaked,
+ uint256 snapshotTotalBonus,
+ uint256 snapshotSumStakeTime,
+ uint256 snapshotSumStakeTimeSq,
+ uint256 outcomeFlaggedAt,
+ uint256 corruptedReserve,
+ uint256 bountyEntitlement,
+ uint256 corruptedClaimDeadline,
+ address autoResolutionCaller
+ );
emit OutcomeFlagged(msg.sender, newOutcome, goodFaith_, attacker_);
+ emit OutcomeSnapshot(
+ snapshotTotalStaked,
+ snapshotTotalBonus,
+ snapshotSumStakeTime,
+ snapshotSumStakeTimeSq,
+ outcomeFlaggedAt,
+ corruptedReserve,
+ bountyEntitlement,
+ corruptedClaimDeadline,
+ address(0)
+ );

Support

FAQs

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

Give us feedback!