1#[cfg(target_arch = "wasm32")]
2use crate::ledger;
3use crate::{
4 cdk::structures::{
5 DefaultMemoryImpl,
6 memory::{MemoryId, VirtualMemory},
7 },
8 manager::MEMORY_MANAGER,
9 registry::{
10 MemoryRange, MemoryRangeAuthority, MemoryRegistry, MemoryRegistryError, defer_register,
11 defer_register_with_key_metadata,
12 },
13 runtime::{MemoryRuntimeApi, registry::MemoryRegistryRuntime},
14};
15
16pub struct MemoryApi;
23
24#[derive(Clone, Debug, Eq, PartialEq)]
30pub struct MemoryInspection {
31 pub id: u8,
33 pub owner: String,
35 pub range: MemoryRange,
37 pub label: Option<String>,
39 pub stable_key: Option<String>,
41 pub schema_version: Option<u32>,
43 pub schema_fingerprint: Option<String>,
45}
46
47#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct RegisteredMemory {
54 pub id: u8,
56 pub owner: String,
58 pub range: MemoryRange,
60 pub label: String,
62 pub stable_key: String,
64 pub schema_version: Option<u32>,
66 pub schema_fingerprint: Option<String>,
68}
69
70#[derive(Clone, Debug, Eq, PartialEq)]
76pub struct LedgerSnapshot {
77 pub authorities: Vec<MemoryRangeAuthority>,
79 pub ranges: Vec<(String, MemoryRange)>,
81 pub entries: Vec<(u8, crate::registry::MemoryRegistryEntry)>,
83}
84
85impl MemoryApi {
86 pub fn bootstrap_owner_range(
88 crate_name: &'static str,
89 start: u8,
90 end: u8,
91 ) -> Result<(), MemoryRegistryError> {
92 let _ = MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)?;
93 Ok(())
94 }
95
96 pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
99 let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
100 Ok(())
101 }
102
103 pub fn declare(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
107 if MemoryRegistryRuntime::is_initialized() {
108 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
109 ranges: 0,
110 registrations: 1,
111 });
112 }
113
114 defer_register(id, crate_name, label)
115 }
116
117 pub fn declare_with_key(
121 id: u8,
122 crate_name: &str,
123 label: &str,
124 stable_key: &str,
125 ) -> Result<(), MemoryRegistryError> {
126 Self::declare_with_key_metadata(id, crate_name, label, stable_key, None, None)
127 }
128
129 pub fn declare_with_key_metadata(
134 id: u8,
135 crate_name: &str,
136 label: &str,
137 stable_key: &str,
138 schema_version: Option<u32>,
139 schema_fingerprint: Option<&str>,
140 ) -> Result<(), MemoryRegistryError> {
141 if MemoryRegistryRuntime::is_initialized() {
142 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
143 ranges: 0,
144 registrations: 1,
145 });
146 }
147
148 defer_register_with_key_metadata(
149 id,
150 crate_name,
151 label,
152 stable_key,
153 schema_version,
154 schema_fingerprint,
155 )
156 }
157
158 pub fn register(
163 id: u8,
164 crate_name: &str,
165 label: &str,
166 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
167 if !MemoryRegistryRuntime::is_initialized() {
168 return Err(MemoryRegistryError::RegistryNotBootstrapped);
169 }
170
171 if let Some(entry) = MemoryRegistry::get(id)
172 && entry.crate_name == crate_name
173 && entry.label == label
174 {
175 return Ok(open_memory(id));
176 }
177
178 Err(MemoryRegistryError::RegistrationAfterBootstrap {
179 ranges: 0,
180 registrations: 1,
181 })
182 }
183
184 pub fn register_with_key(
186 id: u8,
187 _crate_name: &str,
188 _label: &str,
189 stable_key: &str,
190 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
191 if !MemoryRegistryRuntime::is_initialized() {
192 return Err(MemoryRegistryError::RegistryNotBootstrapped);
193 }
194
195 if let Some(entry) = MemoryRegistry::get(id)
196 && entry.stable_key == stable_key
197 {
198 return Ok(open_memory(id));
199 }
200
201 Err(MemoryRegistryError::RegistrationAfterBootstrap {
202 ranges: 0,
203 registrations: 1,
204 })
205 }
206
207 #[must_use]
209 pub fn inspect(id: u8) -> Option<MemoryInspection> {
210 let range = MemoryRegistry::export_range_entries()
211 .into_iter()
212 .find(|entry| entry.range.contains(id))?;
213 let entry = MemoryRegistry::get(id);
214 let label = entry.as_ref().map(|entry| entry.label.clone());
215 let stable_key = entry.as_ref().map(|entry| entry.stable_key.clone());
216 let schema_version = entry.as_ref().and_then(|entry| entry.schema_version);
217 let schema_fingerprint = entry.and_then(|entry| entry.schema_fingerprint);
218
219 Some(MemoryInspection {
220 id,
221 owner: range.owner,
222 range: range.range,
223 label,
224 stable_key,
225 schema_version,
226 schema_fingerprint,
227 })
228 }
229
230 #[must_use]
232 pub fn registered() -> Vec<RegisteredMemory> {
233 MemoryRegistry::export_ids_by_range()
234 .into_iter()
235 .flat_map(|snapshot| {
236 snapshot
237 .entries
238 .into_iter()
239 .map(move |(id, entry)| RegisteredMemory {
240 id,
241 owner: snapshot.owner.clone(),
242 range: snapshot.range,
243 label: entry.label,
244 stable_key: entry.stable_key,
245 schema_version: entry.schema_version,
246 schema_fingerprint: entry.schema_fingerprint,
247 })
248 })
249 .collect()
250 }
251
252 #[must_use]
254 pub fn registered_for_owner(owner: &str) -> Vec<RegisteredMemory> {
255 Self::registered()
256 .into_iter()
257 .filter(|entry| entry.owner == owner)
258 .collect()
259 }
260
261 #[must_use]
263 pub fn find(owner: &str, label: &str) -> Option<RegisteredMemory> {
264 Self::registered()
265 .into_iter()
266 .find(|entry| entry.owner == owner && entry.label == label)
267 }
268
269 pub fn ledger_snapshot() -> Result<LedgerSnapshot, MemoryRegistryError> {
271 #[cfg(target_arch = "wasm32")]
272 {
273 let snapshot = ledger::try_diagnostic_snapshot()?;
274 Ok(LedgerSnapshot {
275 authorities: snapshot.authorities,
276 ranges: snapshot.ranges,
277 entries: snapshot.entries,
278 })
279 }
280
281 #[cfg(not(target_arch = "wasm32"))]
282 {
283 Ok(LedgerSnapshot {
284 authorities: MemoryRegistry::try_export_historical_authorities()?,
285 ranges: MemoryRegistry::try_export_historical_ranges()?,
286 entries: MemoryRegistry::try_export_historical()?,
287 })
288 }
289 }
290}
291
292fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
294 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
295}
296
297#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::registry::{
305 MemoryRegistryError, defer_register, defer_reserve_range, reset_for_tests,
306 };
307
308 #[test]
309 fn register_memory_opens_validated_memory_for_reserved_slot() {
310 reset_for_tests();
311 defer_reserve_range("crate_a", 100, 102).expect("defer range");
312 defer_register(101, "crate_a", "slot").expect("defer register");
313 MemoryApi::bootstrap_pending().expect("bootstrap registry");
314
315 let _memory = MemoryApi::register(101, "crate_a", "slot").expect("open memory");
316 }
317
318 #[test]
319 fn register_memory_is_idempotent_for_same_entry() {
320 reset_for_tests();
321 defer_reserve_range("crate_a", 100, 102).expect("defer range");
322 defer_register(101, "crate_a", "slot").expect("defer register");
323 MemoryApi::bootstrap_pending().expect("bootstrap registry");
324 let _ = MemoryApi::register(101, "crate_a", "slot").expect("first open succeeds");
325
326 let _ = MemoryApi::register(101, "crate_a", "slot").expect("second open succeeds");
327 }
328
329 #[test]
330 fn register_with_key_opens_validated_explicit_key() {
331 reset_for_tests();
332 defer_reserve_range("crate_a", 100, 102).expect("defer range");
333 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
334 .expect("defer register");
335 MemoryApi::bootstrap_pending().expect("bootstrap registry");
336
337 let _memory = MemoryApi::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
338 .expect("open memory");
339 }
340
341 #[test]
342 fn declare_with_key_metadata_records_schema_metadata() {
343 reset_for_tests();
344 defer_reserve_range("crate_a", 100, 102).expect("defer range");
345 MemoryApi::declare_with_key_metadata(
346 101,
347 "crate_a",
348 "slot",
349 "app.crate_a.slot.v1",
350 Some(3),
351 Some("sha256:abc123"),
352 )
353 .expect("defer register");
354 MemoryApi::bootstrap_pending().expect("bootstrap registry");
355
356 let registered = MemoryApi::find("crate_a", "slot").expect("registered memory");
357 assert_eq!(registered.schema_version, Some(3));
358 assert_eq!(
359 registered.schema_fingerprint.as_deref(),
360 Some("sha256:abc123")
361 );
362
363 let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
364 let (_, entry) = snapshot
365 .entries
366 .into_iter()
367 .find(|(id, _)| *id == 101)
368 .expect("ledger entry");
369 assert_eq!(entry.schema_version, Some(3));
370 assert_eq!(entry.schema_fingerprint.as_deref(), Some("sha256:abc123"));
371 }
372
373 #[test]
374 fn declare_memory_does_not_open_before_bootstrap() {
375 reset_for_tests();
376
377 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
378 .expect("declare memory");
379
380 assert!(MemoryRegistry::get(101).is_none());
381 }
382
383 #[test]
384 fn declare_memory_rejects_after_bootstrap_seal() {
385 reset_for_tests();
386 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
387
388 let err = MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
389 .expect_err("late declaration should fail");
390 assert!(matches!(
391 err,
392 MemoryRegistryError::RegistrationAfterBootstrap {
393 ranges: 0,
394 registrations: 1,
395 }
396 ));
397 }
398
399 #[test]
400 fn register_memory_rejects_before_bootstrap_validation() {
401 reset_for_tests();
402
403 let Err(err) = MemoryApi::register(100, "crate_a", "slot") else {
404 panic!("opening before bootstrap must fail")
405 };
406 assert!(matches!(err, MemoryRegistryError::RegistryNotBootstrapped));
407 }
408
409 #[test]
410 fn register_memory_rejects_new_claim_after_bootstrap_seal() {
411 reset_for_tests();
412 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
413
414 let Err(err) = MemoryApi::register(101, "crate_a", "slot") else {
415 panic!("new registration after bootstrap must fail")
416 };
417 assert!(matches!(
418 err,
419 MemoryRegistryError::RegistrationAfterBootstrap {
420 ranges: 0,
421 registrations: 1,
422 }
423 ));
424 }
425
426 #[test]
427 fn bootstrap_pending_flushes_deferred_state() {
428 reset_for_tests();
429 defer_reserve_range("crate_a", 100, 102).expect("defer range");
430 defer_register(101, "crate_a", "slot").expect("defer register");
431
432 MemoryApi::bootstrap_pending().expect("bootstrap pending");
433
434 assert!(MemoryRegistry::export_ranges().contains(&(
435 "crate_a".to_string(),
436 MemoryRange {
437 start: 100,
438 end: 102
439 }
440 )));
441 let entries = MemoryRegistry::export();
442 assert!(entries.iter().any(|(id, entry)| {
443 *id == 101 && entry.crate_name == "crate_a" && entry.label == "slot"
444 }));
445 }
446
447 #[test]
448 fn inspect_memory_returns_reserved_owner_without_label() {
449 reset_for_tests();
450 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
451
452 let inspection = MemoryApi::inspect(101).expect("reserved slot should inspect");
453 assert_eq!(inspection.owner, "crate_a");
454 assert_eq!(
455 inspection.range,
456 MemoryRange {
457 start: 100,
458 end: 102
459 }
460 );
461 assert_eq!(inspection.label, None);
462 }
463
464 #[test]
465 fn inspect_memory_returns_registered_label() {
466 reset_for_tests();
467 defer_reserve_range("crate_a", 100, 102).expect("defer range");
468 defer_register(101, "crate_a", "slot").expect("defer register");
469 MemoryApi::bootstrap_pending().expect("bootstrap registry");
470
471 let inspection = MemoryApi::inspect(101).expect("registered slot should inspect");
472 assert_eq!(inspection.owner, "crate_a");
473 assert_eq!(
474 inspection.range,
475 MemoryRange {
476 start: 100,
477 end: 102
478 }
479 );
480 assert_eq!(inspection.label.as_deref(), Some("slot"));
481 assert_eq!(
482 inspection.stable_key.as_deref(),
483 Some("legacy.crate_a.slot.v1")
484 );
485 }
486
487 #[test]
488 fn inspect_memory_returns_none_for_unowned_id() {
489 reset_for_tests();
490 assert_eq!(MemoryApi::inspect(99), None);
491 }
492
493 #[test]
494 fn registered_memories_lists_registered_slots_with_owner_context() {
495 reset_for_tests();
496 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
497 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
498 defer_register(101, "crate_a", "slot_a").expect("defer register A");
499 defer_register(111, "crate_b", "slot_b").expect("defer register B");
500 MemoryApi::bootstrap_pending().expect("bootstrap registry");
501
502 let registrations = MemoryApi::registered();
503 assert_eq!(registrations.len(), 3);
504 assert!(registrations.contains(&RegisteredMemory {
505 id: 101,
506 owner: "crate_a".to_string(),
507 range: MemoryRange {
508 start: 100,
509 end: 102
510 },
511 label: "slot_a".to_string(),
512 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
513 schema_version: None,
514 schema_fingerprint: None,
515 }));
516 assert!(registrations.contains(&RegisteredMemory {
517 id: 111,
518 owner: "crate_b".to_string(),
519 range: MemoryRange {
520 start: 110,
521 end: 112
522 },
523 label: "slot_b".to_string(),
524 stable_key: "legacy.crate_b.slot_b.v1".to_string(),
525 schema_version: None,
526 schema_fingerprint: None,
527 }));
528 }
529
530 #[test]
531 fn registered_memories_for_owner_filters_to_owner() {
532 reset_for_tests();
533 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
534 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
535 defer_register(101, "crate_a", "slot_a").expect("defer register A");
536 defer_register(111, "crate_b", "slot_b").expect("defer register B");
537 MemoryApi::bootstrap_pending().expect("bootstrap registry");
538
539 let registrations = MemoryApi::registered_for_owner("crate_a");
540 assert_eq!(
541 registrations,
542 vec![RegisteredMemory {
543 id: 101,
544 owner: "crate_a".to_string(),
545 range: MemoryRange {
546 start: 100,
547 end: 102
548 },
549 label: "slot_a".to_string(),
550 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
551 schema_version: None,
552 schema_fingerprint: None,
553 }]
554 );
555 }
556
557 #[test]
558 fn find_registered_memory_returns_match_for_owner_and_label() {
559 reset_for_tests();
560 defer_reserve_range("crate_a", 100, 102).expect("defer range");
561 defer_register(101, "crate_a", "slot_a").expect("defer register");
562 MemoryApi::bootstrap_pending().expect("bootstrap registry");
563
564 let registration = MemoryApi::find("crate_a", "slot_a").expect("slot should exist");
565 assert_eq!(
566 registration,
567 RegisteredMemory {
568 id: 101,
569 owner: "crate_a".to_string(),
570 range: MemoryRange {
571 start: 100,
572 end: 102
573 },
574 label: "slot_a".to_string(),
575 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
576 schema_version: None,
577 schema_fingerprint: None,
578 }
579 );
580 }
581
582 #[test]
583 fn find_registered_memory_returns_none_when_missing() {
584 reset_for_tests();
585 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
586 assert_eq!(MemoryApi::find("crate_a", "slot_a"), None);
587 }
588
589 #[test]
590 fn ledger_snapshot_reads_historical_records() {
591 reset_for_tests();
592 defer_reserve_range("crate_a", 100, 102).expect("defer range");
593 defer_register(101, "crate_a", "slot").expect("defer register");
594 MemoryApi::bootstrap_pending().expect("bootstrap registry");
595
596 let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
597 assert!(snapshot.authorities.iter().any(|authority| {
598 authority.owner == "canic.framework"
599 && authority.range == MemoryRange { start: 0, end: 99 }
600 }));
601 assert!(snapshot.authorities.iter().any(|authority| {
602 authority.owner == "applications"
603 && authority.range
604 == MemoryRange {
605 start: 100,
606 end: 254,
607 }
608 }));
609 assert!(snapshot.ranges.iter().any(|(owner, range)| {
610 owner == "crate_a"
611 && *range
612 == MemoryRange {
613 start: 100,
614 end: 102,
615 }
616 }));
617 assert!(snapshot.entries.iter().any(|(id, entry)| {
618 *id == 101
619 && entry.crate_name == "crate_a"
620 && entry.label == "slot"
621 && entry.stable_key == "legacy.crate_a.slot.v1"
622 }));
623 }
624}