ic-memory 0.35.5

Durable stable-memory allocation governance for Internet Computer canisters
Documentation
mod id_range;
mod memory_manager;

pub use id_range::{MemoryManagerIdRange, MemoryManagerRangeError};
pub use memory_manager::LEDGER_SLOT;
pub use memory_manager::{
    IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID, MemoryManagerSlot,
    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
    validate_memory_manager_id,
};

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn memory_manager_default_constructor_rejects_sentinel() {
        let err =
            MemoryManagerSlot::new(MEMORY_MANAGER_INVALID_ID).expect_err("sentinel must fail");

        assert_eq!(
            err,
            MemoryManagerSlotError::InvalidMemoryManagerId {
                id: MEMORY_MANAGER_INVALID_ID
            }
        );
    }

    #[test]
    fn memory_manager_usable_domain_is_u8_with_255_sentinel() {
        assert_eq!(MEMORY_MANAGER_MIN_ID, 0);
        assert_eq!(MEMORY_MANAGER_MAX_ID, 254);
        assert_eq!(MEMORY_MANAGER_INVALID_ID, 255);
        assert_eq!(MEMORY_MANAGER_INVALID_ID, u8::MAX);

        MemoryManagerSlot::new(MEMORY_MANAGER_MAX_ID)
            .expect("254 is the last usable MemoryManager ID");
        MemoryManagerSlot::new(MEMORY_MANAGER_INVALID_ID)
            .expect_err("255 is always the unallocated sentinel");
    }

    #[test]
    fn slots_validate_ids_at_construction_and_decode() {
        for id in 0..MEMORY_MANAGER_INVALID_ID {
            let slot = MemoryManagerSlot::new(id).unwrap();
            assert_eq!(slot.id(), id);
            let json = serde_json::to_value(&slot).unwrap();
            assert_eq!(
                json,
                serde_json::json!({ "slot": { "MemoryManagerId": id } })
            );
            assert_eq!(
                serde_json::from_value::<MemoryManagerSlot>(json).unwrap(),
                slot
            );
            let bytes = crate::test_cbor::to_vec(&slot).unwrap();
            assert_eq!(
                crate::cbor::from_slice_exact::<MemoryManagerSlot>(&bytes).unwrap(),
                slot
            );
        }
        for json in [
            serde_json::json!({ "slot": { "MemoryManagerId": 255 } }),
            serde_json::json!({ "slot": { "MemoryManagerId": 256 } }),
            serde_json::json!({ "slot": { "MemoryManagerId": -1 } }),
            serde_json::json!({ "slot": { "MemoryManagerId": 1 }, "extra": true }),
            serde_json::json!({ "slot": { "Other": 1 } }),
            serde_json::json!({ "slot": null }),
            serde_json::json!({}),
        ] {
            assert!(serde_json::from_value::<MemoryManagerSlot>(json.clone()).is_err());
            let bytes = crate::test_cbor::to_vec(&json).unwrap();
            assert!(crate::cbor::from_slice_exact::<MemoryManagerSlot>(&bytes).is_err());
        }
    }

    #[test]
    fn memory_manager_range_accepts_usable_ranges() {
        let range = MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
            .expect("usable full range");

        assert!(range.contains(MEMORY_MANAGER_MIN_ID));
        assert!(range.contains(MEMORY_MANAGER_MAX_ID));
        assert!(!range.contains(MEMORY_MANAGER_INVALID_ID));
    }

    #[test]
    fn memory_manager_range_all_usable_matches_usable_bounds() {
        assert_eq!(
            MemoryManagerIdRange::all_usable(),
            MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
                .expect("usable full range")
        );
    }

    #[test]
    fn memory_manager_governance_range_is_owned_by_ic_memory() {
        let range = memory_manager_governance_range();

        assert_eq!(range.start(), MEMORY_MANAGER_MIN_ID);
        assert_eq!(MEMORY_MANAGER_LEDGER_ID, range.start());
        assert!(range.contains(MEMORY_MANAGER_LEDGER_ID));
        assert!(is_ic_memory_stable_key(IC_MEMORY_LEDGER_STABLE_KEY));
        assert_eq!(IC_MEMORY_AUTHORITY_OWNER, "ic-memory");
    }

    #[test]
    fn memory_manager_range_rejects_reversed_bounds() {
        for (start, end) in [(10, 9), (MEMORY_MANAGER_INVALID_ID, MEMORY_MANAGER_MAX_ID)] {
            assert_eq!(
                MemoryManagerIdRange::new(start, end).unwrap_err(),
                MemoryManagerRangeError::InvalidRange { start, end }
            );
        }
    }

    #[test]
    fn memory_manager_range_rejects_sentinel_bounds() {
        for start in [240, MEMORY_MANAGER_INVALID_ID] {
            assert_eq!(
                MemoryManagerIdRange::new(start, MEMORY_MANAGER_INVALID_ID).unwrap_err(),
                MemoryManagerRangeError::InvalidMemoryManagerId {
                    id: MEMORY_MANAGER_INVALID_ID
                }
            );
        }
    }

    #[test]
    fn memory_manager_range_decode_enforces_usable_bounds() {
        for (start, end) in [(0, 0), (0, 254), (100, 109), (254, 254)] {
            let range = MemoryManagerIdRange::new(start, end).unwrap();
            let json = serde_json::to_value(range).unwrap();
            assert_eq!(json, serde_json::json!({ "start": start, "end": end }));
            assert_eq!(
                serde_json::from_value::<MemoryManagerIdRange>(json).unwrap(),
                range
            );
            let bytes = crate::test_cbor::to_vec(&range).unwrap();
            assert_eq!(
                crate::test_cbor::from_slice::<MemoryManagerIdRange>(&bytes).unwrap(),
                range
            );
        }
        let range = MemoryManagerIdRange::new(100, 109).unwrap();
        assert_eq!(
            crate::test_cbor::to_vec(&range).unwrap(),
            b"\xa2\x65start\x18\x64\x63end\x18\x6d"
        );

        for json in [
            serde_json::json!({ "start": 100, "end": 99 }),
            serde_json::json!({ "start": 100, "end": 255 }),
            serde_json::json!({ "start": 255, "end": 255 }),
            serde_json::json!({ "start": 0, "end": 256 }),
            serde_json::json!({ "start": 0 }),
            serde_json::json!({ "start": 0, "end": 1, "extra": true }),
        ] {
            assert!(serde_json::from_value::<MemoryManagerIdRange>(json.clone()).is_err());
            let bytes = crate::test_cbor::to_vec(&json).unwrap();
            assert!(crate::test_cbor::from_slice::<MemoryManagerIdRange>(&bytes).is_err());
        }
    }
}