> For the complete documentation index, see [llms.txt](https://docs.kton.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kton.io/protocol-internals/10-governance-roles-upgrades.md).

# Governance, Roles and Upgrades

KTON's pool is not controlled by a single owner key, and it is not controlled by token-holder voting at the pool level. Instead, authority is split across eight named role slots, each holding a specific, limited power. Some powers carry on-chain timelocks (a mandatory waiting period before the action takes effect), and the most sensitive power of all, replacing the contract's code, is locked behind the longest timelock and a separate role from the one that governs day-to-day parameters. This chapter walks through every role, the separation of powers between them, the code-upgrade mechanism, the governor-migration handshake, and the halt/unhalt safety controls. All op-codes, fields, and constants below are cited from the deployed FunC sources.

## The eight role slots

The pool packs all of its authority into one storage tuple. The shape is defined in `roles_helper.func` and loaded by `~load_roles` in `pool_storage.func`:

```
roles = [sudoer, sudoer_set_at, governor, governor_update_after,
         interest_manager, halter, approver, treasury]
```

The accessors in `roles_helper.func` index this tuple by position (`0 INDEX` for sudoer, `1 INDEX` for `sudoer_set_at`, and so on through `7 INDEX` for treasury). Two of the eight slots are not addresses at all: `sudoer_set_at` (index 1) and `governor_update_after` (index 3) are 48-bit timestamps that drive the timelocks described later. The other six slots are contract addresses (slices). Any documentation that lists only six roles, and omits the two timestamp slots, is describing an older layout.

`~load_roles` parses the addresses across two cells: `sudoer`, `sudoer_set_at`, `governor`, `governor_update_after`, and `interest_manager` sit in the first cell, then a referenced cell holds `halter`, `approver`, and `treasury`. There is a backward-compatibility detail worth noting: if the inner cell has run out of bits when the parser reaches the treasury slot (`ds.slice_bits() ? ... : interest_manager`), the treasury defaults to the interest-manager address. `save_roles` writes the same two-cell structure back.

| Slot                    | Index | Type               | Held by                                                       |
| ----------------------- | ----- | ------------------ | ------------------------------------------------------------- |
| `sudoer`                | 0     | address            | Superuser key (code/data upgrades, arbitrary messages)        |
| `sudoer_set_at`         | 1     | timestamp (48-bit) | When the current sudoer was installed; starts the 2-day clock |
| `governor`              | 2     | address            | Central day-to-day authority                                  |
| `governor_update_after` | 3     | timestamp (48-bit) | Earliest time a prepared governor migration may execute       |
| `interest_manager`      | 4     | address            | Sets interest rate and loan operational params                |
| `halter`                | 5     | address            | Emergency stop (halt / partial-halt)                          |
| `approver`              | 6     | address            | Approves controllers to borrow (used on the controller side)  |
| `treasury`              | 7     | address            | Passive fee sink                                              |

## Each role's powers

### Governor

The governor is the central operating authority. In `pool.func` it is the only sender accepted for the following operations (each guarded by `assert_sender!(sender_address, get_governor(roles))`):

* `governor::set_sudoer` (`0x79e7c016`): installs a new sudoer address and stamps `sudoer_set_at` to `now()`, which restarts the sudoer timelock.
* `governor::unhalt` (`0x7247e7a5`): clears `halted?` back to false.
* `governor::prepare_governance_migration` (`0x9971881c`): arms a future governor change (the first step of the two-step migration).
* `governor::set_roles` (`0x5e517f36`): updates `governor` (only after migration matures), `interest_manager`, `halter`, `approver`, and `treasury`.
* `governor::set_deposit_settings` (`0x9bf5561c`): sets `optimistic_deposit_withdrawals`, `deposits_open?`, and `instant_withdrawal_fee`.
* `governor::set_governance_fee` (`0x2aaa96a0`): sets `governance_fee_share`.

Crucially, the governor cannot upgrade the contract's code and cannot set the interest rate. Those powers belong to the sudoer and the interest-manager respectively. This is the core of the separation of powers: the role that tunes economics day to day is structurally unable to rewrite the contract or move the validator interest rate.

### Sudoer

The sudoer is the superuser, but it is the most constrained in time. It can do two things, both handled in `sudoer_requests.func` and both gated by the 2-day quarantine:

* `sudo::send_message` (`0x270695fb`): `process_sudo_request` loads an 8-bit send mode and a message cell, then issues `send_raw_message(message, mode)`. This lets the sudoer emit an arbitrary message with an arbitrary send-mode on behalf of the pool.
* `sudo::upgrade` (`0x96e7f528`): `process_sudo_upgrade_request` loads up to three maybe-refs, `data`, `code`, and `after_upgrade`. If `data` is present it calls `set_data(data)`; if `code` is present it calls `set_code(code)`; if `after_upgrade` is present it `execute`s that continuation. It then `throw(1)` to unwind.

Both sudo handlers begin with `assert_sender!(sender, sudoer)` and `throw_unless(sudoer::quarantine, now() > sudoer_set_at + SUDOER_QUARANTINE)`. The only guardrail on the sudoer is the timelock; once the quarantine has elapsed, the sudoer can replace code and storage outright. The sudoer cannot install itself: only the governor can call `set_sudoer`, and doing so resets the clock.

### Interest-manager

The interest-manager governs the loan economics, not the user-facing pool flags. In `pool.func`, guarded by `assert_sender!(sender_address, get_interest_manager(roles))` and `assert_state!(state::NORMAL)`:

* `interest_manager::set_interest` (`0xc9f04485`): sets `interest_rate` (a 24-bit share value).
* `interest_manager::set_operational_params` (`0x4485c9f0`): sets `min_loan_per_validator`, `max_loan_per_validator` (with `max >= min` enforced via `error::contradicting_operational_params`), `disbalance_tolerance` (an 8-bit value, `DISBALANCE_BIT_SIZE`), and `credit_start_prior_elections_end`.

The interest-manager also receives per-round statistics. It cannot touch the governance fee, the sudoer, the deposit settings, or the halt state.

### Halter

The halter is the emergency-stop role. It can pause but never resume:

* `halter::halt` (`0x139a1b4e`): sets `halted? = true`. The handler intentionally does not `end_parse` the message, so any trailing data can carry an off-chain reason.
* `halter::partial_halt` (`0x77778888`): reads two booleans, `stop_optimistic` and `close_deposits`. Setting the first flips `optimistic_deposit_withdrawals` to false; setting the second flips `deposits_open?` to false.

The halter cannot call `unhalt`. Only the governor can clear `halted?`. This split means an emergency key can freeze the pool quickly but cannot quietly un-freeze it.

### Approver

The approver lives on the controller side of the protocol. It approves controllers to request loans and is part of the profit-share setup (`approver_set_profit_share`). It has no authority over pool state, fees, halting, or upgrades.

### Treasury

The treasury is a passive fee sink. It receives the governance fee and other protocol fees routed out of the pool. It holds no operational authority of any kind: it cannot send pool messages, change parameters, halt, or upgrade.

## Separation of powers, at a glance

| Action                                               | Required role     | Timelock                       |
| ---------------------------------------------------- | ----------------- | ------------------------------ |
| Replace code / data (`sudo::upgrade`)                | sudoer            | 2 days (`SUDOER_QUARANTINE`)   |
| Send arbitrary pool message (`sudo::send_message`)   | sudoer            | 2 days (`SUDOER_QUARANTINE`)   |
| Install a new sudoer (`set_sudoer`)                  | governor          | none (but resets sudoer clock) |
| Prepare governor migration                           | governor          | none to arm                    |
| Execute governor migration (via `set_roles`)         | governor          | 1 day (`GOVERNOR_QUARANTINE`)  |
| Set interest\_manager / halter / approver / treasury | governor          | none (instant)                 |
| Set interest rate / loan params                      | interest\_manager | none                           |
| Set deposit settings / governance fee                | governor          | none                           |
| Halt / partial-halt                                  | halter            | none                           |
| Unhalt                                               | governor          | none                           |

No single role can both rewrite the contract and operate it day to day; the upgrade key (sudoer) and the operating key (governor) are deliberately different roles, and the only role that can mint a new sudoer (governor) cannot itself perform the upgrade.

## The upgrade mechanism

Code upgrades are sudoer-only, executed through `sudo::upgrade` (`0x96e7f528`), and they are not possible until the sudoer has been in place for at least `SUDOER_QUARANTINE`. From `sudoer_requests.func`:

```
const int SUDOER_QUARANTINE = 2 * 24 * 3600 ;   ;; 172800 seconds, 2 days
```

The flow is:

1. The governor installs the intended sudoer with `governor::set_sudoer`, which stamps `sudoer_set_at = now()`.
2. Two days must pass. Until then, every `sudo::upgrade` (and every `sudo::send_message`) reverts with `sudoer::quarantine` (`0xa000`).
3. After the quarantine, the sudoer sends `sudo::upgrade` with optional `data`, `code`, and `after_upgrade` refs. The handler applies `set_data` and/or `set_code`, optionally runs the `after_upgrade` continuation, then unwinds.

Re-installing the sudoer (calling `set_sudoer` again) restarts the two-day clock, because `sudoer_set_at` is reset to the new `now()`. There is no governor-driven code upgrade, and there is no on-chain version-bump operation. The pool exposes `get_code_version()` in `versioning.func`, which simply returns a compile-time `git_hash` from `auto/git-hash.func`. That value is informational only; it is not a control surface and does not change as part of any upgrade transaction.

## Governor migration (two-step, 1-day timelock)

Replacing the governor itself is a deliberate two-step handshake with its own timelock, defined in `pool.func`:

```
const int GOVERNOR_QUARANTINE = 86400; ;; 1 day
```

Step one, `governor::prepare_governance_migration` (`0x9971881c`): the current governor submits a `governor_update_after` timestamp. The handler stores it via `set_governor_update_after`, then enforces `throw_unless(error::governor_update_too_soon, governor_update_after - now() > GOVERNOR_QUARANTINE)`, that is, the proposed effective time must be more than one day in the future. The error code for proposing too soon is `error::governor_update_too_soon` (`0xa001`).

Step two, `governor::set_roles` (`0x5e517f36`): the governor change is applied only inside `set_roles`, and only when its first boolean flag is set. At that point the handler enforces `throw_unless(error::governor_update_not_matured, get_governor_update_after(roles) < now())`, so the prepared timestamp must already have passed. The new governor address is then installed and `governor_update_after` is set to a sentinel far-future value (`0xffffffffffff`) so the slot cannot be re-used without a fresh prepare step. The error code for executing before maturity is `error::governor_update_not_matured` (`0xa003`).

The same `set_roles` message can also update `interest_manager`, `halter`, `approver`, and `treasury` in the same transaction, each behind its own boolean flag. Those four role changes are instant: only the governor slot itself carries the 1-day maturity check. This is the practical asymmetry of the model: rotating the emergency, interest, approval, and fee-sink keys is immediate, but rotating the central governor key requires a public, day-long waiting window.

## Halt, partial-halt, unhalt and self-halt

Halting is the pool's circuit breaker. There are three ways it can engage and exactly one way it can be cleared.

**Full halt.** `halter::halt` (`0x139a1b4e`) sets `halted? = true`. Once halted, `assert_not_halted!()` (defined in `asserts.func`) makes the gated operations revert. Halting blocks deposits, loan requests, loan repayments, round upkeep, controller deployment, and similar pool operations.

**Partial halt.** `halter::partial_halt` (`0x77778888`) is a softer brake. It can flip `optimistic_deposit_withdrawals` off (forcing the queued, pessimistic path) and/or flip `deposits_open?` off (closing new deposits), without setting the global `halted?` flag.

**Self-halt.** The pool can halt itself. If, at round finalization, it cannot cover the queued withdrawals plus its storage reserve (`MIN_TONS_FOR_STORAGE`, 10 Gram), it sets `halted? = true` on its own and pays nobody until it is topped up. No human key is involved in this path; it is a safety latch.

**Withdrawals stay open.** This is the most important property of the halt design. The withdraw handler runs `assert_not_halted!()` inside a `try` block, so a halt during withdrawal does not destroy value: the burned KTON is re-minted back to the user (the `catch` branch calls `request_to_mint_pool_jettons(from_address, jetton_amount, query_id, true)`). The burn-notification op (`pool::withdraw`) is the path a user's KTON burn lands on, and it is structured so that any revert refunds the user's KTON rather than losing it. In other words, halting can stop new deposits and loan activity, but it is built so the protocol can always either pay out or hand back the user's tokens.

**Only the governor unhalts.** Clearing the halt requires `governor::unhalt` (`0x7247e7a5`), guarded by `assert_sender!(sender_address, get_governor(roles))`. The halter that pressed the button cannot release it; resumption is a separate, higher-trust role. This guarantees that an emergency stop is reviewed by the governing key before normal operation resumes.

## Why this model matters

The role split turns a single point of failure into a layered one. The key that can rewrite the contract (sudoer) is different from the key that runs it (governor), is different again from the key that can freeze it (halter), and the key that profits from fees (treasury) has no power at all. The two most dangerous transitions, replacing code and replacing the governor, are both delayed by on-chain timelocks (2 days and 1 day respectively) that are visible to anyone watching the chain before they take effect. None of these controls are token-voted at the pool level; they are address-keyed with mandatory waiting periods, which is the protocol's chosen form of trust minimization.

Next: **Payout NFT Receipts**


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.kton.io/protocol-internals/10-governance-roles-upgrades.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
