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