essential_vm/
vm.rs

1//! The VM state machine, used to drive forward execution.
2
3use crate::{
4    error::{ExecError, OpError},
5    future, Access, BytecodeMapped, BytecodeMappedLazy, Gas, GasLimit, LazyCache, Memory, Op,
6    OpAccess, OpGasCost, Repeat, Stack, StateRead,
7};
8
9/// The operation execution state of the VM.
10#[derive(Debug, Default, PartialEq)]
11pub struct Vm {
12    /// The program counter, i.e. index of the current operation within the program.
13    pub pc: usize,
14    /// The stack machine.
15    pub stack: Stack,
16    /// The memory for temporary storage of words.
17    pub memory: Memory,
18    /// The repeat stack.
19    pub repeat: Repeat,
20    /// Lazily cached data for the VM.
21    pub cache: LazyCache,
22}
23
24impl Vm {
25    /// Execute the given operations from the current state of the VM.
26    ///
27    /// Upon reaching a `Halt` operation or reaching the end of the operation
28    /// sequence, returns the gas spent and the `Vm` will be left in the
29    /// resulting state.
30    ///
31    /// This is a wrapper around [`Vm::exec`] that expects operation access in
32    /// the form of a `&[Op]`.
33    ///
34    /// If memory bloat is a concern, consider using the [`Vm::exec_bytecode`]
35    /// or [`Vm::exec_bytecode_iter`] methods which allow for providing a more
36    /// compact representation of the operations in the form of mapped bytecode.
37    pub async fn exec_ops<S>(
38        &mut self,
39        ops: &[Op],
40        access: Access<'_>,
41        state_read: &S,
42        op_gas_cost: &impl OpGasCost,
43        gas_limit: GasLimit,
44    ) -> Result<Gas, ExecError<S::Error>>
45    where
46        S: StateRead,
47    {
48        self.exec(access, state_read, ops, op_gas_cost, gas_limit)
49            .await
50    }
51
52    /// Execute the given mapped bytecode from the current state of the VM.
53    ///
54    /// Upon reaching a `Halt` operation or reaching the end of the operation
55    /// sequence, returns the gas spent and the `Vm` will be left in the
56    /// resulting state.
57    ///
58    /// This is a wrapper around [`Vm::exec`] that expects operation access in
59    /// the form of [`&BytecodeMapped`][BytecodeMapped].
60    ///
61    /// This can be a more memory efficient alternative to [`Vm::exec_ops`] due
62    /// to the compact representation of operations in the form of bytecode and
63    /// indices.
64    pub async fn exec_bytecode<S, B>(
65        &mut self,
66        bytecode_mapped: &BytecodeMapped<B>,
67        access: Access<'_>,
68        state_read: &S,
69        op_gas_cost: &impl OpGasCost,
70        gas_limit: GasLimit,
71    ) -> Result<Gas, ExecError<S::Error>>
72    where
73        S: StateRead,
74        B: core::ops::Deref<Target = [u8]>,
75    {
76        self.exec(access, state_read, bytecode_mapped, op_gas_cost, gas_limit)
77            .await
78    }
79
80    /// Execute the given bytecode from the current state of the VM.
81    ///
82    /// Upon reaching a `Halt` operation or reaching the end of the operation
83    /// sequence, returns the gas spent and the `Vm` will be left in the
84    /// resulting state.
85    ///
86    /// The given bytecode will be mapped lazily during execution. This
87    /// can be more efficient than pre-mapping the bytecode and using
88    /// [`Vm::exec_bytecode`] in the case that execution may fail early.
89    ///
90    /// However, successful execution still requires building the full
91    /// [`BytecodeMapped`] instance internally. So if bytecode has already been
92    /// mapped, [`Vm::exec_bytecode`] should be preferred.
93    pub async fn exec_bytecode_iter<S, I>(
94        &mut self,
95        bytecode_iter: I,
96        access: Access<'_>,
97        state_read: &S,
98        op_gas_cost: &impl OpGasCost,
99        gas_limit: GasLimit,
100    ) -> Result<Gas, ExecError<S::Error>>
101    where
102        S: StateRead,
103        I: IntoIterator<Item = u8>,
104        I::IntoIter: Unpin,
105    {
106        let bytecode_lazy = BytecodeMappedLazy::new(bytecode_iter);
107        self.exec(access, state_read, bytecode_lazy, op_gas_cost, gas_limit)
108            .await
109    }
110
111    /// Execute over the given operation access from the current state of the VM.
112    ///
113    /// Upon reaching a `Halt` operation or reaching the end of the operation
114    /// sequence, returns the gas spent and the `Vm` will be left in the
115    /// resulting state.
116    ///
117    /// The type requirements for the `op_access` argument can make this
118    /// finicky to use directly. You may prefer one of the convenience methods:
119    ///
120    /// - [`Vm::exec_ops`]
121    /// - [`Vm::exec_bytecode`]
122    /// - [`Vm::exec_bytecode_iter`]
123    pub async fn exec<S, OA>(
124        &mut self,
125        access: Access<'_>,
126        state_read: &S,
127        op_access: OA,
128        op_gas_cost: &impl OpGasCost,
129        gas_limit: GasLimit,
130    ) -> Result<Gas, ExecError<S::Error>>
131    where
132        S: StateRead,
133        OA: OpAccess<Op = Op> + Unpin,
134        OA::Error: Into<OpError<S::Error>>,
135    {
136        future::exec(self, access, state_read, op_access, op_gas_cost, gas_limit).await
137    }
138}