<!--
Sitemap:
- [Welcome to River](/index)
- [Asset-Backed Finance Primer](/concepts/finance-primer)
- [River for Lenders](/lenders/introduction)
- [Depositing](/lenders/depositing)
- [Earning: the Coupon](/lenders/earning)
- [The Accumulating Wrapper](/lenders/wrapper)
- [Withdrawals & Exits](/lenders/withdrawals)
- [Losses & Impairments](/lenders/losses)
- [FAQ](/lenders/faq)
- [Integrate with River](/integrate/get-started)
- [Wrapper Integration](/integrate/wrapper)
- [Class Token Integration](/integrate/class-token)
- [Contract Addresses](/integrate/addresses)
- [Protocol Architecture](/architecture)
- [Actors: Roles & Permissions](/technical/actors)
- [Proxies & Upgradeability](/technical/proxies)
- [Strategies & Reporters](/technical/strategies)
- [Glossary](/concepts/glossary)
- [Dual NAV & Exchange Rates](/accounting/dual-nav)
- [Settlement & Conservation](/accounting/settlement)
- [The Coupon Ledger](/accounting/coupon-ledger)
- [Wrapper Accounting](/accounting/wrapper)
- [Accounting Examples](/accounting/examples)
- [The Tiered Waterfall](/waterfall/)
- [Waterfall Scenarios](/waterfall/scenarios)
- [Security Model](/security)
- [Protocol Invariants](/technical/invariants)
- [List of Assumptions](/technical/assumptions)
- [External Entry Points](/technical/entry-points)
- [Governance](/governance)
- [Contract Reference](/reference/contracts)
- [River Engineering Standards](/reference/engineering-standards)
-->

# Governance

River governance is a **timelock** with a **pluggable authorization policy**. Every privileged action on a
deal — implementation upgrades, reporter/signer rotation, parameter changes — is a delegatecall *scroll* that
a proposer plots, that matures after a delay, and that a revoker can cancel beforehand. The timelock is the
immutable root of trust; everything it governs (the `Structure` and its modules) is upgradeable *beneath* it.

The design separates **mechanism** from **policy**:

* **Mechanism** lives in `RiverPause` and never changes: the scroll lifecycle (delay, code binding, expiry)
  and delegatecall execution.
* **Policy** lives behind a swappable `IAuthority`: who may propose, who may revoke, the membership rules, and
  the count invariants. The rules can evolve by swapping the authority — but the *rules for changing the
  rules* stay fixed in the immutable pause.

:::info\[Prior art (auditor note)]
The plot / exec / drop mechanism is adapted from Sky/Maker's **DSPause**, whose delegatecall payloads are
called *spells*. River calls them **scrolls**. We adopted that model deliberately because we liked it; the
divergences — a pluggable `IAuthority`, code-hash binding, an expiry window, and the batch-reject
kill-switch — are documented below.
:::

## Contracts

| Contract | Owns | Notes |
| --- | --- | --- |
| `RiverPause` | Scroll lifecycle (plot / drop / reject / exec), the delay, the `authority` pointer | Immutable. The root of trust. |
| `RiverPauseProxy` | Holds system authority; executes scrolls by `delegatecall` | Immutable. Created by the pause; owner is the pause. |
| `IAuthority` | One method, `canCall(src, dst, sig)` | The seam the pause consults on every guarded call. |
| `RiverGovAuthority` | Proposer / revoker membership, count floors, the revoker-removal lane | The v1 policy. Swappable as a unit. |

The **proxy is the governance root**: its address is what holds the privileged roles
(`STRUCTURE_ADMIN_ROLE`, `UPGRADER_ROLE`, …) inside each deal's `RiverAccessControls`. A scroll runs *as the
proxy*, so its calls into the `Structure` and modules present `msg.sender == proxy == role holder`.

```mermaid
flowchart TB
    PROP["Proposers<br/>(multisigs)"]
    REVK["Revokers<br/>(multisigs)"]
    GOV["Governor<br/>(guardian / the proxy)"]
    AUTH["RiverGovAuthority<br/>(IAuthority policy)"]
    PAUSE[["RiverPause<br/>(timelock)"]]
    PROXY["RiverPauseProxy<br/>(authority holder)"]
    SCROLL["Scroll<br/>(delegatecall payload)"]
    SYS["Structure + modules<br/>(via RiverAccessControls)"]

    PROP -->|"plot / plotRevokerRemoval"| PAUSE
    REVK -->|"drop / rejectAllPending / veto"| PAUSE
    GOV -->|"add / removeProposer / setRemovalDelay"| AUTH
    PAUSE -.->|"canCall(src, this, sig)"| AUTH
    PAUSE -->|"exec → delegatecall"| PROXY
    PROXY -->|"runs"| SCROLL
    SCROLL -->|"privileged calls as proxy"| SYS
```

Solid arrows move value or state. Dashed arrows are read-only checks.

## The scroll lifecycle

A scroll is a contract exposing a function (e.g. `execute()`); `fax` is its ABI-encoded call. Plans are
stored **by hash** of `(usr, tag, fax, eta)`, so there is no enumeration and the number of outstanding plots
never affects gas — every operation is O(1).

```mermaid
flowchart LR
    A["plot(usr, tag, fax, eta)<br/>proposer"] --> B["pending<br/>plottedAt = now"]
    B -->|"now ≥ eta"| C["exec<br/>(anyone)"]
    B -->|"drop / rejectAllPending<br/>revoker"| D["cancelled"]
    B -->|"now > eta + maxAge"| E["expired"]
    C --> F["proxy delegatecalls the scroll"]
```

| Step | Who | Rule |
| --- | --- | --- |
| `plot(usr, tag, fax, eta)` | proposer | `eta ≥ now + delay`. `tag` must equal `usr.codehash`. |
| `drop(...)` | revoker | Surgically cancels one plan. |
| `rejectAllPending()` | revoker | Batch kill-switch — invalidates everything plotted at or before this block. |
| `exec(...)` | **anyone** | Only after `eta`, before `eta + maxAge`. Re-checks `usr.codehash == tag`, then `delegatecall`s. |

### Why each guard exists

* **`exec` is permissionless.** Once a plan survives the delay un-vetoed, nothing can withhold its execution.
  This is a deliberate *liveness guarantee* — there is no execution chokepoint. A keeper can still be the one
  who fires it; it simply isn't the *only* one who can.
* **Code-hash binding (`tag`).** The scroll's runtime code hash is recorded at plot and re-checked at exec,
  defeating a metamorphic swap (plot benign code, redeploy malicious code at the same address before exec). A
  scroll with no code is rejected outright.
* **Expiry window (`maxAge`).** A matured-but-unexecuted plan would otherwise linger forever; a stale scroll
  dug up months later could execute into a since-changed context. The executable window is
  `[eta, eta + maxAge]`. `maxAge = 0` disables expiry (forever-valid plots).
* **Hash-keyed storage.** No enumeration means a flood of plots can never make any operation unbounded.

### Delay bounds

`delay` is mutable (via a timelocked scroll) but capped by the immutable `maxDelay`. The asymmetry is
deliberate: a *too-low* delay self-heals — governance still runs, can re-raise it, and any lowering is itself a
delay-gated, vetoable scroll. A *too-high* delay would be **sticky** (the scroll to fix it would itself need
the huge delay), so only the cap is fixed.

## Swapping the policy

`RiverPause.setAuthority` is `onlyProxy` — reachable **only** through an executed scroll. So swapping the
entire governance policy is itself a normal, delay-gated, vetoable governance action:

1. A current proposer plots a scroll calling `pause.setAuthority(newAuthority)`.
2. It sits for `delay`. The **current** authority still gates everything, so the **current** revokers can
   `drop` / `rejectAllPending` a malicious swap during the window.
3. After `eta`, anyone execs; the pointer flips and the new policy governs.

There is deliberately **no fast path** for `setAuthority` — no guardian instant-swap. That is what keeps the
*rules for changing the rules* immutable: the policy is flexible, but a swap to a hostile authority is always
subject to the existing delay and the existing revokers' veto.

## The authorization policy (`RiverGovAuthority`)

`canCall` maps `plot` to the proposer set and `drop` / `rejectAllPending` to the revoker set; everything else
(including `exec`) is ungated. Membership is stored in `AccessControlEnumerable`, but the raw
`grantRole` / `revokeRole` / `renounceRole` API is **disabled** — every mutation funnels through an
invariant-checked wrapper, so nothing (not even the proxy) can bypass the floors or the removal lane.

### Count floors

| Role | Minimum | Why |
| --- | --- | --- |
| Proposers | **2** | No single proposer is a single point of failure for *initiating* governance (including a removal). |
| Revokers | **3** | Guarantees **≥ 2 non-target defenders** for any single removal, so veto defense survives one defender being offline or itself compromised. |

The constructor asserts the floors *after* granting, so duplicate or zero addresses in the seed arrays are
caught. Floors are re-checked on every removal.

### Two membership domains

* **`governor`** (a guardian-grade key, or the proxy itself) handles **adds**, **proposer removal**, and the
  removal **cooldown**. If `governor` is the proxy, these become timelocked scrolls; if it is a separate
  guardian, they are instant. Either way `governor` deliberately has **no power to remove a revoker**.
* **Revoker removal** is peer-policed and lives only in the lane below — never a governor action, never a
  scroll. This is the load-bearing rule (see below).

## Removing a compromised revoker

A revoker is the only role that can *cancel*, so it is the only role that could try to **censor its own
removal**. The lane is built so it cannot.

```mermaid
flowchart LR
    P["plotRevokerRemoval(target, replacement)<br/>proposer (not the target)"] --> W["pending<br/>eta = now + removalDelay"]
    W -->|"now ≥ eta"| X["execRevokerRemoval<br/>(anyone)"]
    W -->|"vetoRevokerRemoval<br/>any OTHER revoker"| V["cancelled"]
    X --> R["revoke target + grant replacement<br/>atomically; floor re-checked"]
```

Why a compromised revoker `rev1` cannot stop its own removal:

* **`drop` / `rejectAllPending` can't reach it.** Those operate on the *pause's* plan store; the pending
  removal lives in the *authority's* own storage. Different contract, different mapping — nothing for `drop`
  to delete.
* **It can't veto its own.** `vetoRevokerRemoval` excludes `msg.sender == target`. `rev1` may veto *other*
  revokers' removals, never its own.
* **It can't occupy its own slot.** A dual-role key (proposer + revoker) is barred from
  `plotRevokerRemoval(self, …)` (`CannotPlotOwnRemoval`), so it cannot grief by front-running the slot with a
  junk self-removal.
* **The floor holds atomically.** At the minimum (3 revokers), removal *requires* a `replacement`, and exec
  revokes the target and grants the replacement in one step — so a compromised revoker can be ejected even
  while it is freezing normal governance, without ever dropping below 3.

### The two compromise paths

| Compromise | Response | Outcome |
| --- | --- | --- |
| A proposer floods plots | Any revoker calls `rejectAllPending` | All junk plots invalidated; later-block plots survive. |
| A revoker censors (drops/freezes everything) | A proposer plots its removal; other revokers don't veto | Ejected after `removalDelay`; freeze lifts the instant `canCall` stops honoring it. |

During a revoker ejection, that revoker can keep freezing *other* governance for up to `removalDelay` (it
spams `rejectAllPending`) — but the removal lane is never frozen, so liveness returns the moment it is
removed. `removalDelay` is mutable within immutable `[minRemovalDelay, maxRemovalDelay]` bounds: longer gives
more time to veto a *malicious* removal of a good revoker; shorter ejects a real bad actor faster.

### Residual risk

A **colluding revoker majority** can veto each other's removals indefinitely. The worst they achieve is
**denial of service** (frozen governance), never theft — they still cannot execute scrolls. Recovery is a
fresh authority (which they could also block) and ultimately a redeploy. A 2-of-3-honest assumption on the
revoker set avoids it; this is why the floor is 3 and why proposer/revoker keys should be independent
high-threshold multisigs.

## Deliberate non-features

* **No exclusive executor role.** Gating `exec` would reintroduce a censorship chokepoint (and let an
  executor block its own removal). `exec` stays permissionless; run a keeper against it if you want scheduled
  execution.
* **No mandatory guardian.** Revoker removal is peer-policed, so the system needs no standing super-key to
  recover from a compromised revoker. A `governor` still manages adds/params, but it cannot remove revokers.
* **Disjointness is a deployment-discipline choice.** One address *can* hold both proposer and revoker roles;
  the hard rule blocks acting on your own removal (neither plot nor veto). Keeping the two checking roles on
  independent keys is recommended, since separation of duties is the point of having two roles.

## Deployment

`governor` is wired to the proxy from birth in a single broadcast, with no `setGovernor` follow-up, by
predicting the proxy address (it is created *last*, so nothing references an empty address at construction and
the pause's `authority.code.length > 0` guard stays intact):

```
deployer nonce n   : RiverGovAuthority   (governor = predicted proxy)
deployer nonce n+1 : RiverPause          (mints the proxy as its 1st CREATE)
proxy              = computeCreateAddress(computeCreateAddress(deployer, n+1), 1)
```

See `script/DeployGovernance.s.sol`. The deploy asserts the predicted proxy matches the real one before
returning, so a mispredicted `governor` can never ship silently.
