#![deny(missing_docs, unsafe_code)]
use constraint::{ProgramControlFlow, Repeat};
#[doc(inline)]
pub use error::{OpAsyncResult, OpResult, OpSyncResult, StateMemoryResult, StateReadResult};
use error::{OpError, OpSyncError, StateMemoryError, StateReadError};
use essential_constraint_vm::LazyCache;
#[doc(inline)]
pub use essential_constraint_vm::{
self as constraint, Access, OpAccess, SolutionAccess, Stack, StateSlotSlice, StateSlots,
};
#[doc(inline)]
pub use essential_state_asm as asm;
use essential_state_asm::Op;
pub use essential_types as types;
use essential_types::{ContentAddress, Word};
#[doc(inline)]
pub use future::ExecFuture;
pub use state_memory::StateMemory;
pub use state_read::StateRead;
pub mod error;
mod future;
mod state_memory;
mod state_read;
#[derive(Debug, Default, PartialEq)]
pub struct Vm {
pub pc: usize,
pub stack: Stack,
pub temp_memory: essential_constraint_vm::Memory,
pub repeat: Repeat,
pub cache: LazyCache,
pub state_memory: StateMemory,
}
pub type Gas = u64;
pub type BytecodeMapped<Bytes = Vec<u8>> = constraint::BytecodeMapped<Op, Bytes>;
pub type BytecodeMappedSlice<'a> = constraint::BytecodeMappedSlice<'a, Op>;
pub type BytecodeMappedLazy<I> = constraint::BytecodeMappedLazy<Op, I>;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GasLimit {
pub per_yield: Gas,
pub total: Gas,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub(crate) enum OpKind {
Sync(OpSync),
Async(OpAsync),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub(crate) enum OpSync {
Constraint(asm::Constraint),
StateMemory(asm::StateMemory),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub(crate) enum OpAsync {
StateReadKeyRange,
StateReadKeyRangeExt,
}
pub trait OpGasCost {
fn op_gas_cost(&self, op: &Op) -> Gas;
}
impl GasLimit {
pub const DEFAULT_PER_YIELD: Gas = 4_096;
pub const UNLIMITED: Self = Self {
per_yield: Self::DEFAULT_PER_YIELD,
total: Gas::MAX,
};
}
impl Vm {
pub async fn exec_ops<'a, S>(
&mut self,
ops: &[Op],
access: Access<'a>,
state_read: &S,
op_gas_cost: &impl OpGasCost,
gas_limit: GasLimit,
) -> Result<Gas, StateReadError<S::Error>>
where
S: StateRead,
{
self.exec(access, state_read, ops, op_gas_cost, gas_limit)
.await
}
pub async fn exec_bytecode<'a, S, B>(
&mut self,
bytecode_mapped: &BytecodeMapped<B>,
access: Access<'a>,
state_read: &S,
op_gas_cost: &impl OpGasCost,
gas_limit: GasLimit,
) -> Result<Gas, StateReadError<S::Error>>
where
S: StateRead,
B: core::ops::Deref<Target = [u8]>,
{
self.exec(access, state_read, bytecode_mapped, op_gas_cost, gas_limit)
.await
}
pub async fn exec_bytecode_iter<'a, S, I>(
&mut self,
bytecode_iter: I,
access: Access<'a>,
state_read: &S,
op_gas_cost: &impl OpGasCost,
gas_limit: GasLimit,
) -> Result<Gas, StateReadError<S::Error>>
where
S: StateRead,
I: IntoIterator<Item = u8>,
I::IntoIter: Unpin,
{
let bytecode_lazy = BytecodeMappedLazy::new(bytecode_iter);
self.exec(access, state_read, bytecode_lazy, op_gas_cost, gas_limit)
.await
}
pub async fn exec<'a, S, OA>(
&mut self,
access: Access<'a>,
state_read: &S,
op_access: OA,
op_gas_cost: &impl OpGasCost,
gas_limit: GasLimit,
) -> Result<Gas, StateReadError<S::Error>>
where
S: StateRead,
OA: OpAccess<Op = Op> + Unpin,
OA::Error: Into<OpError<S::Error>>,
{
future::exec(self, access, state_read, op_access, op_gas_cost, gas_limit).await
}
pub fn into_state_slots(self) -> Vec<Vec<Word>> {
self.state_memory.into()
}
}
impl From<Op> for OpKind {
fn from(op: Op) -> Self {
match op {
Op::Constraint(op) => OpKind::Sync(OpSync::Constraint(op)),
Op::StateMemory(op) => OpKind::Sync(OpSync::StateMemory(op)),
Op::KeyRange => OpKind::Async(OpAsync::StateReadKeyRange),
Op::KeyRangeExtern => OpKind::Async(OpAsync::StateReadKeyRangeExt),
}
}
}
impl<F> OpGasCost for F
where
F: Fn(&Op) -> Gas,
{
fn op_gas_cost(&self, op: &Op) -> Gas {
(*self)(op)
}
}
pub(crate) fn step_op_sync(op: OpSync, access: Access, vm: &mut Vm) -> OpSyncResult<Option<usize>> {
match op {
OpSync::Constraint(op) => {
let Vm {
stack,
repeat,
pc,
temp_memory,
cache,
..
} = vm;
match constraint::step_op(access, op, stack, temp_memory, *pc, repeat, cache)? {
Some(ProgramControlFlow::Pc(pc)) => return Ok(Some(pc)),
Some(ProgramControlFlow::Halt) => return Ok(None),
None => (),
}
}
OpSync::StateMemory(op) => step_op_state_slots(op, &mut *vm)?,
}
let new_pc = vm.pc.checked_add(1).ok_or(OpSyncError::PcOverflow)?;
Ok(Some(new_pc))
}
pub(crate) fn step_op_state_slots(op: asm::StateMemory, vm: &mut Vm) -> OpSyncResult<()> {
match op {
asm::StateMemory::AllocSlots => {
state_memory::alloc_slots(&mut vm.stack, &mut vm.state_memory)
}
asm::StateMemory::Truncate => state_memory::truncate(&mut vm.stack, &mut vm.state_memory),
asm::StateMemory::Length => state_memory::length(&mut vm.stack, &vm.state_memory),
asm::StateMemory::ValueLen => state_memory::value_len(&mut vm.stack, &vm.state_memory),
asm::StateMemory::Load => state_memory::load(&mut vm.stack, &vm.state_memory),
asm::StateMemory::Store => state_memory::store(&mut vm.stack, &mut vm.state_memory),
}
}