ic_stable_structures/
cell.rs

1//! A serializable value stored in the stable memory.
2use crate::storable::Storable;
3use crate::{read_to_vec, Memory, WASM_PAGE_SIZE};
4use std::borrow::{Borrow, Cow};
5use std::fmt;
6
7#[cfg(test)]
8mod tests;
9
10const MAGIC: &[u8; 3] = b"SCL"; // short for "stable cell"
11const HEADER_V1_SIZE: u64 = 8;
12const LAYOUT_VERSION: u8 = 1;
13
14// NOTE: the size of this structure should be equal to [HEADER_V1_SIZE].
15// NOTE: if you have to add more fields, you need to increase the version and handle decoding of
16// previous versions in `Cell::read_header`.
17//
18// # V1 layout
19//
20// -------------------------------
21// Magic "SCL"         ↕ 3 bytes
22// -------------------------------
23// Layout version      ↕ 1 byte
24// -------------------------------
25// Value length = N    ↕ 4 bytes
26// -------------------------------
27// <encoded value>     ↕ N bytes
28// -------------------------------
29#[derive(Debug)]
30struct HeaderV1 {
31    magic: [u8; 3],
32    version: u8,
33    value_length: u32,
34}
35
36/// Indicates a failure to initialize a Cell.
37#[derive(Debug, PartialEq, Eq)]
38pub enum InitError {
39    /// The version of the library does not support version of the cell layout encoded in the
40    /// memory.
41    IncompatibleVersion {
42        last_supported_version: u8,
43        decoded_version: u8,
44    },
45    /// The initial value was to large to fit into the memory.
46    ValueTooLarge { value_size: u64 },
47}
48
49impl fmt::Display for InitError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            InitError::IncompatibleVersion {
53                last_supported_version,
54                decoded_version,
55            } => write!(
56                f,
57                "Incompatible version: last supported version is {}, but the memory contains version {}",
58                last_supported_version, decoded_version
59            ),
60            InitError::ValueTooLarge { value_size } => write!(
61                f,
62                "The initial value is too large to fit into the memory: {} bytes",
63                value_size
64            ),
65        }
66    }
67}
68
69/// Indicates a failure to set cell's value.
70#[derive(Debug, PartialEq, Eq)]
71pub enum ValueError {
72    /// The value is too large to fit into the cell memory.
73    ValueTooLarge { value_size: u64 },
74}
75
76impl From<ValueError> for InitError {
77    fn from(e: ValueError) -> InitError {
78        match e {
79            ValueError::ValueTooLarge { value_size } => InitError::ValueTooLarge { value_size },
80        }
81    }
82}
83
84/// Represents a serializable value stored in the stable memory.
85/// It has semantics similar to "stable variables" in Motoko and share the same limitations.
86/// The main difference is that Cell writes its value to the memory on each assignment, not just in
87/// upgrade hooks.
88/// You should use cells only for small (up to a few MiB) values to keep upgrades safe.
89///
90/// Cell is a good choice for small read-only configuration values set once on canister installation
91/// and rarely updated.
92pub struct Cell<T: Storable, M: Memory> {
93    memory: M,
94    value: T,
95}
96
97impl<T: Storable, M: Memory> Cell<T, M> {
98    /// Creates a new cell in the specified memory, overwriting the previous contents of the memory.
99    pub fn new(memory: M, value: T) -> Result<Self, ValueError> {
100        Self::flush_value(&memory, &value)?;
101        Ok(Self { memory, value })
102    }
103
104    /// Initializes the value of the cell based on the contents of the `memory`.
105    /// If the memory already contains a cell, initializes the cell with the decoded value.
106    /// Otherwise, sets the cell value to `default_value` and writes it to the memory.
107    pub fn init(memory: M, default_value: T) -> Result<Self, InitError> {
108        if memory.size() == 0 {
109            return Ok(Self::new(memory, default_value)?);
110        }
111
112        let header = Self::read_header(&memory);
113
114        if &header.magic != MAGIC {
115            return Ok(Self::new(memory, default_value)?);
116        }
117
118        if header.version != LAYOUT_VERSION {
119            return Err(InitError::IncompatibleVersion {
120                last_supported_version: LAYOUT_VERSION,
121                decoded_version: header.version,
122            });
123        }
124
125        Ok(Self {
126            value: Self::read_value(&memory, header.value_length),
127            memory,
128        })
129    }
130
131    /// Reads and decodes the value of specified length.
132    ///
133    /// PRECONDITION: memory is large enough to contain the value.
134    fn read_value(memory: &M, len: u32) -> T {
135        let mut buf = vec![];
136        read_to_vec(memory, HEADER_V1_SIZE.into(), &mut buf, len as usize);
137        T::from_bytes(Cow::Owned(buf))
138    }
139
140    /// Reads the header from the specified memory.
141    ///
142    /// PRECONDITION: memory.size() > 0
143    fn read_header(memory: &M) -> HeaderV1 {
144        let mut magic: [u8; 3] = [0; 3];
145        let mut version: [u8; 1] = [0; 1];
146        let mut len: [u8; 4] = [0; 4];
147
148        memory.read(0, &mut magic);
149        memory.read(3, &mut version);
150        memory.read(4, &mut len);
151
152        HeaderV1 {
153            magic,
154            version: version[0],
155            value_length: u32::from_le_bytes(len),
156        }
157    }
158
159    /// Returns the current value in the cell.
160    pub fn get(&self) -> &T {
161        &self.value
162    }
163
164    /// Returns the underlying memory.
165    pub fn into_memory(self) -> M {
166        self.memory
167    }
168
169    /// Updates the current value in the cell.
170    /// If the new value is too large to fit into the memory, the value in the cell does not
171    /// change.
172    pub fn set(&mut self, value: T) -> Result<T, ValueError> {
173        Self::flush_value(&self.memory, &value)?;
174        Ok(std::mem::replace(&mut self.value, value))
175    }
176
177    /// Writes the value to the memory, growing the memory size if needed.
178    fn flush_value(memory: &M, value: &T) -> Result<(), ValueError> {
179        let encoded = value.to_bytes();
180        let bytes: &[u8] = encoded.borrow();
181        let len = bytes.len();
182        if len > u32::MAX as usize {
183            return Err(ValueError::ValueTooLarge {
184                value_size: len as u64,
185            });
186        }
187        let size = memory.size();
188        let available_space = size * WASM_PAGE_SIZE;
189        if len as u64 > available_space.saturating_sub(HEADER_V1_SIZE) || size == 0 {
190            let grow_by =
191                (len as u64 + HEADER_V1_SIZE + WASM_PAGE_SIZE - size * WASM_PAGE_SIZE - 1)
192                    / WASM_PAGE_SIZE;
193            if memory.grow(grow_by) < 0 {
194                return Err(ValueError::ValueTooLarge {
195                    value_size: len as u64,
196                });
197            }
198        }
199
200        debug_assert!(memory.size() * WASM_PAGE_SIZE >= len as u64 + HEADER_V1_SIZE);
201
202        let version = [LAYOUT_VERSION; 1];
203        memory.write(0, MAGIC);
204        memory.write(3, &version);
205        memory.write(4, &(len as u32).to_le_bytes());
206        memory.write(HEADER_V1_SIZE, bytes);
207        Ok(())
208    }
209}