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

Agreement owner can indefinitely DoS staker deposits by oscillating registry state between UNDER_ATTACK and PROMOTION_REQUESTED

Author Revealed upon completion

Root + Impact

Description

  • stake() and contributeBonus() gate on live registry state via _observePoolState()_assertDepositsAllowed(), which is meant to keep staking open through NOT_DEPLOYED / NEW_DEPLOYMENT / ATTACK_REQUESTED / UNDER_ATTACK, and only close it once the registry enters the PROMOTION_REQUESTED or a terminal state PRODUCTION/CORRUPTED.

  • _assertDepositsAllowed is evaluated against live registry state on every call, with no memory of past observations and no one-way latch (unlike scopeLocked, riskWindowStart, and riskWindowEnd).

  • The agreement owner has the right to request promotion to close staking, then cancel that request to revert the registry back to UNDER_ATTACK and reopen it — since _assertDepositsAllowed re-evaluates live state each call with nothing latched from the prior observation. Repeating this request → cancel cycle lets the owner toggle staking open and closed at will, with no cooldown, cost, or bound before expiry,the owner can cause DoS for stakers by toggling it again and again.

function stake(uint256 amount) external nonReentrant whenPoolNotPaused {
if (amount == 0) revert InvalidAmount();
if (amount < minStake) revert BelowMinStake();
if (outcome != PoolStates.Outcome.UNRESOLVED) revert OutcomeAlreadySet();
if (block.timestamp >= expiry) revert StakingClosed();
@> _assertDepositsAllowed(_observePoolState());
...
}
/// @dev `UNDER_ATTACK` is intentionally NOT blocked while `PROMOTION_REQUESTED` is. ...
function _assertDepositsAllowed(IAttackRegistry.ContractState state) private pure {
if (
@> state == IAttackRegistry.ContractState.PROMOTION_REQUESTED
|| state == IAttackRegistry.ContractState.PRODUCTION || state == IAttackRegistry.ContractState.CORRUPTED
) {
revert StakingClosed();
}
}

Risk

Likelihood:

  • The agreement owner toggles the agreement state, by requesting promotion just before a staker stakes and again rejecting promotion.

Impact:

  • Stakers will face a DoS when they stake, or bonus contributors

Recommended Mitigation

Latch the deposit-closing decision the same way scopeLocked/riskWindowStart/riskWindowEnd are latched, so a later observation of PROMOTION_REQUESTED (or a terminal state) permanently closes staking instead of being re-opened by a subsequent rewind to UNDER_ATTACK:

Support

FAQs

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

Give us feedback!