use crate::{Memory, WASM_PAGE_SIZE_BYTES};
use thiserror::Error;
pub const STABLE_CELL_MAGIC: &[u8; 8] = b"ICCELL01";
pub const STABLE_CELL_HEADER_SIZE: usize = 24;
pub const STABLE_CELL_VALUE_OFFSET: u64 = STABLE_CELL_HEADER_SIZE as u64;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum StableCellPayloadError {
#[error("stable-cell payload length {value_len} exceeds the caller's limit")]
TooLarge { value_len: u64 },
#[error("memory is not a Storage Cell")]
NotStableCell,
#[error("stable-cell payload length {value_len} exceeds available bytes {available_bytes}")]
InvalidLength {
value_len: u64,
available_bytes: u64,
},
}
pub fn stable_cell_payload_length<M: Memory>(
memory: &M,
maximum: usize,
) -> Result<usize, StableCellPayloadError> {
if memory.size() == 0 {
return Err(StableCellPayloadError::NotStableCell);
}
let mut header = [0; STABLE_CELL_HEADER_SIZE];
memory.read(0, &mut header);
if &header[..8] != STABLE_CELL_MAGIC || header[12..16].iter().any(|&byte| byte != 0) {
return Err(StableCellPayloadError::NotStableCell);
}
let value_len = u64::from(u32::from_le_bytes(header[8..12].try_into().unwrap()));
let available_bytes = memory.size().saturating_mul(WASM_PAGE_SIZE_BYTES);
let payload_capacity = available_bytes.saturating_sub(STABLE_CELL_VALUE_OFFSET);
if value_len > payload_capacity {
return Err(StableCellPayloadError::InvalidLength {
value_len,
available_bytes: payload_capacity,
});
}
if value_len > maximum as u64 {
return Err(StableCellPayloadError::TooLarge { value_len });
}
#[expect(
clippy::cast_possible_truncation,
reason = "the admitted caller ceiling is representable as usize"
)]
let value_len = value_len as usize;
Ok(value_len)
}