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}