essential_constraint_vm/
access.rs

1//! Access operation implementations.
2
3use crate::{
4    cached::LazyCache,
5    error::{AccessError, MissingAccessArgError},
6    repeat::Repeat,
7    sets::encode_set,
8    types::convert::bool_from_word,
9    OpResult, Stack,
10};
11use essential_constraint_asm::Word;
12use essential_types::{
13    convert::{bytes_from_word, u8_32_from_word_4, word_4_from_u8_32},
14    solution::{Solution, SolutionData, SolutionDataIndex},
15    Key, Value,
16};
17use std::collections::HashSet;
18
19#[cfg(test)]
20mod dec_vars;
21#[cfg(test)]
22mod num_slots;
23#[cfg(test)]
24mod predicate_exists;
25#[cfg(test)]
26mod state;
27#[cfg(test)]
28mod test_utils;
29#[cfg(test)]
30mod tests;
31
32/// All necessary solution data and state access required to check an individual predicate.
33#[derive(Clone, Copy, Debug)]
34pub struct Access<'a> {
35    /// All necessary solution data access required to check an individual predicate.
36    pub solution: SolutionAccess<'a>,
37    /// The pre and post mutation state slot values for the predicate being solved.
38    pub state_slots: StateSlots<'a>,
39}
40
41/// All necessary solution data access required to check an individual predicate.
42#[derive(Clone, Copy, Debug)]
43pub struct SolutionAccess<'a> {
44    /// The input data for each predicate being solved within the solution.
45    ///
46    /// We require *all* predicate solution data in order to handle checking
47    /// predicate exists.
48    pub data: &'a [SolutionData],
49    /// Checking is performed for one predicate at a time. This index refers to
50    /// the checked predicate's associated solution data within `data`.
51    pub index: usize,
52    /// The keys being proposed for mutation for the predicate.
53    pub mutable_keys: &'a HashSet<&'a [Word]>,
54}
55
56/// The pre and post mutation state slot values for the predicate being solved.
57#[derive(Clone, Copy, Debug)]
58pub struct StateSlots<'a> {
59    /// Predicate state slot values before the solution's mutations are applied.
60    pub pre: &'a StateSlotSlice,
61    /// Predicate state slot values after the solution's mutations are applied.
62    pub post: &'a StateSlotSlice,
63}
64
65/// The state slots declared within the predicate.
66pub type StateSlotSlice = [Vec<Word>];
67
68impl<'a> SolutionAccess<'a> {
69    /// A shorthand for constructing a `SolutionAccess` instance for checking
70    /// the predicate at the given index within the given solution.
71    ///
72    /// This constructor assumes that the given mutable keys contract is correct
73    /// for this solution. It is not checked by this function for performance.
74    pub fn new(
75        solution: &'a Solution,
76        predicate_index: SolutionDataIndex,
77        mutable_keys: &'a HashSet<&[Word]>,
78    ) -> Self {
79        Self {
80            data: &solution.data,
81            index: predicate_index.into(),
82            mutable_keys,
83        }
84    }
85
86    /// The solution data associated with the predicate currently being checked.
87    ///
88    /// **Panics** in the case that `self.index` is out of range of the `self.data` slice.
89    pub fn this_data(&self) -> &SolutionData {
90        self.data
91            .get(self.index)
92            .expect("predicate index out of range of solution data")
93    }
94}
95
96impl<'a> StateSlots<'a> {
97    /// Empty state slots.
98    pub const EMPTY: Self = Self {
99        pre: &[],
100        post: &[],
101    };
102}
103
104/// A helper for collecting all mutable keys that are proposed for mutation for
105/// the predicate at the given index.
106///
107/// Specifically, assists in calculating the `mut_keys_len` for
108/// `SolutionAccess`, as this is equal to the `.count()` of the returned iterator.
109///
110/// **Note:** In the case that the given solution is invalid and contains multiple
111/// mutations to the same key, the same key will be yielded multiple times.
112pub fn mut_keys(
113    solution: &Solution,
114    predicate_index: SolutionDataIndex,
115) -> impl Iterator<Item = &Key> {
116    solution.data[predicate_index as usize]
117        .state_mutations
118        .iter()
119        .map(|m| &m.key)
120}
121
122/// Get the mutable keys as slices
123pub fn mut_keys_slices(
124    solution: &Solution,
125    predicate_index: SolutionDataIndex,
126) -> impl Iterator<Item = &[Word]> {
127    solution.data[predicate_index as usize]
128        .state_mutations
129        .iter()
130        .map(|m| m.key.as_ref())
131}
132
133/// Get the contract of mutable keys for this predicate.
134pub fn mut_keys_set(solution: &Solution, predicate_index: SolutionDataIndex) -> HashSet<&[Word]> {
135    mut_keys_slices(solution, predicate_index).collect()
136}
137
138/// `Access::DecisionVar` implementation.
139pub(crate) fn decision_var(this_decision_vars: &[Value], stack: &mut Stack) -> OpResult<()> {
140    let len = stack.pop().map_err(|_| MissingAccessArgError::DecVarLen)?;
141    let value_ix = stack
142        .pop()
143        .map_err(|_| MissingAccessArgError::DecVarValueIx)?;
144    let slot_ix = stack
145        .pop()
146        .map_err(|_| MissingAccessArgError::DecVarSlotIx)?;
147    let slot_ix =
148        usize::try_from(slot_ix).map_err(|_| AccessError::DecisionSlotIxOutOfBounds(slot_ix))?;
149    let range = range_from_start_len(value_ix, len).ok_or(AccessError::InvalidAccessRange)?;
150    let words = resolve_decision_var_range(this_decision_vars, slot_ix, range)?;
151    stack.extend(words.iter().copied())?;
152    Ok(())
153}
154
155/// `Access::DecisionVarLen` implementation.
156pub(crate) fn decision_var_len(this_decision_vars: &[Value], stack: &mut Stack) -> OpResult<()> {
157    let slot_ix = stack
158        .pop()
159        .map_err(|_| MissingAccessArgError::DecVarSlotIx)?;
160    let slot_ix =
161        usize::try_from(slot_ix).map_err(|_| AccessError::DecisionSlotIxOutOfBounds(slot_ix))?;
162    let len = resolve_decision_var_len(this_decision_vars, slot_ix)?;
163    let w = Word::try_from(len).map_err(|_| AccessError::DecisionLengthTooLarge(len))?;
164    stack
165        .push(w)
166        .expect("Can't fail because 1 is popped and 1 is pushed");
167    Ok(())
168}
169
170/// `Access::MutKeys` implementation.
171pub(crate) fn push_mut_keys(solution: SolutionAccess, stack: &mut Stack) -> OpResult<()> {
172    encode_set(
173        solution.mutable_keys.iter().map(|k| k.iter().copied()),
174        stack,
175    )
176}
177
178/// `Access::State` implementation.
179pub(crate) fn state(slots: StateSlots, stack: &mut Stack) -> OpResult<()> {
180    let delta = stack.pop().map_err(|_| MissingAccessArgError::StateDelta)?;
181    let len = stack.pop().map_err(|_| MissingAccessArgError::StateLen)?;
182    let value_ix = stack
183        .pop()
184        .map_err(|_| MissingAccessArgError::StateValueIx)?;
185    let slot_ix = stack
186        .pop()
187        .map_err(|_| MissingAccessArgError::StateSlotIx)?;
188    let values = state_slot_value_range(slots, slot_ix, value_ix, len, delta)?;
189    stack.extend(values.iter().copied())?;
190    Ok(())
191}
192
193/// `Access::StateLen` implementation.
194pub(crate) fn state_len(slots: StateSlots, stack: &mut Stack) -> OpResult<()> {
195    let delta = stack.pop().map_err(|_| MissingAccessArgError::StateDelta)?;
196    let slot_ix = stack
197        .pop()
198        .map_err(|_| MissingAccessArgError::StateSlotIx)?;
199    let slot = state_slot(slots, slot_ix, delta)?;
200    let len =
201        Word::try_from(slot.len()).map_err(|_| AccessError::StateValueTooLarge(slot.len()))?;
202    stack
203        .push(len)
204        .expect("Can't fail because 2 are popped and 1 is pushed");
205    Ok(())
206}
207
208/// `Access::ThisAddress` implementation.
209pub(crate) fn this_address(data: &SolutionData, stack: &mut Stack) -> OpResult<()> {
210    let words = word_4_from_u8_32(data.predicate_to_solve.predicate.0);
211    stack.extend(words)?;
212    Ok(())
213}
214
215/// `Access::ThisContractAddress` implementation.
216pub(crate) fn this_contract_address(data: &SolutionData, stack: &mut Stack) -> OpResult<()> {
217    let words = word_4_from_u8_32(data.predicate_to_solve.contract.0);
218    stack.extend(words)?;
219    Ok(())
220}
221
222pub(crate) fn repeat_counter(stack: &mut Stack, repeat: &Repeat) -> OpResult<()> {
223    let counter = repeat.counter()?;
224    Ok(stack.push(counter)?)
225}
226
227/// Implementation of the `Access::NumSlots` operation.
228pub(crate) fn num_slots(
229    stack: &mut Stack,
230    state_slots: &StateSlots<'_>,
231    decision_variables: &[Value],
232) -> OpResult<()> {
233    const DEC_VAR_SLOTS: Word = 0;
234    const PRE_STATE_SLOTS: Word = 1;
235    const POST_STATE_SLOTS: Word = 2;
236
237    let which_slots = stack.pop()?;
238
239    match which_slots {
240        DEC_VAR_SLOTS => {
241            let num_slots = Word::try_from(decision_variables.len())
242                .map_err(|_| AccessError::SlotsLengthTooLarge(decision_variables.len()))?;
243            stack.push(num_slots)?;
244        }
245        PRE_STATE_SLOTS => {
246            let num_slots = Word::try_from(state_slots.pre.len())
247                .map_err(|_| AccessError::SlotsLengthTooLarge(state_slots.pre.len()))?;
248            stack.push(num_slots)?;
249        }
250        POST_STATE_SLOTS => {
251            let num_slots = Word::try_from(state_slots.post.len())
252                .map_err(|_| AccessError::SlotsLengthTooLarge(state_slots.post.len()))?;
253            stack.push(num_slots)?;
254        }
255        _ => return Err(AccessError::InvalidSlotType(which_slots).into()),
256    }
257    Ok(())
258}
259
260/// Resolve a range of words at a decision variable slot.
261///
262/// Errors if the solution data or decision var indices are out of bounds.
263pub(crate) fn resolve_decision_var_range(
264    decision_variables: &[Value],
265    slot_ix: usize,
266    value_range_ix: core::ops::Range<usize>,
267) -> Result<&[Word], AccessError> {
268    decision_variables
269        .get(slot_ix)
270        .ok_or(AccessError::DecisionSlotIxOutOfBounds(slot_ix as Word))?
271        .get(value_range_ix.clone())
272        .ok_or(AccessError::DecisionValueRangeOutOfBounds(
273            value_range_ix.start as Word,
274            value_range_ix.end as Word,
275        ))
276}
277
278/// Resolve the length of decision variable slot.
279///
280/// Errors if the solution data or decision var indices are out of bounds.
281pub(crate) fn resolve_decision_var_len(
282    decision_variables: &[Value],
283    slot_ix: usize,
284) -> Result<usize, AccessError> {
285    decision_variables
286        .get(slot_ix)
287        .map(|slot| slot.len())
288        .ok_or(AccessError::DecisionSlotIxOutOfBounds(slot_ix as Word))
289}
290
291pub(crate) fn predicate_exists(
292    stack: &mut Stack,
293    data: &[SolutionData],
294    cache: &LazyCache,
295) -> OpResult<()> {
296    let hash = u8_32_from_word_4(stack.pop4()?);
297    let found = cache.get_dec_var_hashes(data).contains(&hash);
298    stack.push(found as Word)?;
299    Ok(())
300}
301
302pub(crate) fn init_predicate_exists(
303    data: &[SolutionData],
304) -> impl Iterator<Item = essential_types::Hash> + '_ {
305    data.iter().map(|d| {
306        let data = d
307            .decision_variables
308            .iter()
309            .flat_map(|slot| {
310                Some(slot.len() as Word)
311                    .into_iter()
312                    .chain(slot.iter().cloned())
313            })
314            .chain(word_4_from_u8_32(d.predicate_to_solve.contract.0))
315            .chain(word_4_from_u8_32(d.predicate_to_solve.predicate.0))
316            .flat_map(bytes_from_word)
317            .collect::<Vec<_>>();
318        sha256(&data)
319    })
320}
321
322fn sha256(bytes: &[u8]) -> [u8; 32] {
323    use sha2::{Digest, Sha256};
324    let mut hasher = Sha256::new();
325    hasher.update(bytes);
326    let result: [u8; 32] = hasher.finalize().into();
327    result
328}
329
330fn state_slot(slots: StateSlots, slot_ix: Word, delta: Word) -> OpResult<&Vec<Word>> {
331    let delta = bool_from_word(delta).ok_or(AccessError::InvalidStateSlotDelta(delta))?;
332    let slots = state_slots_from_delta(slots, delta);
333    let ix = usize::try_from(slot_ix).map_err(|_| AccessError::StateSlotIxOutOfBounds(slot_ix))?;
334    let slot = slots
335        .get(ix)
336        .ok_or(AccessError::StateSlotIxOutOfBounds(slot_ix))?;
337    Ok(slot)
338}
339
340fn state_slot_value_range(
341    slots: StateSlots,
342    slot_ix: Word,
343    value_ix: Word,
344    len: Word,
345    delta: Word,
346) -> OpResult<&[Word]> {
347    let delta = bool_from_word(delta).ok_or(AccessError::InvalidStateSlotDelta(delta))?;
348    let slots = state_slots_from_delta(slots, delta);
349    let slot_ix =
350        usize::try_from(slot_ix).map_err(|_| AccessError::StateSlotIxOutOfBounds(slot_ix))?;
351    let range = range_from_start_len(value_ix, len).ok_or(AccessError::InvalidAccessRange)?;
352    let values = slots
353        .get(slot_ix)
354        .ok_or(AccessError::StateSlotIxOutOfBounds(slot_ix as Word))?
355        .get(range.clone())
356        .ok_or(AccessError::StateValueRangeOutOfBounds(
357            range.start as Word,
358            range.end as Word,
359        ))?;
360    Ok(values)
361}
362
363fn range_from_start_len(start: Word, len: Word) -> Option<std::ops::Range<usize>> {
364    let start = usize::try_from(start).ok()?;
365    let len = usize::try_from(len).ok()?;
366    let end = start.checked_add(len)?;
367    Some(start..end)
368}
369
370fn state_slots_from_delta(slots: StateSlots, delta: bool) -> &StateSlotSlice {
371    if delta {
372        slots.post
373    } else {
374        slots.pre
375    }
376}