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

Missing event emission when expiryLocked is initialized in stake() reduces off-chain observability

Author Revealed upon completion

Summary

The first successful call to stake() permanently transitions the pool by setting expiryLocked = true. However, this state transition occurs silently without emitting an event.

if (!expiryLocked) {
expiryLocked = true;
}

As a result, off-chain indexers, monitoring systems, analytics dashboards, and automation services cannot efficiently detect when the expiry becomes locked without continuously polling contract storage.


Finding Details

The stake() function contains the following logic:

if (!expiryLocked) {
expiryLocked = true;
}

https://github.com/CodeHawks-Contests/2026-07-bc-confidence-pools/blob/main/src/ConfidencePool.sol#L229-L231

expiryLocked appears to represent an important lifecycle transition for the pool:

  • Before the first stake, expiry is still mutable.

  • After the first stake, expiry becomes permanently locked.

  • The transition only occurs once during the pool's lifetime.

Despite representing a one-time and irreversible state change, no event is emitted.

Ethereum events are the standard mechanism for exposing important state transitions to off-chain consumers. Without an event, external systems must either:

  • continuously poll the contract,

  • compare storage snapshots between blocks,

  • or detect the state transition indirectly through transaction traces.

These approaches are less efficient and more error-prone than subscribing to a dedicated event.


Impact

Although this issue does not affect protocol correctness or fund safety, it negatively impacts operational visibility.

Affected off-chain integrations include:

  • Indexers (The Graph, custom indexers)

  • Monitoring and alerting systems

  • Automation bots


Because the transition occurs only once, an event would provide a reliable and canonical signal that the pool's expiry has become immutable.


Recommendation

Emit an event when expiryLocked changes from false to true.

Example:

event ExpiryLocked(address indexed pool, uint256 timestamp);
function stake(uint256 amount) external {
...
if (!expiryLocked) {
expiryLocked = true;
emit ExpiryLocked(address(this), block.timestamp);
}
...
}

This provides an inexpensive, standardized notification for off-chain consumers while preserving existing protocol behavior.

Support

FAQs

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

Give us feedback!