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

Pool owner can front-run the first stake to indefinitely extend expiry, delaying withdrawals if the moderator becomes inactive

Author Revealed upon completion

Summary

The pool owner is permitted to modify expiry until the first successful stake, at which point expiryLocked becomes true.

This creates a front-running opportunity where a malicious owner can observe the first staking transaction in the mempool, update the pool expiry to a much later timestamp before the stake is mined, and force the staker into a significantly longer lock period than expected.

If the moderator subsequently becomes inactive, users cannot rely on the protocol's automatic resolution until the extended expiry is reached, effectively delaying withdrawals far beyond what the first staker anticipated.


Finding Details

The owner may freely update the expiry while expiryLocked == false:

function setExpiry(uint256 newExpiry) external onlyOwner {
if (expiryLocked) revert ExpiryLocked();
...
expiry = uint32(newExpiry);
emit ExpiryUpdated(oldExpiry, newExpiry);
}

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

The expiry only becomes immutable after the first successful stake:

if (!expiryLocked) {
expiryLocked = true;
}

Meanwhile, all automatic resolution logic depends on the pool having reached its expiry:

if (block.timestamp < expiry) revert PoolNotExpired();

In particular, the fallback path intended to protect users when the moderator is unavailable can only execute after:

  • expiry, and

  • for corrupted agreements, expiry + MODERATOR_CORRUPTED_GRACE.

As a result, extending expiry also delays every subsequent recovery path.

Example Scenario

  1. A pool is created with an expiry that users consider acceptable.

  2. A user submits the first stake() transaction after reviewing the pool parameters.

  3. Before the transaction is mined, the owner observes it in the mempool.

  4. The owner front-runs the transaction with setExpiry() and increases the expiry to a much later timestamp.

  5. The user's stake is then mined, causing expiryLocked to become true with the newly extended expiry.

  6. If the moderator later becomes inactive, users cannot trigger claimExpired() until the extended expiry (and any additional moderator grace period) has elapsed.

The first staker therefore becomes locked into a pool with materially different terms than those they intended to join.


Impact

This issue does not place protocol funds at immediate risk or allow theft.

However, it enables a malicious pool owner to unilaterally extend the staking period immediately before the first stake is finalized, without the first staker having an opportunity to react.

Consequently:

  • users may be locked into the pool significantly longer than expected,

  • the protocol's automatic recovery mechanism for inactive moderators is delayed by the same extension,

  • a dishonest owner can misrepresent the effective staking duration until the first stake is mined.

This weakens user trust in the immutability of the advertised pool parameters during the initial participation phase.


Recommendation

Consider making the expiry immutable before the pool is publicly available for staking, or otherwise preventing owner modifications once users are reasonably expected to rely on the published expiry.

Alternatively, if post-deployment expiry updates are required, consider introducing safeguards such as:

  • only allowing expiry reductions before the first stake,

  • enforcing a bounded maximum extension,

  • requiring a timelock for expiry changes, or

  • invalidating pending first-stake transactions when the expiry is modified.

These approaches reduce the ability for the owner to front-run the first participant and unexpectedly extend the staking period.

Support

FAQs

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

Give us feedback!