ic_storage/
stable_cell.rs1use crate::{Memory, WASM_PAGE_SIZE_BYTES};
2use thiserror::Error;
3
4pub const STABLE_CELL_MAGIC: &[u8; 3] = b"SCL";
6pub const STABLE_CELL_HEADER_SIZE: usize = 8;
8pub const STABLE_CELL_VALUE_OFFSET: u64 = STABLE_CELL_HEADER_SIZE as u64;
10
11#[expect(
12 clippy::empty_line_after_doc_comments,
13 reason = "Repository rustdoc style separates item docs from attributes."
14)]
15#[non_exhaustive]
22#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
23pub enum StableCellPayloadError {
24 #[error("stable-cell payload length {value_len} exceeds the caller's limit")]
26 TooLarge { value_len: u64 },
27 #[error("memory is not a Storage Cell")]
29 NotStableCell,
30 #[error("stable-cell payload length {value_len} exceeds available bytes {available_bytes}")]
32 InvalidLength {
33 value_len: u64,
35 available_bytes: u64,
37 },
38}
39
40pub fn stable_cell_payload_length<M: Memory>(
53 memory: &M,
54 maximum: usize,
55) -> Result<usize, StableCellPayloadError> {
56 if memory.size() == 0 {
57 return Err(StableCellPayloadError::NotStableCell);
58 }
59
60 let mut header = [0; STABLE_CELL_HEADER_SIZE];
61 memory.read(0, &mut header);
62 if &header[..3] != STABLE_CELL_MAGIC || header[3] != 1 {
63 return Err(StableCellPayloadError::NotStableCell);
64 }
65 let value_len = u64::from(u32::from_le_bytes(header[4..8].try_into().unwrap()));
66 let available_bytes = memory.size().saturating_mul(WASM_PAGE_SIZE_BYTES);
67 let payload_capacity = available_bytes.saturating_sub(STABLE_CELL_VALUE_OFFSET);
68 if value_len > payload_capacity {
69 return Err(StableCellPayloadError::InvalidLength {
70 value_len,
71 available_bytes: payload_capacity,
72 });
73 }
74 if value_len > maximum as u64 {
75 return Err(StableCellPayloadError::TooLarge { value_len });
76 }
77 #[expect(
78 clippy::cast_possible_truncation,
79 reason = "the admitted caller ceiling is representable as usize"
80 )]
81 let value_len = value_len as usize;
82 Ok(value_len)
83}