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