# Asymmetric pause gating on exit paths

Pause or suspension checks applied to one direction only, blocking refunds, withdrawals or unwinding of in-flight state

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2024-09-06. Updated 2026-10-25._

## How it works
A pause or suspension flag is meant to stop new risk from entering the system. The bug is that the same gate is also applied to the path that lets value leave or unwind. The assumption that breaks is "pausing stops activity safely": in reality pausing one side of a paired entry/exit path leaves in-flight state with no legal way out. Funds or collateral become stuck because the only code that could return them is behind the same flag that is blocking everything else.

## Where it shows up
- A modifier such as `whenNotPaused` on a refund, revert, cancel or withdraw function but not on its paired receive/deposit function, or the reverse [E1](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md).
- A shared validation helper (e.g. `convert_to_yang`, `assert_can_enter`) called from both deposit and withdraw paths [E2](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-07.md)[E3](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md).
- Suspension or pause checks that do not distinguish direction of flow.
- Cap or limit assertions that always add the amount, even when the caller is removing value [E3](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md).
- Cross-chain flows where the unwind happens on a different contract or chain than the original action [E1](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md).

## How to review
List every state-changing entry point and mark it as inflow, outflow, or unwind (refund/revert/cancel). For each pause or suspension flag, check which of these it gates. The invariant: any path needed to return value already in the system must remain callable while paused, or the pause must be scoped so it cannot strand in-flight state. For shared helpers, confirm the direction of the check matches the caller: a withdrawal must subtract, not add, against caps [E3](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md), and a "no deposits" restriction must not block exits [E2](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-07.md). Trace cross-chain unwind paths end to end, including the contract on the far side [E1](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md).

## Variants seen
- Forward path ungated, refund path gated: failed inbound message can never be refunded during pause [E1](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md).
- Suspension check reused on withdrawal, freezing collateral the user should be able to replace [E2](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-07.md).
- Deposit cap check applied to withdrawals, so a full cap blocks all exits and an attacker can hold it there [E3](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md).

## Impact and severity
User funds stranded in the protocol [E1](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md), collateral frozen [E2](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-07.md), and withdrawals blockable by any party who can fill the cap [E3](https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md). Severity rises when the stuck state is user-owned value and the trigger is an ordinary emergency action (a pause) or a cheap state change (reaching a cap). Cross-chain unwind paths are worse because the failure surfaces on a different contract than the one being paused.

## Why fixes fail
Gating only the obvious entry function leaves the paired exit ungated or vice versa; the fix must cover the whole inflow/outflow/unwind set, not one function.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-13.md
- E2: https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-07.md
- E3: https://bugbountylibrary.com/r/c4%3A2024-01-opus%3AM-08.md
