|
| 1 | +# StableRoute — Config Model & Invariants |
| 2 | + |
| 3 | +Authoritative reference for the router's admin-configurable state |
| 4 | +([`src/lib.rs`](../src/lib.rs)): what can be configured, which entrypoints |
| 5 | +write it, and the invariants the code enforces. For the fee *arithmetic* |
| 6 | +itself see [`docs/fees.md`](fees.md); for raw storage-key shapes see |
| 7 | +[`docs/storage.md`](storage.md). |
| 8 | + |
| 9 | +## Two config surfaces |
| 10 | + |
| 11 | +Config splits into two independent surfaces: |
| 12 | + |
| 13 | +- **Per-pair config** — scoped to a `(source, destination)` corridor. |
| 14 | +- **Global config** — singleton, contract-wide. |
| 15 | + |
| 16 | +### Per-pair config |
| 17 | + |
| 18 | +| Field | Setter | Getter | Bound | Default | |
| 19 | +|-------|--------|--------|-------|---------| |
| 20 | +| Registration | `register_pair` / `register_pairs` | `is_pair_registered` | `source != destination` | `false` | |
| 21 | +| Fee (bps) | `set_pair_fee_bps` / `set_pair_fees_bps` | `get_pair_fee_bps` | `<= MAX_FEE_BPS` (#4) | `0` | |
| 22 | +| Min amount | `set_pair_min_amount` | `get_pair_min_amount` | `>= 0` (#6) | `0` | |
| 23 | +| Max amount | `set_pair_max_amount` | `get_pair_max_amount` | `> 0` (#6) | `i128::MAX` (unbounded) | |
| 24 | +| Cooldown (secs) | `set_pair_cooldown` | — (see `PairInfoExt`) | `<= MAX_COOLDOWN_SECS` (#20) | `0` (disabled) | |
| 25 | +| Liquidity | `set_pair_liquidity` | `get_pair_liquidity` | `>= 0` (#6) | unbounded when unset | |
| 26 | + |
| 27 | +`get_pair_info` / `get_pair_info_ext` (see `docs/abi.md`) return the whole |
| 28 | +per-pair surface in one call instead of five-plus separate getters. |
| 29 | + |
| 30 | +### Global config |
| 31 | + |
| 32 | +| Field | Setter | Getter | Bound | Default | |
| 33 | +|-------|--------|--------|-------|---------| |
| 34 | +| Fee recipient | `set_fee_recipient` | `get_fee_recipient` | — | `None` | |
| 35 | +| Max fee (absolute) | `set_max_fee_absolute` / `clear_max_fee_absolute` | `get_max_fee_absolute` | `> 0` (#6, #21) | `None` | |
| 36 | +| Min fee (absolute) | `set_min_fee_absolute` | `get_min_fee_absolute` | `>= 0` (#6) | `None` | |
| 37 | +| Oracle | `set_oracle` / `remove_oracle` | `get_oracle` | — | `None` | |
| 38 | +| Timelock delay | `set_timelock` | `get_timelock` | — | `0` | |
| 39 | + |
| 40 | +`get_global_config` returns this whole surface — fee recipient, both |
| 41 | +absolute fee bounds, oracle, and timelock delay — in a single read-only |
| 42 | +call (added in #409). |
| 43 | + |
| 44 | +## Invariants |
| 45 | + |
| 46 | +1. **Admin-gated writes.** Every setter above calls `Self::require_admin`, |
| 47 | + which loads the stored admin and calls `require_auth()`. There is no |
| 48 | + config field that can be written by a non-admin caller. (`set_pair_liquidity` |
| 49 | + is the one exception: it also accepts calls from the configured oracle — |
| 50 | + see `docs/roles.md`.) |
| 51 | +2. **Registration-first.** `set_pair_fee_bps`, `set_pair_min_amount`, |
| 52 | + `set_pair_max_amount`, `set_pair_cooldown`, and `set_pair_liquidity` all |
| 53 | + require the pair to already be registered via `register_pair`, and panic |
| 54 | + with `PairNotRegistered` (#5) otherwise. A corridor's config cannot exist |
| 55 | + without the corridor itself existing. |
| 56 | +3. **All-or-nothing batches.** `register_pairs` and `set_pair_fees_bps` |
| 57 | + validate every entry before writing any of them; a single invalid entry |
| 58 | + rolls back the whole call (Soroban transaction atomicity). |
| 59 | +4. **Read views never mutate.** `get_pair_info`, `get_pair_info_ext`, |
| 60 | + `get_global_config`, and every individual getter are pure reads — no |
| 61 | + entrypoint that returns config data writes to storage or emits events. |
| 62 | +5. **Config change events are not duplicated.** Every setter that changes |
| 63 | + persisted config state emits a dedicated event carrying the new value |
| 64 | + (`fee_set`, `min_set`, `max_set`, `cd_set`, `liq_set`, `maxfee`, |
| 65 | + `minfee`, `recip_set`, `tlock_set`, …). `register_pair`/`register_pairs` |
| 66 | + specifically guard against re-emitting `pair_reg` on an idempotent |
| 67 | + re-registration of an already-registered pair (see #410). |
| 68 | +6. **Clearing config is explicit, not defaulting.** `unregister_pair` and |
| 69 | + `purge_pair_metrics` remove state (and emit `cfg_clr` / `pair_mrst` |
| 70 | + respectively) rather than silently falling back to defaults on the next |
| 71 | + read; `clear_max_fee_absolute` is the equivalent for the global absolute |
| 72 | + fee cap. |
| 73 | + |
| 74 | +## See also |
| 75 | + |
| 76 | +- [`docs/fees.md`](fees.md) — fee arithmetic, relative/absolute cap |
| 77 | + composition, worked examples |
| 78 | +- [`docs/storage.md`](storage.md) — `DataKey` shapes and TTL classification |
| 79 | +- [`docs/roles.md`](roles.md) — admin vs. oracle authorization |
| 80 | +- [`docs/abi.md`](abi.md) — full entrypoint signatures |
0 commit comments