essential_state_read_vm/
state_memory.rs

1//! State slot operation implementations and related items.
2
3use core::ops::Range;
4
5use essential_constraint_vm::{error::StackError, Stack};
6
7use crate::{asm::Word, OpSyncResult, StateMemoryError, StateMemoryResult};
8
9#[cfg(test)]
10mod tests;
11
12/// A type representing the VM's mutable state slots.
13///
14/// `StateSlots` is a thin wrapper around a `Vec<Vec<Word>>`. The `Vec` mutable methods
15/// are predicateionally not exposed in order to maintain close control over capacity.
16#[derive(Clone, Debug, Default, PartialEq)]
17pub struct StateMemory(Vec<Vec<Word>>);
18
19impl StateMemory {
20    /// The maximum number of slots that can be allocated.
21    pub const SLOT_LIMIT: usize = 4096;
22
23    /// The maximum number of words stored in a single value.
24    pub const VALUE_LIMIT: usize = 4096;
25
26    /// Allocate new slots to the end of the vector.
27    pub fn alloc_slots(&mut self, size: usize) -> StateMemoryResult<()> {
28        if self.len() + size > Self::SLOT_LIMIT {
29            return Err(StateMemoryError::Overflow);
30        }
31        self.0.resize_with(self.len() + size, Default::default);
32        Ok(())
33    }
34
35    /// Load a value at the given slot index.
36    pub fn load(&self, slot_ix: usize, range: Range<usize>) -> StateMemoryResult<&[Word]> {
37        let slot = self
38            .get(slot_ix)
39            .ok_or(StateMemoryError::IndexOutOfBounds)?
40            .get(range)
41            .ok_or(StateMemoryError::IndexOutOfBounds)?;
42        Ok(slot)
43    }
44
45    /// Store the given value at the given slot `index`.
46    pub fn store(
47        &mut self,
48        slot_ix: usize,
49        value_ix: usize,
50        data: Vec<Word>,
51    ) -> StateMemoryResult<()> {
52        let slot = self
53            .0
54            .get_mut(slot_ix)
55            .ok_or(StateMemoryError::IndexOutOfBounds)?;
56
57        if value_ix.saturating_add(data.len()) > Self::VALUE_LIMIT {
58            return Err(StateMemoryError::Overflow);
59        }
60
61        let (_, rem) = slot
62            .split_at_mut_checked(value_ix)
63            .ok_or(StateMemoryError::IndexOutOfBounds)?;
64        let len = rem.len().min(data.len());
65        rem[..len].copy_from_slice(&data[..len]);
66        if len < data.len() {
67            slot.extend_from_slice(&data[len..]);
68        }
69        Ok(())
70    }
71
72    /// Truncate the value at the given slot index.
73    pub fn truncate(&mut self, slot_ix: usize, len: usize) -> StateMemoryResult<()> {
74        self.0
75            .get_mut(slot_ix)
76            .ok_or(StateMemoryError::IndexOutOfBounds)?
77            .truncate(len);
78        Ok(())
79    }
80
81    /// Get the length of a value at the given slot index.
82    pub fn value_len(&self, slot_ix: usize) -> StateMemoryResult<usize> {
83        let slot = self
84            .0
85            .get(slot_ix)
86            .ok_or(StateMemoryError::IndexOutOfBounds)?;
87        Ok(slot.len())
88    }
89
90    /// Store the given values starting at the given slot `index`.
91    pub fn store_slots_range(
92        &mut self,
93        index: usize,
94        values: Vec<Vec<Word>>,
95    ) -> StateMemoryResult<()> {
96        if values.iter().any(|val| val.len() > Self::VALUE_LIMIT) {
97            return Err(StateMemoryError::Overflow);
98        }
99
100        let slots = self
101            .0
102            .get_mut(index..(index + values.len()))
103            .ok_or(StateMemoryError::IndexOutOfBounds)?;
104
105        for (slot, value) in slots.iter_mut().zip(values) {
106            *slot = value;
107        }
108        Ok(())
109    }
110}
111
112impl core::ops::Deref for StateMemory {
113    type Target = Vec<Vec<Word>>;
114    fn deref(&self) -> &Self::Target {
115        &self.0
116    }
117}
118
119impl From<StateMemory> for Vec<Vec<Word>> {
120    fn from(state_slots: StateMemory) -> Self {
121        state_slots.0
122    }
123}
124
125/// `StateMemory::AllocSlots` operation.
126pub fn alloc_slots(stack: &mut Stack, slots: &mut StateMemory) -> OpSyncResult<()> {
127    let size = stack.pop()?;
128    let size = usize::try_from(size).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
129    slots.alloc_slots(size)?;
130    Ok(())
131}
132
133/// `StateMemory::Length` operation.
134pub fn length(stack: &mut Stack, slots: &StateMemory) -> OpSyncResult<()> {
135    let len = Word::try_from(slots.len()).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
136    stack.push(len)?;
137    Ok(())
138}
139
140/// `StateMemory::ValueLen` operation.
141pub fn value_len(stack: &mut Stack, slots: &StateMemory) -> OpSyncResult<()> {
142    let slot = stack.pop()?;
143    let slot = usize::try_from(slot).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
144    let len =
145        Word::try_from(slots.value_len(slot)?).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
146    stack.push(len)?;
147    Ok(())
148}
149
150/// `StateMemory::Truncate` operation.
151pub fn truncate(stack: &mut Stack, slots: &mut StateMemory) -> OpSyncResult<()> {
152    let len = stack.pop()?;
153    let index = stack.pop()?;
154    let len = usize::try_from(len).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
155    let index = usize::try_from(index).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
156    slots.truncate(index, len)?;
157    Ok(())
158}
159
160/// `StateMemory::Load` operation.
161pub fn load(stack: &mut Stack, slots: &StateMemory) -> OpSyncResult<()> {
162    let len = stack.pop()?;
163    let value_ix = stack.pop()?;
164    let slot_ix = stack.pop()?;
165    let slot_ix = usize::try_from(slot_ix).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
166    let range = range_from_start_len(value_ix, len).ok_or(StateMemoryError::IndexOutOfBounds)?;
167    let value = slots.load(slot_ix, range)?;
168    stack.extend(value.iter().copied())?;
169    Ok(())
170}
171
172/// `StateMemory::Store` operation.
173pub fn store(stack: &mut Stack, slots: &mut StateMemory) -> OpSyncResult<()> {
174    let data = stack.pop_len_words::<_, _, StackError>(|value| Ok(value.to_vec()))?;
175    let value_ix = stack.pop()?;
176    let value_ix = usize::try_from(value_ix).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
177    let slot_ix = stack.pop()?;
178    let slot_ix = usize::try_from(slot_ix).map_err(|_| StateMemoryError::IndexOutOfBounds)?;
179    slots.store(slot_ix, value_ix, data)?;
180    Ok(())
181}
182
183fn range_from_start_len(start: Word, len: Word) -> Option<std::ops::Range<usize>> {
184    let start = usize::try_from(start).ok()?;
185    let len = usize::try_from(len).ok()?;
186    let end = start.checked_add(len)?;
187    Some(start..end)
188}