Skip to main content

ic_memory/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(rustdoc::broken_intra_doc_links)]
3#![doc = include_str!("../README.md")]
4
5//! Stable-memory allocation-governance primitives for Internet Computer
6//! canister upgrades.
7//!
8//! `ic-memory` prevents stable-memory slot drift.
9//!
10//! Once a stable key is committed to a physical allocation slot, future binaries
11//! must either reopen that same stable key on that same slot or declare a new
12//! stable key.
13//!
14//! The crate records and validates durable ownership in both directions: an
15//! active stable key cannot move to a different physical slot, and an active
16//! physical slot cannot be reused by a different stable key.
17//!
18//! The intended integration flow is:
19//!
20//! 1. Recover the persisted allocation ledger.
21//! 2. Declare the stable stores expected by the current binary.
22//! 3. Validate those declarations against ledger history and any framework
23//!    policy.
24//! 4. Commit the next generation.
25//! 5. Only then open stable-memory handles through a validated allocation
26//!    session.
27//!
28//! This crate owns allocation invariants, not framework policy. Namespace
29//! rules, controller authorization, endpoint lifecycle, schema migrations, and
30//! application validation belong to the framework or application.
31//!
32//! For the default `MemoryManager` runtime, registered `ic-memory` range claims
33//! are generic allocation policy and are enforced before caller-supplied
34//! policy. A framework such as Canic that wants higher-level range semantics
35//! should adapt to this contract deliberately: either register the ranges it
36//! wants `ic-memory` to enforce, or omit user ranges and enforce application
37//! space through its own [`AllocationPolicy`].
38//!
39//! Use these primitives before opening stable-memory handles. Integrations
40//! should recover the historical ledger, declare the stores expected by the
41//! current binary, validate declarations against history and policy, commit a
42//! new generation, and only then publish a validated allocation session that can
43//! open slots through a storage substrate.
44//!
45//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
46//! ledger store. Canic may own bootstrap for a framework canister and compose
47//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
48//! directly for generated database stores; or a standalone application canister
49//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
50//! store. Multiple layers in the same canister must either compose declarations
51//! into that owner or use distinct ledger stores and allocation domains.
52//!
53//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
54//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
55//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
56//! The crate still keeps narrow internal abstractions for storage adapters and
57//! diagnostics, but the native IC path is
58//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
59//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
60//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`].
61//!
62//! `ic-memory` is not a replacement for `ic-stable-structures` collections and
63//! does not wrap typed stores such as `StableBTreeMap`.
64
65mod bootstrap;
66mod constants;
67mod declaration;
68mod diagnostics;
69mod key;
70mod ledger;
71mod physical;
72mod policy;
73mod registry;
74mod runtime;
75mod schema;
76mod session;
77mod slot;
78mod stable_cell;
79mod substrate;
80mod validation;
81
82pub use ic_stable_structures as stable_structures;
83
84pub use bootstrap::{
85    AllocationBootstrap, BootstrapCommit, BootstrapError, BootstrapReservationError,
86    BootstrapRetirementError,
87};
88pub use constants::WASM_PAGE_SIZE_BYTES;
89pub use declaration::{
90    AllocationDeclaration, DeclarationCollector, DeclarationSnapshot, DeclarationSnapshotError,
91};
92pub use diagnostics::{
93    DefaultMemoryManagerDoctorReport, DiagnosticCheck, DiagnosticCheckStatus,
94    DiagnosticDeclaration, DiagnosticExport, DiagnosticGeneration, DiagnosticMemorySize,
95    DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticStableCell, DiagnosticStableCellStatus,
96};
97pub use key::{StableKey, StableKeyError};
98pub use ledger::{
99    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
100    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
101    GenerationRecord, LedgerCommitError, LedgerCommitStore, LedgerIntegrityError,
102    LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger, SchemaMetadataRecord,
103};
104pub use physical::{
105    AuthoritativeSlot, CommitRecoveryError, CommitSlotDiagnostic, CommitSlotIndex,
106    CommitStoreDiagnostic, CommittedGenerationBytes, DualCommitStore, DualProtectedCommitStore,
107    ProtectedGenerationSlot, select_authoritative_slot,
108};
109pub use policy::AllocationPolicy;
110pub use registry::{
111    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
112    collect_static_memory_declarations, register_static_memory_declaration,
113    register_static_memory_manager_declaration,
114    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
115    register_static_memory_range_declaration, static_memory_declaration_snapshot,
116    static_memory_declarations, static_memory_range_authority, static_memory_range_declarations,
117};
118pub use runtime::{
119    RuntimeBootstrapError, RuntimeDiagnosticError, RuntimeOpenError, RuntimePolicyError,
120    bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_policy,
121    default_memory_manager_commit_recovery_diagnostic, default_memory_manager_diagnostic_export,
122    default_memory_manager_doctor_report, is_default_memory_manager_bootstrapped,
123    open_default_memory_manager_memory, validated_allocations,
124};
125pub use schema::{SchemaMetadata, SchemaMetadataError};
126pub use session::{AllocationSession, AllocationSessionError, ValidatedAllocations};
127pub use slot::{
128    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
129    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
130    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
131    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
132    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
133    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
134    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
135    validate_memory_manager_id,
136};
137pub use stable_cell::{
138    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
139    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
140    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
141    validate_stable_cell_ledger_memory,
142};
143pub use substrate::{LedgerAnchor, StorageSubstrate};
144pub use validation::{AllocationValidationError, Validate, validate_allocations};
145
146#[doc(hidden)]
147pub use runtime::defer_eager_init;
148
149#[doc(hidden)]
150pub mod __reexports {
151    pub use ctor;
152}
153
154/// Register a `MemoryManager` allocation declaration during static initialization.
155///
156/// This macro only registers declaration metadata. It does not open stable
157/// memory. The bootstrap owner still has to collect/seal declarations, validate
158/// them against the ledger, commit the generation, and then open memory handles.
159#[macro_export]
160macro_rules! ic_memory_declaration {
161    (key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
162        const _: () = {
163            #[expect(dead_code, reason = "type alias exists only to validate the macro type path")]
164            type IcMemoryTypeCheck = $label;
165
166            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
167            fn __ic_memory_register_static_declaration() {
168                $crate::register_static_memory_manager_declaration(
169                    $id,
170                    env!("CARGO_PKG_NAME"),
171                    stringify!($label),
172                    $stable_key,
173                )
174                .expect("ic-memory static memory declaration failed");
175            }
176        };
177    };
178    (key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
179        const _: () = {
180            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
181            fn __ic_memory_register_static_declaration() {
182                $crate::register_static_memory_manager_declaration(
183                    $id,
184                    env!("CARGO_PKG_NAME"),
185                    $label,
186                    $stable_key,
187                )
188                .expect("ic-memory static memory declaration failed");
189            }
190        };
191    };
192}
193
194/// Declare a `MemoryManager` allocation range during static initialization.
195#[macro_export]
196macro_rules! ic_memory_range {
197    (start = $start:expr, end = $end:expr $(,)?) => {
198        $crate::ic_memory_range!(
199            start = $start,
200            end = $end,
201            mode = Reserved,
202        );
203    };
204    (start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
205        const _: () = {
206            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
207            fn __ic_memory_register_static_range() {
208                $crate::register_static_memory_manager_range(
209                    $start,
210                    $end,
211                    env!("CARGO_PKG_NAME"),
212                    $crate::MemoryManagerRangeMode::$mode,
213                    None,
214                )
215                .expect("ic-memory static memory range declaration failed");
216            }
217        };
218    };
219}
220
221/// Declare and open a validated `MemoryManager` slot by stable key.
222///
223/// The macro registers declaration metadata during static initialization and
224/// returns the validated default-runtime memory handle at expression use time.
225#[macro_export]
226macro_rules! ic_memory_key {
227    ($stable_key:literal, $label:path, $id:expr $(,)?) => {{
228        $crate::ic_memory_declaration!(key = $stable_key, ty = $label, id = $id,);
229        $crate::open_default_memory_manager_memory($stable_key, $id)
230            .expect("ic-memory failed to open validated stable memory; bootstrap must run first and the stable key/id must match the validated declaration")
231    }};
232    (key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{ $crate::ic_memory_key!($stable_key, $label, $id) }};
233    (key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
234        $crate::ic_memory_declaration!(key = $stable_key, label = $label, id = $id,);
235        $crate::open_default_memory_manager_memory($stable_key, $id)
236            .expect("ic-memory failed to open validated stable memory; bootstrap must run first and the stable key/id must match the validated declaration")
237    }};
238}
239
240/// Register one pre-bootstrap hook.
241#[macro_export]
242macro_rules! eager_init {
243    ($body:block) => {
244        const _: () = {
245            fn __ic_memory_registered_eager_init_body() {
246                $body
247            }
248
249            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
250            fn __ic_memory_register_eager_init() {
251                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
252            }
253        };
254    };
255}