essential_vm/
vm.rs

1//! The VM state machine, used to drive forward execution.
2
3use crate::{
4    error::{EvalError, EvalResult, ExecError, OpError, OutOfGasError},
5    sync::step_op,
6    Access, BytecodeMapped, Gas, GasLimit, LazyCache, Memory, Op, OpAccess, OpGasCost,
7    ProgramControlFlow, Repeat, Stack, StateReads,
8};
9use essential_types::convert::bool_from_word;
10use std::sync::Arc;
11
12/// The operation execution state of the VM.
13#[derive(Clone, Debug, Default, PartialEq)]
14pub struct Vm {
15    /// The program counter, i.e. index of the current operation within the program.
16    pub pc: usize,
17    /// The stack machine.
18    pub stack: Stack,
19    /// The memory for temporary storage of words.
20    pub memory: Memory,
21    /// The stack of parent `Memory`s.
22    ///
23    /// This is empty at the beginning of execution, but is pushed to each time
24    /// we enter a [`Compute`] op context with the parent's `Memory`.
25    ///
26    /// This can also be used to observe the `Compute` op depth.
27    pub parent_memory: Vec<Arc<Memory>>,
28    /// Propagation of `Halt` encountered in compute program.
29    pub halt: bool,
30    /// The repeat stack.
31    pub repeat: Repeat,
32    /// Lazily cached data for the VM.
33    pub cache: Arc<LazyCache>,
34}
35
36impl Vm {
37    /// Execute the given operations from the current state of the VM.
38    ///
39    /// This function uses synchronous state reading and is intended for use
40    /// with in-memory state implementations.
41    ///
42    /// Upon reaching a `Halt` operation or reaching the end of the operation
43    /// sequence, returns the gas spent and the `Vm` will be left in the
44    /// resulting state.
45    ///
46    /// This is a wrapper around [`Vm::exec`] that expects operation access in
47    /// the form of a `&[Op]`.
48    ///
49    /// If memory bloat is a concern, consider using the [`Vm::exec_bytecode`]
50    /// method which allows for providing a more compact representation of the
51    /// operations in the form of mapped bytecode.
52    pub fn exec_ops<S>(
53        &mut self,
54        ops: &[Op],
55        access: Access,
56        state_reads: &S,
57        op_gas_cost: &impl OpGasCost,
58        gas_limit: GasLimit,
59    ) -> Result<Gas, ExecError<S::Error>>
60    where
61        S: StateReads,
62    {
63        self.exec(access, state_reads, ops, op_gas_cost, gas_limit)
64    }
65
66    /// Execute the given mapped bytecode from the current state of the VM.
67    ///
68    /// This function uses synchronous state reading and is intended for use
69    /// with in-memory state implementations.
70    ///
71    /// Upon reaching a `Halt` operation or reaching the end of the operation
72    /// sequence, returns the gas spent and the `Vm` will be left in the
73    /// resulting state.
74    ///
75    /// This is a wrapper around [`Vm::exec`] that expects operation access in
76    /// the form of [`&BytecodeMapped`][BytecodeMapped].
77    ///
78    /// This can be a more memory efficient alternative to [`Vm::exec_ops`] due
79    /// to the compact representation of operations in the form of bytecode and
80    /// indices.
81    pub fn exec_bytecode<S, B>(
82        &mut self,
83        bytecode_mapped: &BytecodeMapped<B>,
84        access: Access,
85        state_reads: &S,
86        op_gas_cost: &impl OpGasCost,
87        gas_limit: GasLimit,
88    ) -> Result<Gas, ExecError<S::Error>>
89    where
90        S: StateReads,
91        B: core::ops::Deref<Target = [u8]> + Send + Sync,
92    {
93        self.exec(access, state_reads, bytecode_mapped, op_gas_cost, gas_limit)
94    }
95
96    /// Execute the given operations synchronously from the current state of the VM.
97    ///
98    /// This function uses synchronous state reading and is intended for use
99    /// with in-memory state implementations.
100    ///
101    /// Upon reaching a `Halt` operation or reaching the end of the operation
102    /// sequence, returns the gas spent and the `Vm` will be left in the
103    /// resulting state.
104    pub fn exec<S, OA>(
105        &mut self,
106        access: Access,
107        state_reads: &S,
108        op_access: OA,
109        op_gas_cost: &impl OpGasCost,
110        gas_limit: GasLimit,
111    ) -> Result<Gas, ExecError<S::Error>>
112    where
113        S: StateReads,
114        OA: OpAccess<Op = Op>,
115        OA::Error: Into<OpError<S::Error>>,
116    {
117        // Track the gas spent.
118        let mut gas_spent: u64 = 0;
119
120        // Execute each operation
121        while let Some(res) = op_access.op_access(self.pc) {
122            let op = res.map_err(|err| ExecError(self.pc, err.into()))?;
123
124            // Calculate the gas cost of the operation.
125            let op_gas = op_gas_cost.op_gas_cost(&op);
126
127            // Check that the operation wouldn't exceed gas limit.
128            let next_spent = gas_spent
129                .checked_add(op_gas)
130                .filter(|&spent| spent <= gas_limit.total)
131                .ok_or(ExecError(
132                    self.pc,
133                    OutOfGasError {
134                        spent: gas_spent,
135                        op_gas,
136                        limit: gas_limit.total,
137                    }
138                    .into(),
139                ))?;
140
141            // Update the gas spent.
142            gas_spent = next_spent;
143
144            // Execute the operation.
145            let res = step_op(
146                access.clone(),
147                op,
148                self,
149                state_reads,
150                op_access.clone(),
151                op_gas_cost,
152                gas_limit,
153            );
154
155            #[cfg(feature = "tracing")]
156            crate::trace_op_res(
157                &op_access,
158                self.pc,
159                &self.stack,
160                &self.memory,
161                &self.parent_memory,
162                self.halt,
163                &res,
164            );
165
166            // Handle the result of the operation.
167            let update = match res {
168                Ok(update) => update,
169                Err(err) => return Err(ExecError(self.pc, err)),
170            };
171
172            // Update the program counter.
173            match update {
174                Some(ProgramControlFlow::Pc(new_pc)) => self.pc = new_pc,
175                Some(ProgramControlFlow::Halt) => break,
176                Some(ProgramControlFlow::ComputeEnd) => {
177                    self.pc += 1;
178                    break;
179                }
180                // TODO: compute gas_spent is not inferrable above
181                Some(ProgramControlFlow::ComputeResult((pc, gas, halt))) => {
182                    gas_spent += gas;
183                    self.pc = pc;
184                    self.halt |= halt;
185                    if self.halt {
186                        break;
187                    }
188                }
189                None => self.pc += 1,
190            }
191        }
192        Ok(gas_spent)
193    }
194
195    /// Evaluate a slice of synchronous operations and return their boolean result.
196    ///
197    /// This is the same as [`exec_ops`], but retrieves the boolean result from the resulting stack.
198    pub fn eval_ops<S>(
199        &mut self,
200        ops: &[Op],
201        access: Access,
202        state: &S,
203        op_gas_cost: &impl OpGasCost,
204        gas_limit: GasLimit,
205    ) -> EvalResult<bool, S::Error>
206    where
207        S: StateReads,
208    {
209        self.eval(ops, access, state, op_gas_cost, gas_limit)
210    }
211
212    // Evaluate the operations of a single synchronous program and return its boolean result.
213    ///
214    /// This is the same as [`exec`], but retrieves the boolean result from the resulting stack.
215    pub fn eval<OA, S>(
216        &mut self,
217        op_access: OA,
218        access: Access,
219        state: &S,
220        op_gas_cost: &impl OpGasCost,
221        gas_limit: GasLimit,
222    ) -> EvalResult<bool, S::Error>
223    where
224        OA: OpAccess<Op = Op>,
225        OA::Error: Into<OpError<S::Error>>,
226        S: StateReads,
227    {
228        self.exec(access, state, op_access, op_gas_cost, gas_limit)?;
229
230        let word = match self.stack.last() {
231            Some(&w) => w,
232            None => return Err(EvalError::InvalidEvaluation(self.stack.clone())),
233        };
234        bool_from_word(word).ok_or_else(|| EvalError::InvalidEvaluation(self.stack.clone()))
235    }
236}