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}