use crate::{
error::{AccessError, LenWordsError, MissingAccessArgError, OpError, StackError},
repeat::Repeat,
sets::encode_set,
types::convert::bool_from_word,
OpResult, Stack,
};
use essential_constraint_asm::Word;
use essential_types::{
convert::word_4_from_u8_32,
solution::{Mutation, Solution, SolutionData, SolutionDataIndex},
Key, Value,
};
use std::collections::{HashMap, HashSet};
#[cfg(test)]
mod dec_vars;
#[cfg(test)]
mod num_slots;
#[cfg(test)]
mod pub_vars;
#[cfg(test)]
mod state;
#[cfg(test)]
mod test_utils;
#[cfg(test)]
mod tests;
pub type TransientData = HashMap<SolutionDataIndex, HashMap<Key, Vec<Word>>>;
#[derive(Clone, Copy, Debug)]
pub struct Access<'a> {
pub solution: SolutionAccess<'a>,
pub state_slots: StateSlots<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct SolutionAccess<'a> {
pub data: &'a [SolutionData],
pub index: usize,
pub mutable_keys: &'a HashSet<&'a [Word]>,
pub transient_data: &'a TransientData,
}
#[derive(Clone, Copy, Debug)]
pub struct StateSlots<'a> {
pub pre: &'a StateSlotSlice,
pub post: &'a StateSlotSlice,
}
pub type StateSlotSlice = [Vec<Word>];
impl<'a> SolutionAccess<'a> {
pub fn new(
solution: &'a Solution,
predicate_index: SolutionDataIndex,
mutable_keys: &'a HashSet<&[Word]>,
transient_data: &'a TransientData,
) -> Self {
Self {
data: &solution.data,
index: predicate_index.into(),
mutable_keys,
transient_data,
}
}
pub fn this_data(&self) -> &SolutionData {
self.data
.get(self.index)
.expect("predicate index out of range of solution data")
}
pub fn this_transient_data(&self) -> Option<&HashMap<Key, Vec<Word>>> {
self.transient_data.get(&(self.index as SolutionDataIndex))
}
}
impl<'a> StateSlots<'a> {
pub const EMPTY: Self = Self {
pre: &[],
post: &[],
};
}
pub fn mut_keys(
solution: &Solution,
predicate_index: SolutionDataIndex,
) -> impl Iterator<Item = &Key> {
solution.data[predicate_index as usize]
.state_mutations
.iter()
.map(|m| &m.key)
}
pub fn mut_keys_slices(
solution: &Solution,
predicate_index: SolutionDataIndex,
) -> impl Iterator<Item = &[Word]> {
solution.data[predicate_index as usize]
.state_mutations
.iter()
.map(|m| m.key.as_ref())
}
pub fn mut_keys_set(solution: &Solution, predicate_index: SolutionDataIndex) -> HashSet<&[Word]> {
mut_keys_slices(solution, predicate_index).collect()
}
pub fn transient_data(solution: &Solution) -> TransientData {
let mut transient_data = HashMap::new();
for (ix, data) in solution.data.iter().enumerate() {
if !data.transient_data.is_empty() {
transient_data.insert(
ix as SolutionDataIndex,
data.transient_data
.iter()
.cloned()
.map(|Mutation { key, value }| (key, value))
.collect(),
);
}
}
transient_data
}
pub(crate) fn decision_var(this_decision_vars: &[Value], stack: &mut Stack) -> OpResult<()> {
let len = stack.pop().map_err(|_| MissingAccessArgError::DecVarLen)?;
let value_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::DecVarValueIx)?;
let slot_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::DecVarSlotIx)?;
let slot_ix =
usize::try_from(slot_ix).map_err(|_| AccessError::DecisionSlotIxOutOfBounds(slot_ix))?;
let range = range_from_start_len(value_ix, len).ok_or(AccessError::InvalidAccessRange)?;
let words = resolve_decision_var_range(this_decision_vars, slot_ix, range)?;
stack.extend(words.iter().copied())?;
Ok(())
}
pub(crate) fn decision_var_len(this_decision_vars: &[Value], stack: &mut Stack) -> OpResult<()> {
let slot_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::DecVarSlotIx)?;
let slot_ix =
usize::try_from(slot_ix).map_err(|_| AccessError::DecisionSlotIxOutOfBounds(slot_ix))?;
let len = resolve_decision_var_len(this_decision_vars, slot_ix)?;
let w = Word::try_from(len).map_err(|_| AccessError::DecisionLengthTooLarge(len))?;
stack
.push(w)
.expect("Can't fail because 1 is popped and 1 is pushed");
Ok(())
}
pub(crate) fn push_mut_keys(solution: SolutionAccess, stack: &mut Stack) -> OpResult<()> {
encode_set(
solution.mutable_keys.iter().map(|k| k.iter().copied()),
stack,
)
}
pub(crate) fn push_pub_var_keys(pub_vars: &TransientData, stack: &mut Stack) -> OpResult<()> {
let pathway_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::PushPubVarKeysPathwayIx)?;
let pathway_ix = SolutionDataIndex::try_from(pathway_ix)
.map_err(|_| AccessError::PathwayOutOfBounds(pathway_ix))?;
let pub_vars = pub_vars
.get(&pathway_ix)
.ok_or(AccessError::PathwayOutOfBounds(pathway_ix as Word))?;
encode_set(pub_vars.keys().map(|k| k.iter().copied()), stack)?;
Ok(())
}
pub(crate) fn state(slots: StateSlots, stack: &mut Stack) -> OpResult<()> {
let delta = stack.pop().map_err(|_| MissingAccessArgError::StateDelta)?;
let len = stack.pop().map_err(|_| MissingAccessArgError::StateLen)?;
let value_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::StateValueIx)?;
let slot_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::StateSlotIx)?;
let values = state_slot_value_range(slots, slot_ix, value_ix, len, delta)?;
stack.extend(values.iter().copied())?;
Ok(())
}
pub(crate) fn state_len(slots: StateSlots, stack: &mut Stack) -> OpResult<()> {
let delta = stack.pop().map_err(|_| MissingAccessArgError::StateDelta)?;
let slot_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::StateSlotIx)?;
let slot = state_slot(slots, slot_ix, delta)?;
let len =
Word::try_from(slot.len()).map_err(|_| AccessError::StateValueTooLarge(slot.len()))?;
stack
.push(len)
.expect("Can't fail because 2 are popped and 1 is pushed");
Ok(())
}
pub(crate) fn this_address(data: &SolutionData, stack: &mut Stack) -> OpResult<()> {
let words = word_4_from_u8_32(data.predicate_to_solve.predicate.0);
stack.extend(words)?;
Ok(())
}
pub(crate) fn this_contract_address(data: &SolutionData, stack: &mut Stack) -> OpResult<()> {
let words = word_4_from_u8_32(data.predicate_to_solve.contract.0);
stack.extend(words)?;
Ok(())
}
pub(crate) fn this_pathway(index: usize, stack: &mut Stack) -> OpResult<()> {
let index: Word = index
.try_into()
.map_err(|_| AccessError::SolutionDataOutOfBounds)?;
Ok(stack.push(index)?)
}
pub(crate) fn repeat_counter(stack: &mut Stack, repeat: &Repeat) -> OpResult<()> {
let counter = repeat.counter()?;
Ok(stack.push(counter)?)
}
pub(crate) fn pub_var(stack: &mut Stack, pub_vars: &TransientData) -> OpResult<()> {
let value_len = stack
.pop()
.map_err(|_| MissingAccessArgError::PubVarValueLen)?;
let value_ix = stack
.pop()
.map_err(|_| MissingAccessArgError::PubVarValueIx)?;
let range = range_from_start_len(value_ix, value_len).ok_or(AccessError::InvalidAccessRange)?;
let key_length = stack
.pop()
.map_err(|_| MissingAccessArgError::PubVarKeyLen)?;
let length = usize::try_from(key_length)
.map_err(|_| AccessError::KeyLengthOutOfBounds(key_length))?
.checked_add(1)
.ok_or(AccessError::KeyLengthOutOfBounds(key_length))?;
let value = stack
.pop_words::<_, _, OpError>(length, |slice| {
let (pathway_ix, key) = slice
.split_first()
.expect("Can't fail because must have at least 1 word");
let pathway_ix = SolutionDataIndex::try_from(*pathway_ix)
.map_err(|_| AccessError::PathwayOutOfBounds(*pathway_ix))?;
let value = pub_vars
.get(&pathway_ix)
.ok_or(AccessError::PathwayOutOfBounds(pathway_ix as Word))?
.get(key)
.ok_or(AccessError::PubVarKeyOutOfBounds)?
.get(range)
.ok_or(AccessError::PubVarDataOutOfBounds)?;
Ok(value.to_vec())
})
.map_err(map_key_len_err)?;
Ok(stack.extend(value)?)
}
pub(crate) fn pub_var_len(stack: &mut Stack, pub_vars: &TransientData) -> OpResult<()> {
let key_length = stack
.pop()
.map_err(|_| MissingAccessArgError::PubVarKeyLen)?;
let length = usize::try_from(key_length)
.map_err(|_| AccessError::KeyLengthOutOfBounds(key_length))?
.checked_add(1)
.ok_or(AccessError::KeyLengthOutOfBounds(key_length))?;
let length = stack
.pop_words::<_, _, OpError>(length, |slice| {
let (pathway_ix, key) = slice
.split_first()
.expect("Can't fail because must have at least 1 word");
let pathway_ix = SolutionDataIndex::try_from(*pathway_ix)
.map_err(|_| AccessError::PathwayOutOfBounds(*pathway_ix))?;
let value = pub_vars
.get(&pathway_ix)
.ok_or(AccessError::PathwayOutOfBounds(pathway_ix as Word))?
.get(key)
.ok_or(AccessError::PubVarKeyOutOfBounds)?;
Ok(value.len())
})
.map_err(map_key_len_err)?;
let length = Word::try_from(length).map_err(|_| AccessError::PubVarDataOutOfBounds)?;
stack
.push(length)
.expect("Can't fail because 3 are popped and 1 is pushed");
Ok(())
}
pub(crate) fn predicate_at(stack: &mut Stack, data: &[SolutionData]) -> OpResult<()> {
let pathway = stack.pop()?;
let pathway = usize::try_from(pathway).map_err(|_| AccessError::PathwayOutOfBounds(pathway))?;
let address = data
.get(pathway)
.ok_or(StackError::IndexOutOfBounds)?
.predicate_to_solve
.clone();
let contract_address = word_4_from_u8_32(address.contract.0);
let predicate_address = word_4_from_u8_32(address.predicate.0);
stack.extend(contract_address)?;
stack.extend(predicate_address)?;
Ok(())
}
pub(crate) fn num_slots(
stack: &mut Stack,
state_slots: &StateSlots<'_>,
decision_variables: &[Value],
) -> OpResult<()> {
const DEC_VAR_SLOTS: Word = 0;
const PRE_STATE_SLOTS: Word = 1;
const POST_STATE_SLOTS: Word = 2;
let which_slots = stack.pop()?;
match which_slots {
DEC_VAR_SLOTS => {
let num_slots = Word::try_from(decision_variables.len())
.map_err(|_| AccessError::SlotsLengthTooLarge(decision_variables.len()))?;
stack.push(num_slots)?;
}
PRE_STATE_SLOTS => {
let num_slots = Word::try_from(state_slots.pre.len())
.map_err(|_| AccessError::SlotsLengthTooLarge(state_slots.pre.len()))?;
stack.push(num_slots)?;
}
POST_STATE_SLOTS => {
let num_slots = Word::try_from(state_slots.post.len())
.map_err(|_| AccessError::SlotsLengthTooLarge(state_slots.post.len()))?;
stack.push(num_slots)?;
}
_ => return Err(AccessError::InvalidSlotType(which_slots).into()),
}
Ok(())
}
fn map_key_len_err(e: OpError) -> OpError {
match e {
OpError::Stack(StackError::LenWords(e)) => match e {
LenWordsError::OutOfBounds(_) => MissingAccessArgError::PubVarKey.into(),
LenWordsError::MissingLength => MissingAccessArgError::PubVarKeyLen.into(),
LenWordsError::InvalidLength(l) => AccessError::KeyLengthOutOfBounds(l).into(),
e => StackError::LenWords(e).into(),
},
e => e,
}
}
pub(crate) fn resolve_decision_var_range(
decision_variables: &[Value],
slot_ix: usize,
value_range_ix: core::ops::Range<usize>,
) -> Result<&[Word], AccessError> {
decision_variables
.get(slot_ix)
.ok_or(AccessError::DecisionSlotIxOutOfBounds(slot_ix as Word))?
.get(value_range_ix.clone())
.ok_or(AccessError::DecisionValueRangeOutOfBounds(
value_range_ix.start as Word,
value_range_ix.end as Word,
))
}
pub(crate) fn resolve_decision_var_len(
decision_variables: &[Value],
slot_ix: usize,
) -> Result<usize, AccessError> {
decision_variables
.get(slot_ix)
.map(|slot| slot.len())
.ok_or(AccessError::DecisionSlotIxOutOfBounds(slot_ix as Word))
}
fn state_slot(slots: StateSlots, slot_ix: Word, delta: Word) -> OpResult<&Vec<Word>> {
let delta = bool_from_word(delta).ok_or(AccessError::InvalidStateSlotDelta(delta))?;
let slots = state_slots_from_delta(slots, delta);
let ix = usize::try_from(slot_ix).map_err(|_| AccessError::StateSlotIxOutOfBounds(slot_ix))?;
let slot = slots
.get(ix)
.ok_or(AccessError::StateSlotIxOutOfBounds(slot_ix))?;
Ok(slot)
}
fn state_slot_value_range(
slots: StateSlots,
slot_ix: Word,
value_ix: Word,
len: Word,
delta: Word,
) -> OpResult<&[Word]> {
let delta = bool_from_word(delta).ok_or(AccessError::InvalidStateSlotDelta(delta))?;
let slots = state_slots_from_delta(slots, delta);
let slot_ix =
usize::try_from(slot_ix).map_err(|_| AccessError::StateSlotIxOutOfBounds(slot_ix))?;
let range = range_from_start_len(value_ix, len).ok_or(AccessError::InvalidAccessRange)?;
let values = slots
.get(slot_ix)
.ok_or(AccessError::StateSlotIxOutOfBounds(slot_ix as Word))?
.get(range.clone())
.ok_or(AccessError::StateValueRangeOutOfBounds(
range.start as Word,
range.end as Word,
))?;
Ok(values)
}
fn range_from_start_len(start: Word, len: Word) -> Option<std::ops::Range<usize>> {
let start = usize::try_from(start).ok()?;
let len = usize::try_from(len).ok()?;
let end = start.checked_add(len)?;
Some(start..end)
}
fn state_slots_from_delta(slots: StateSlots, delta: bool) -> &StateSlotSlice {
if delta {
slots.post
} else {
slots.pre
}
}