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

Paused Time Is Not Accounted for in the Pool's Staking Period

Author Revealed upon completion

Finding description and Impact

When a pool is paused, staking is disabled; however, block.timestampcontinues to advance while the pool remains paused. As a result, the paused duration is still counted toward the pool's staking window. Once the pool is unpaused, the expiry remains unchanged, reducing the effective time available for staking.

To preserve the intended staking duration, the pool's expiry should be extended by the amount of time the pool remained paused.

/// @inheritdoc IConfidencePool
function stake(uint256 amount) external nonReentrant whenPoolNotPaused { //@audit
if (amount == 0) revert InvalidAmount();
if (amount < minStake) revert BelowMinStake();
if (outcome != PoolStates.Outcome.UNRESOLVED) revert OutcomeAlreadySet();
if (block.timestamp >= expiry) revert StakingClosed(); //@audit

Risk

Likelihood:

  • Reason 1: Occurs whenever the pool is paused for any period of time, as block.timestamp continues advancing while staking remains disabled.

  • Reason 2: The longer the pool remains paused, the greater the reduction in the effective staking window, making the issue increasingly likely to impact participants(breaks intended timeframes).

Impact

  • Impact 1: The pool incorrectly accounts for paused time by continuing to include it in the staking period, causing the staking phase to end earlier than intended.

  • Impact 2: The effective staking period becomes shorter than the configured pool duration.

Proof of Concept(PoC)

  • A pool is created with an expiry of 60 days.

On day 55, the pool is paused, preventing any further staking.

  • The pool is unpaused on day 61. Since the expiry timestamp was never adjusted, block.timestamp >= expiry, causing all subsequent calls to stake() to revert with StakingClosed().

  • Although the owner could increase the expiry using setExpiry(), the function enforces a minimum lead time of 30 days

  • Therefore, recovering only the 6 days lost during the pause is impossible. The owner would instead be forced to extend the expiry by at least 30 days, resulting in a staking period that is longer than originally intended.

Instead, the pool should automatically extend the expiry by exactly the duration it remained paused, preserving the originally configured staking window.

Recommended Mitigation

Track the total time spent paused and extend the expiry when the pool is unpaused.
One simple approach is to record the pause timestamp and shift expiry by the paused duration:
uint256 public pausedAt;

function pause() external onlyOwner whenPoolNotPaused {
pausedAt = block.timestamp;
_pause();
}

function unpause() external onlyOwner whenPoolPaused {
uint256 pauseDuration = block.timestamp - pausedAt;

// Only adjust while expiry remains mutable, or define a separate
// effective-expiry mechanism if post-stake pauses must also suspend time.
expiry = uint32(uint256(expiry) + pauseDuration);
pausedAt = 0;
_unpause();

}

Support

FAQs

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

Give us feedback!