ic_storage/
stable_cell.rs1use crate::{Memory, WASM_PAGE_SIZE_BYTES};
2use thiserror::Error;
3
4pub const STABLE_CELL_MAGIC: &[u8; 8] = b"ICCELL01";
6pub const STABLE_CELL_HEADER_SIZE: usize = 24;
8pub const STABLE_CELL_VALUE_OFFSET: u64 = STABLE_CELL_HEADER_SIZE as u64;
10
11#[non_exhaustive]
18#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
19pub enum StableCellPayloadError {
20 #[error("stable-cell payload length {value_len} exceeds the caller's limit")]
22 TooLarge { value_len: u64 },
23 #[error("memory is not a Storage Cell")]
25 NotStableCell,
26 #[error("stable-cell payload length {value_len} exceeds available bytes {available_bytes}")]
28 InvalidLength {
29 value_len: u64,
31 available_bytes: u64,
33 },
34}
35
36pub fn stable_cell_payload_length<M: Memory>(
46 memory: &M,
47 maximum: usize,
48) -> Result<usize, StableCellPayloadError> {
49 if memory.size() == 0 {
50 return Err(StableCellPayloadError::NotStableCell);
51 }
52
53 let mut header = [0; STABLE_CELL_HEADER_SIZE];
54 memory.read(0, &mut header);
55 if &header[..8] != STABLE_CELL_MAGIC || header[12..16].iter().any(|&byte| byte != 0) {
56 return Err(StableCellPayloadError::NotStableCell);
57 }
58 let value_len = u64::from(u32::from_le_bytes(header[8..12].try_into().unwrap()));
59 let available_bytes = memory.size().saturating_mul(WASM_PAGE_SIZE_BYTES);
60 let payload_capacity = available_bytes.saturating_sub(STABLE_CELL_VALUE_OFFSET);
61 if value_len > payload_capacity {
62 return Err(StableCellPayloadError::InvalidLength {
63 value_len,
64 available_bytes: payload_capacity,
65 });
66 }
67 if value_len > maximum as u64 {
68 return Err(StableCellPayloadError::TooLarge { value_len });
69 }
70 #[expect(
71 clippy::cast_possible_truncation,
72 reason = "the admitted caller ceiling is representable as usize"
73 )]
74 let value_len = value_len as usize;
75 Ok(value_len)
76}