Skip to content

Commit a00cdaf

Browse files
docs: add contract lifecycle state diagram for init, pause, migrate, and config-freeze (ApexChainx#256)
Adds docs/CONTRACT_LIFECYCLE.md — a comprehensive reference document containing Mermaid state-transition diagrams for every major lifecycle flow in the apexchainx_calculator contract. All transitions are sourced directly from lib.rs, governance.rs, and config_freeze.rs. ## Diagrams included 1. Top-level lifecycle — Uninitialized → Active, plus Paused, NeedsMigration, ConfigFrozen, and AdminRenounced states with their triggering function calls and emitted events. 2. Pause / Unpause flow — entry/exit conditions, operations that are blocked (calculate_sla, set_config) vs. still allowed (is_paused, get_pause_info, read-only views) while paused. 3. Storage migration flow — VersionCurrent → VersionMismatch on binary upgrade, migrate() recovery path, and the four functions that bypass check_version() (get_version_info, get_migration_state, healthcheck, migrate itself). 4. Config-freeze flow — Thawed ↔ Frozen transitions, which write operations are blocked (set_config, set_custom_severity, remove_custom_severity) and which reads are always allowed. 5. Admin transfer (two-step) — propose_admin → accept_admin or cancel_admin_proposal, plus the irreversible renounce_admin terminal state. 6. Operator handoff (two-step) — propose_operator → accept_operator or cancel_operator_proposal, plus the direct set_operator single-step legacy path. ## Additional content - Combined orthogonal state matrix — ASCII table showing which operations succeed or fail across the four independent state axes (Not Initialized, NeedsMigration, Running, Paused). - Guard evaluation order — documents the stack: check_version → require_not_paused → require_admin/require_operator → require_not_frozen. - Invariants and guard table — 11 invariants with their enforcement location in the code. - Source references table — maps every transition to its implementation file and function. ## Linked from - README.md: new 'Contract Lifecycle' section with ASCII summary and link to the full document. - docs/PROJECT_CONTEXT.md: new 'Contract Lifecycle' section in the Table of Contents and body, with ASCII quick-overview and link. Closes ApexChainx#256
1 parent 594e198 commit a00cdaf

4 files changed

Lines changed: 263 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
## [Unreleased]
1010

1111
### Added
12+
- `docs/CONTRACT_LIFECYCLE.md` — Mermaid state-transition diagrams for the `apexchainx_calculator` contract lifecycle: top-level lifecycle, pause/unpause, storage migration, config-freeze, admin transfer (two-step), and operator handoff flows; plus the combined orthogonal state matrix and invariants table (closes #256)
1213
- `docs/CONTRACT_MAINTENANCE_POLICY.md` — comprehensive maintenance policy covering `#[contracttype]` compatibility notes (#279), response-shape stability (#283), version negotiation (#284), API archetypes (#285), event payload size checks (#286), event drift review (#287), history write audit (#288), telemetry counters (#289), and role-change incident review (#290)
1314
- `tooling/release-summary.ts` — release summary generator for maintainers (#280)
1415
- `.devcontainer/` — reproducible dev container workspace with Rust + WASM target + just + Node.js (#281)

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,14 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed setup instructions.
9898
- **Dependency auditing:** `cargo audit` runs on CI for every push
9999
- **WASM integrity:** Release artifacts include SHA-256 manifests
100100
- **Reproducible builds:** Local builds can be verified against CI-generated manifests
101+
102+
## Contract Lifecycle
103+
104+
The contract's state machine has four orthogonal axes — initialized,
105+
version-matched, paused, and config-frozen — that stack to determine which
106+
operations are permitted.
107+
108+
**[docs/CONTRACT_LIFECYCLE.md](docs/CONTRACT_LIFECYCLE.md)** — Mermaid
109+
state-transition diagrams for init, pause, migrate, config-freeze, admin
110+
transfer, and operator handoff flows, plus the combined state matrix and
111+
invariants table.

docs/CONTRACT_LIFECYCLE.md

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Contract Lifecycle State Diagram
2+
3+
> State-transition reference for the `apexchainx_calculator` contract.
4+
> Resolves [#256](https://github.com/ApexChainx/ApexChainx-Contracts/issues/256).
5+
> All transitions are sourced directly from
6+
> `apexchainx_calculator/src/lib.rs`, `governance.rs`, and `config_freeze.rs`.
7+
8+
---
9+
10+
## Table of Contents
11+
12+
1. [Overview](#overview)
13+
2. [Top-level lifecycle](#top-level-lifecycle)
14+
3. [Pause / Unpause flow](#pause--unpause-flow)
15+
4. [Storage migration flow](#storage-migration-flow)
16+
5. [Config-freeze flow](#config-freeze-flow)
17+
6. [Admin transfer (two-step)](#admin-transfer-two-step)
18+
7. [Operator handoff (two-step)](#operator-handoff-two-step)
19+
8. [Combined orthogonal state matrix](#combined-orthogonal-state-matrix)
20+
9. [Invariants and guard table](#invariants-and-guard-table)
21+
22+
---
23+
24+
## Overview
25+
26+
The contract has **four independent boolean axes** that combine to determine
27+
which operations are permitted at any moment:
28+
29+
| Axis | Storage key | Default | Blocks |
30+
|------|-------------|---------|--------|
31+
| Initialized | `ADMIN` present | false | All versioned calls |
32+
| Paused | `PAUSED` | false | `calculate_sla`, state-changing ops |
33+
| Version-matched | `VER == STORAGE_VERSION` | true after init | All versioned calls |
34+
| Config frozen | `FREEZE` | false | `set_config`, `set_custom_severity`, `remove_custom_severity` |
35+
36+
The diagrams below model each axis independently, then the matrix section
37+
shows their interactions.
38+
39+
---
40+
41+
## Top-level lifecycle
42+
43+
```mermaid
44+
stateDiagram-v2
45+
[*] --> Uninitialized : contract deployed
46+
47+
Uninitialized --> Active : initialize(admin, operator)\n[stamps VER=1, sets ADMIN, OPERATOR, PAUSED=false]
48+
49+
Active --> Paused : pause(caller, reason)\n[admin only]
50+
Paused --> Active : unpause(caller)\n[admin only]
51+
52+
Active --> NeedsMigration : contract binary upgraded\n(STORAGE_VERSION bumped)
53+
NeedsMigration --> Active : migrate(admin)\n[applies v0→v1→… steps]
54+
55+
Active --> ConfigFrozen : freeze_config(admin)
56+
ConfigFrozen --> Active : unfreeze_config(admin)
57+
58+
Active --> AdminRenounced : renounce_admin(admin)\n[irreversible — admin key removed]
59+
Paused --> AdminRenounced : renounce_admin(admin)\n[irreversible]
60+
```
61+
62+
> **Note:** `AdminRenounced` is a terminal state for governance.
63+
> `pause`, `set_config`, and admin-transfer functions are permanently locked once
64+
> `renounce_admin` is called because all require the `ADMIN` key to be present.
65+
66+
---
67+
68+
## Pause / Unpause flow
69+
70+
```mermaid
71+
stateDiagram-v2
72+
[*] --> Running : initialize() succeeds\n(PAUSED = false)
73+
74+
Running --> Paused : pause(caller, reason)\n• admin only\n• reason ≤ 256 bytes\n• stores PauseInfo{reason, timestamp, paused_by}\n• emits paused event
75+
76+
Paused --> Running : unpause(caller)\n• admin only\n• clears PauseInfo\n• emits unpause event
77+
78+
Running --> Running : calculate_sla ✓\nset_config ✓\nset_operator ✓\n(all state-changing ops allowed)
79+
80+
Paused --> Paused : calculate_sla ✗ → ContractPaused\nset_config ✗ → ContractPaused\nis_paused() ✓\nget_pause_info() ✓\n(read-only ops still work)
81+
```
82+
83+
---
84+
85+
## Storage migration flow
86+
87+
```mermaid
88+
stateDiagram-v2
89+
[*] --> VersionCurrent : initialize()\n(writes VER = STORAGE_VERSION = 1)
90+
91+
VersionCurrent --> VersionMismatch : contract binary upgraded\n(new binary has STORAGE_VERSION = N+1)\n(on-chain VER still = N)
92+
93+
VersionMismatch --> VersionCurrent : admin calls migrate()\n• applies each step: v0→v1→v1→v2…\n• idempotent when already current\n• emits migrate_done event
94+
95+
VersionMismatch --> VersionMismatch : any versioned endpoint called\nreturns VersionMismatch error\n(get_version_info / healthcheck bypass this guard)
96+
97+
VersionCurrent --> VersionCurrent : normal operation\ncheck_version() passes
98+
```
99+
100+
**Key bypass functions** (callable even in `VersionMismatch` state):
101+
102+
| Function | Why it bypasses |
103+
|----------|----------------|
104+
| `get_version_info()` | Backend needs to read version before deciding to call `migrate` |
105+
| `get_migration_state()` | Read-only diagnostic |
106+
| `healthcheck()` | Load-balancer probe — must always respond |
107+
| `migrate()` | The function that fixes the mismatch |
108+
109+
---
110+
111+
## Config-freeze flow
112+
113+
```mermaid
114+
stateDiagram-v2
115+
[*] --> Thawed : initialize()\n(FREEZE key absent → defaults to false)
116+
117+
Thawed --> Frozen : freeze_config(admin)\n• admin only\n• sets FREEZE = true\n• emits cfg_frz event
118+
119+
Frozen --> Thawed : unfreeze_config(admin)\n• admin only\n• sets FREEZE = false\n• emits cfg_unfrz event
120+
121+
Thawed --> Thawed : set_config ✓\nset_custom_severity ✓\nremove_custom_severity ✓
122+
123+
Frozen --> Frozen : set_config ✗ → ConfigFrozen\nset_custom_severity ✗ → ConfigFrozen\nremove_custom_severity ✗ → ConfigFrozen\nget_config ✓\nget_config_snapshot ✓\n(reads are always allowed)
124+
```
125+
126+
---
127+
128+
## Admin transfer (two-step)
129+
130+
```mermaid
131+
stateDiagram-v2
132+
[*] --> AdminSet : initialize()\n(ADMIN = original_admin)
133+
134+
AdminSet --> PendingTransfer : propose_admin(caller, new_admin)\n• current admin only\n• stores PADMIN = new_admin\n• emits adm_prop event
135+
136+
PendingTransfer --> AdminSet : accept_admin(new_admin)\n• must be called by proposed new_admin\n• ADMIN ← new_admin\n• clears PADMIN\n• emits adm_acc event
137+
138+
PendingTransfer --> AdminSet : cancel_admin_proposal(caller)\n• current admin only\n• clears PADMIN\n• emits adm_can event
139+
140+
AdminSet --> Renounced : renounce_admin(admin)\n• removes ADMIN key\n• clears any pending PADMIN\n• emits adm_ren event\n• IRREVERSIBLE
141+
142+
Renounced --> Renounced : all admin-gated calls\npermanently return Unauthorized
143+
```
144+
145+
---
146+
147+
## Operator handoff (two-step)
148+
149+
```mermaid
150+
stateDiagram-v2
151+
[*] --> OperatorSet : initialize()\n(OPERATOR = original_operator)
152+
153+
OperatorSet --> PendingHandoff : propose_operator(admin, new_operator)\n• admin only\n• stores POP = new_operator\n• emits op_prop event
154+
155+
PendingHandoff --> OperatorSet : accept_operator(new_operator)\n• must be called by proposed new_operator\n• OPERATOR ← new_operator\n• clears POP\n• emits op_acc event
156+
157+
PendingHandoff --> OperatorSet : cancel_operator_proposal(admin)\n• admin only\n• clears POP\n• emits op_can event
158+
159+
OperatorSet --> OperatorSet : set_operator(admin, new_operator)\n• direct (single-step) replacement\n• admin only\n• emits op_set event
160+
```
161+
162+
> **Note:** `set_operator` is a direct single-step replacement (legacy path).
163+
> The two-step `propose_operator` / `accept_operator` flow is preferred for
164+
> operational safety because it requires the incoming operator to confirm.
165+
166+
---
167+
168+
## Combined orthogonal state matrix
169+
170+
The four axes (initialized, version-matched, paused, config-frozen) are
171+
independent but **stack**: a call fails at the first guard it hits.
172+
173+
Guard evaluation order inside `calculate_sla` and `set_config`:
174+
175+
1. `check_version()` → fails with `VersionMismatch` or `NotInitialized`
176+
2. `require_not_paused()` (calculate_sla) → fails with `ContractPaused`
177+
3. `require_admin()` / `require_operator()` → fails with `Unauthorized`
178+
4. `require_not_frozen()` (set_config) → fails with `ConfigFrozen`
179+
180+
```
181+
│ Not Init │ NeedsMigr. │ Running │ Paused │
182+
────────────────────────┼──────────┼────────────┼─────────┼────────┤
183+
initialize() │ ✓ │ ✓ │ ✗ │ ✗ │
184+
migrate() │ ✗ │ ✓ │ nop │ ✓ │
185+
calculate_sla() │ ✗ │ ✗ │ ✓ │ ✗ │
186+
set_config() │ ✗ │ ✗ │ ✓/✗* │ ✗ │
187+
pause() / unpause() │ ✗ │ ✗ │ ✓ │ ✓/✓ │
188+
freeze/unfreeze() │ ✗ │ ✗ │ ✓/✓* │ ✓ │
189+
get_version_info() │ ✗ │ ✓ │ ✓ │ ✓ │
190+
healthcheck() │ ✓ │ ✓ │ ✓ │ ✓ │
191+
get_result_schema() │ ✗ │ ✗ │ ✓ │ ✓ │
192+
193+
* depends on ConfigFrozen axis (✗ when frozen, ✓ when thawed)
194+
```
195+
196+
---
197+
198+
## Invariants and guard table
199+
200+
| Invariant | Where enforced |
201+
|-----------|---------------|
202+
| `initialize()` is called exactly once | `ADMIN_KEY` presence check at start of `initialize()` |
203+
| All versioned endpoints require `VER == STORAGE_VERSION` | `check_version()` called at the top of every public function (except bypass list) |
204+
| `calculate_sla` is blocked while paused | `require_not_paused()` called before operator check |
205+
| `set_config` is blocked while config is frozen | `require_not_frozen()` called after admin check |
206+
| Admin transfer requires proposee to accept | `PENDING_ADMIN_KEY` must be present and caller must match |
207+
| Operator handoff requires proposee to accept | `PENDING_OP_KEY` must be present and caller must match |
208+
| `renounce_admin` is irreversible | `ADMIN_KEY` is removed; no path to re-set it without `initialize()` |
209+
| Custom severities cannot shadow canonical ones | `is_canonical_severity()` check in `set_custom_severity()` |
210+
| History capped at `MAX_HISTORY_SIZE` (1000) or configurable limit | FIFO trim after every `calculate_sla` write |
211+
| One outage capped at `MAX_RECALCS_PER_OUTAGE` (16) retained entries | Scan + count inside `calculate_sla` |
212+
213+
---
214+
215+
## Source references
216+
217+
| Transition / guard | Implementation location |
218+
|--------------------|------------------------|
219+
| `initialize()` | `lib.rs::SLACalculatorContract::initialize` |
220+
| `pause()` / `unpause()` | `lib.rs::SLACalculatorContract::pause` / `unpause` |
221+
| `freeze_config()` / `unfreeze_config()` | `config_freeze.rs::freeze_config` / `unfreeze_config` |
222+
| `migrate()` | `lib.rs::SLACalculatorContract::migrate` |
223+
| `check_version()` | `lib.rs::SLACalculatorContract::check_version` |
224+
| `propose_admin()` / `accept_admin()` | `governance.rs::propose_admin` / `accept_admin` |
225+
| `renounce_admin()` | `governance.rs::renounce_admin` |
226+
| `propose_operator()` / `accept_operator()` | `governance.rs::propose_operator` / `accept_operator` |
227+
| `set_operator()` | `governance.rs::set_operator` |

docs/PROJECT_CONTEXT.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Repository Architecture](#repository-architecture)
99
- [System Flow](#system-flow)
1010
- [Architectural Rules](#architectural-rules)
11+
- [Contract Lifecycle](#contract-lifecycle)
1112
- [SC-100: Future Contract Roadmap](#sc-100-future-contract-roadmap)
1213

1314
---
@@ -42,6 +43,29 @@ The ApexChainx platform is composed of three repositories:
4243

4344
---
4445

46+
## Contract Lifecycle
47+
48+
The `apexchainx_calculator` contract has four independent state axes
49+
(initialized, version-matched, paused, config-frozen) that combine to determine
50+
which operations are permitted at any moment.
51+
52+
**→ See the full state-transition diagram: [docs/CONTRACT_LIFECYCLE.md](CONTRACT_LIFECYCLE.md)**
53+
54+
Quick overview of the main lifecycle states:
55+
56+
```
57+
[Uninitialized] ──initialize()──→ [Active]
58+
[Active] ──pause()──→ [Paused] ──unpause()──→ [Active]
59+
[Active] ──(binary upgrade)──→ [NeedsMigration] ──migrate()──→ [Active]
60+
[Active] ──freeze_config()──→ [ConfigFrozen] ──unfreeze_config()──→ [Active]
61+
[Active] ──renounce_admin()──→ [AdminRenounced] ← irreversible
62+
```
63+
64+
See [`CONTRACT_LIFECYCLE.md`](CONTRACT_LIFECYCLE.md) for Mermaid diagrams of
65+
each flow, the combined state matrix, and the full invariants table.
66+
67+
---
68+
4569
## SC-100: Future Contract Roadmap
4670

4771
This section documents the planned evolution of `apexchainx-contracts` based on

0 commit comments

Comments
 (0)