> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mode Escalation

> State machine transitions: NORMAL → DEGRADED on oracle staleness, DEGRADED → PAUSED after 1-hour timeout, PAUSER_ROLE for emergency halt — each mode progressively restricts available operations.

The Open Nile Protocol operates in one of four modes at any given time, with increasing levels of restriction. The mode system allows the protocol to gracefully degrade functionality in response to oracle issues, market stress, or administrative actions, rather than failing abruptly.

<Note>
  Mode transitions are managed by the `ModeController` contract. In the M2 (External Testnet) deployment, mode changes are controlled by admin roles. M3 plans include multi-sig governance for mode transitions and automated escalation based on additional risk metrics.
</Note>

## Operating Modes

The four modes form an escalation path from normal operations to full pause:

<Columns cols={2}>
  <Card title="NORMAL (0)" icon="circle-check">
    All operations available. This is the default operating state. Traders can open, close, increase, and reduce positions. LPs can deposit and withdraw. Settlements and liquidations proceed normally.
  </Card>

  <Card title="DEGRADED (1)" icon="circle-exclamation">
    Restricted operations. New position opens and increases are blocked. Closes, reductions, margin adjustments, settlements, and liquidations continue. Entered when oracle degradation is detected.
  </Card>

  <Card title="REDUCE_ONLY (2)" icon="circle-minus">
    Wind-down mode. Same restrictions as DEGRADED -- no new positions or increases. Designed for administrative wind-down scenarios where the protocol needs to reduce exposure without full pause.
  </Card>

  <Card title="PAUSED (3)" icon="circle-pause">
    All operations suspended. No trading, no settlements, no liquidations, no deposits, no withdrawals. This is the emergency mode for severe oracle failures or critical incidents.
  </Card>
</Columns>

## Operation Restriction Matrix

Each mode progressively restricts available operations:

| Operation              | NORMAL | DEGRADED | REDUCE\_ONLY | PAUSED |
| ---------------------- | :----: | :------: | :----------: | :----: |
| `openPosition`         |   Yes  |    No    |      No      |   No   |
| `increasePosition`     |   Yes  |    No    |      No      |   No   |
| `closePosition`        |   Yes  |    Yes   |      Yes     |   No   |
| `reducePosition`       |   Yes  |    Yes   |      Yes     |   No   |
| `addPositionMargin`    |   Yes  |    Yes   |      Yes     |   No   |
| `removePositionMargin` |   Yes  |    Yes   |      Yes     |   No   |
| `settleMatured`        |   Yes  |    Yes   |      Yes     |   No   |
| `liquidate`            |   Yes  |    Yes   |      Yes     |   No   |
| Vault deposit          |   Yes  |    Yes   |      Yes     |   No   |
| Vault withdraw         |   Yes  |    Yes   |      Yes     |   No   |

<Warning>
  In PAUSED mode, **all** operations are suspended including liquidations and settlements. This means positions that should be liquidated will not be processed until the protocol exits PAUSED mode. The pause is intended to be temporary while the root cause is investigated.
</Warning>

## State Transition Diagram

```
                setReduceOnly(true)
    NORMAL <────────────────────────> REDUCE_ONLY
      |  ^                             |  ^
      |  | exitDegradedMode()          |  |
      |  |                             |  |
      v  |   setDegraded()             v  | setDegraded()
      DEGRADED ────────────────────────/
      |
      | enterPausedMode()
      | or DEGRADED_TIMEOUT
      v
                    PAUSED  <── enterPausedMode() from NORMAL or REDUCE_ONLY
                      |
                      | exitPausedMode(target)
                      v
              NORMAL or REDUCE_ONLY
```

## Transition Functions

<Tabs>
  <Tab title="Pause Controls">
    | Transition                       | Function                             | Required Role |
    | -------------------------------- | ------------------------------------ | ------------- |
    | Any (except PAUSED) to PAUSED    | `enterPausedMode(reasonCode)`        | `PAUSER_ROLE` |
    | PAUSED to NORMAL or REDUCE\_ONLY | `exitPausedMode(target, reasonCode)` | `PAUSER_ROLE` |

    The `PAUSER_ROLE` can enter pause from **any** non-paused mode. This is the emergency brake. When exiting pause, the operator specifies the target mode (either NORMAL or REDUCE\_ONLY). The protocol cannot exit pause directly into DEGRADED mode.
  </Tab>

  <Tab title="Admin Controls">
    | Transition                         | Function                       | Required Role        |
    | ---------------------------------- | ------------------------------ | -------------------- |
    | NORMAL to REDUCE\_ONLY             | `setReduceOnly(true)`          | `DEFAULT_ADMIN_ROLE` |
    | REDUCE\_ONLY to NORMAL             | `setReduceOnly(false)`         | `DEFAULT_ADMIN_ROLE` |
    | NORMAL or REDUCE\_ONLY to DEGRADED | `setDegraded(reasonCode)`      | `DEFAULT_ADMIN_ROLE` |
    | DEGRADED to NORMAL                 | `exitDegradedMode(reasonCode)` | `DEFAULT_ADMIN_ROLE` |

    Admin transitions are used for planned operational changes. `setReduceOnly` toggles between NORMAL and REDUCE\_ONLY. `setDegraded` enters DEGRADED mode from either NORMAL or REDUCE\_ONLY.
  </Tab>

  <Tab title="Automated Controls">
    | Transition        | Function                | Required Role |
    | ----------------- | ----------------------- | ------------- |
    | Context-dependent | `checkOracleAndPause()` | `PAUSER_ROLE` |

    The automated check function can be called by any holder of `PAUSER_ROLE` (including the keeper service). It evaluates oracle health and makes context-dependent mode transitions. See the Auto-Escalation section below.
  </Tab>
</Tabs>

## Auto-Escalation

The `checkOracleAndPause()` function implements automated mode transitions based on oracle health. It is designed to be called periodically by the keeper service or by any holder of `PAUSER_ROLE`.

<Steps>
  <Step title="Check Oracle Validity">
    The function calls `oracleModule.isOracleValid()` via `staticcall` to determine if the oracle is providing fresh, valid forward prices.
  </Step>

  <Step title="Oracle Invalid Path">
    If the oracle is **invalid** (stale forward prices, disabled pair, or no forward ever published):

    * From NORMAL or DEGRADED: escalate to **PAUSED** with reason `ORACLE_INVALID`
    * From REDUCE\_ONLY: escalate to **PAUSED** with reason `ORACLE_INVALID`
    * Already PAUSED: no change
  </Step>

  <Step title="Oracle Valid + DEGRADED Path">
    If the oracle is **valid** and the current mode is DEGRADED, two sub-checks occur:

    1. **Duration check**: If `degradedEnteredAt + degradedDurationSeconds <= block.timestamp`, the degraded state has lasted too long. Escalate to **PAUSED** with reason `DEGRADED_TIMEOUT`.
    2. **Recovery**: If the duration has not been exceeded, recover to **NORMAL** with reason `ORACLE_RECOVERED`.
  </Step>

  <Step title="Return Value">
    The function returns `true` if the oracle is valid, `false` otherwise. This allows callers to take additional action based on the oracle state.
  </Step>
</Steps>

## Reason Codes

Every mode transition includes a reason code for auditability and monitoring. Reason codes are `bytes32` values computed as keccak256 hashes:

| Reason Code               | Value                           | Trigger                                                                                                                        |
| ------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `REASON_ADMIN`            | `keccak256("ADMIN")`            | Manual admin action (pause, unpause, mode change)                                                                              |
| `REASON_ORACLE_INVALID`   | `keccak256("ORACLE_INVALID")`   | Oracle staleness detected by `checkOracleAndPause()`                                                                           |
| `REASON_ORACLE_DEGRADED`  | `keccak256("ORACLE_DEGRADED")`  | Operator manually placed protocol in DEGRADED via `setDegraded(REASON_ORACLE_DEGRADED)` after observing reduced oracle quality |
| `REASON_ORACLE_RECOVERED` | `keccak256("ORACLE_RECOVERED")` | Oracle recovered from degraded state                                                                                           |
| `REASON_DEGRADED_TIMEOUT` | `keccak256("DEGRADED_TIMEOUT")` | Degraded mode duration exceeded without recovery                                                                               |

<Tip>
  Mode transitions emit a `ModeTransition` event with the old mode, new mode, and reason code. These events are indexed by the subgraph and available in the `ModeTransition` entity, allowing historical analysis of all mode changes.
</Tip>

## Degraded Mode Configuration

DEGRADED mode is operator-driven via `ModeController.setDegraded(reasonCode)` and exposes a single onchain timer that caps the time spent in DEGRADED before `checkDegradedTimeout()` auto-escalates to PAUSED:

```
DegradedConfig {
    degradedDurationSeconds: 3600  // 1 hour max before auto-escalation
}
```

| Parameter                 | Default        | Description                                                    |
| ------------------------- | -------------- | -------------------------------------------------------------- |
| `degradedDurationSeconds` | 3,600 (1 hour) | Maximum time in degraded mode before auto-escalation to PAUSED |

<AccordionGroup>
  <Accordion title="Why a 1-hour degraded timeout?">
    DEGRADED mode is a temporary state designed for transient oracle issues. If the oracle cannot recover within one hour, the issue is likely more serious than a temporary glitch. The 1-hour timeout ensures that a "stuck" degraded state does not persist indefinitely with reduced risk protection. The auto-escalation to PAUSED forces human intervention to investigate and resolve the root cause.
  </Accordion>

  <Accordion title="What happens to open positions during DEGRADED mode?">
    Open positions remain active. Traders can close positions, reduce exposure, and adjust margin. Settlements and liquidations continue normally. The key restriction is that no new positions can be opened and no existing positions can be increased. This prevents traders from taking on new exposure with potentially degraded oracle quality.
  </Accordion>

  <Accordion title="How does DEGRADED differ from REDUCE_ONLY?">
    Both modes block new position opens and increases. The primary difference is the **trigger and intent**:

    * **DEGRADED** is triggered by oracle quality issues and has an automatic timeout that escalates to PAUSED. It includes a `degradedEnteredAt` timestamp for duration tracking.
    * **REDUCE\_ONLY** is an administrative decision (e.g., planned wind-down, risk parameter change) with no automatic timeout. It persists until explicitly toggled back to NORMAL.

    DEGRADED mode is automatic and temporary; REDUCE\_ONLY is manual and persistent.
  </Accordion>

  <Accordion title="Can the protocol go directly from NORMAL to PAUSED?">
    Yes. The `enterPausedMode()` function can be called from any non-paused mode, including NORMAL. This is the emergency brake for critical situations that require immediate halting of all operations. Additionally, `checkOracleAndPause()` can escalate directly from NORMAL to PAUSED if the oracle becomes invalid.
  </Accordion>
</AccordionGroup>

## Escalation Scenarios

<Tabs>
  <Tab title="Publisher Goes Down">
    1. Publisher stops sending forward price updates
    2. Forward price becomes stale after 60 seconds (`maxForwardAge`)
    3. `isOracleValid()` returns `false`
    4. `checkOracleAndPause()` escalates to **PAUSED** with `ORACLE_INVALID`
    5. Admin investigates, restarts publisher
    6. Publisher resumes updates, prices become fresh
    7. Admin calls `exitPausedMode(NORMAL, REASON_ADMIN)` to resume operations
  </Tab>

  <Tab title="Operator Detects Reduced Oracle Quality">
    1. Operator observes that an oracle is publishing at reduced quality (deviation, latency, or off-chain signal)
    2. Operator with `DEFAULT_ADMIN_ROLE` calls `setDegraded(REASON_ORACLE_DEGRADED)` to enter DEGRADED mode
    3. New position opens are blocked; closes and liquidations continue
    4. If the oracle recovers, the operator calls `exitDegradedMode(REASON_ORACLE_RECOVERED)` to return to **NORMAL**
    5. If the issue is not resolved within `degradedDurationSeconds`: anyone can call `checkDegradedTimeout()` to escalate to **PAUSED** with `DEGRADED_TIMEOUT`
  </Tab>

  <Tab title="Admin Emergency">
    1. Admin detects an issue requiring immediate halt (e.g., suspected exploit)
    2. Admin calls `enterPausedMode(REASON_ADMIN)` from any current mode
    3. All operations halt immediately
    4. Team investigates and resolves the issue
    5. Admin calls `exitPausedMode(NORMAL, REASON_ADMIN)` or `exitPausedMode(REDUCE_ONLY, REASON_ADMIN)` depending on desired recovery state
  </Tab>
</Tabs>

## M2 vs M3 Governance

<Info>
  In the current M2 deployment, mode transitions are controlled by EOA (Externally Owned Account) addresses assigned the `PAUSER_ROLE` and `DEFAULT_ADMIN_ROLE`. This provides fast response times but relies on trust in the protocol operators.

  For M3 (Mainnet Ready), the plan includes:

  * **Multi-sig governance** for admin mode transitions (timelock + multi-sig)
  * **Automated oracle monitoring** with onchain health metrics
  * **Decentralized keeper network** for `checkOracleAndPause()` calls
  * **Graduated pause powers** with different timelocks for different severity levels

  These changes will decentralize mode control while maintaining the ability to respond quickly to emergencies.
</Info>
