1pub mod analysis;
2mod contract;
3#[cfg(feature = "serde")]
4pub mod serde;
5mod shared_memory;
6mod stack;
7
8pub use contract::Contract;
9pub use shared_memory::{next_multiple_of_32, SharedMemory, EMPTY_SHARED_MEMORY};
10pub use stack::{Stack, STACK_LIMIT};
11
12use crate::EOFCreateOutcome;
13use crate::{
14 primitives::Bytes, push, push_b256, return_ok, return_revert, CallOutcome, CreateOutcome,
15 FunctionStack, Gas, Host, InstructionResult, InterpreterAction,
16};
17use core::cmp::min;
18use rtvm_primitives::{Bytecode, Eof, U256};
19use std::borrow::ToOwned;
20
21#[derive(Debug)]
23pub struct Interpreter {
24 pub instruction_pointer: *const u8,
26 pub gas: Gas,
28 pub contract: Contract,
30 pub instruction_result: InstructionResult,
33 pub bytecode: Bytes,
36 pub is_eof: bool,
39 pub is_eof_init: bool,
41 pub shared_memory: SharedMemory,
46 pub stack: Stack,
48 pub function_stack: FunctionStack,
50 pub return_data_buffer: Bytes,
56 pub is_static: bool,
58 pub next_action: InterpreterAction,
63}
64
65impl Default for Interpreter {
66 fn default() -> Self {
67 Self::new(Contract::default(), 0, false)
68 }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
74pub struct InterpreterResult {
75 pub result: InstructionResult,
77 pub output: Bytes,
79 pub gas: Gas,
81}
82
83impl Interpreter {
84 pub fn new(contract: Contract, gas_limit: u64, is_static: bool) -> Self {
86 if !contract.bytecode.is_execution_ready() {
87 panic!("Contract is not execution ready {:?}", contract.bytecode);
88 }
89 let is_eof = contract.bytecode.is_eof();
90 let bytecode = contract.bytecode.bytecode_bytes();
91 Self {
92 instruction_pointer: bytecode.as_ptr(),
93 bytecode,
94 contract,
95 gas: Gas::new(gas_limit),
96 instruction_result: InstructionResult::Continue,
97 function_stack: FunctionStack::default(),
98 is_static,
99 is_eof,
100 is_eof_init: false,
101 return_data_buffer: Bytes::new(),
102 shared_memory: EMPTY_SHARED_MEMORY,
103 stack: Stack::new(),
104 next_action: InterpreterAction::None,
105 }
106 }
107
108 #[inline]
110 pub fn set_is_eof_init(&mut self) {
111 self.is_eof_init = true;
112 }
113
114 #[inline]
115 pub fn eof(&self) -> Option<&Eof> {
116 self.contract.bytecode.eof()
117 }
118
119 #[cfg(test)]
121 pub fn new_bytecode(bytecode: Bytecode) -> Self {
122 Self::new(
123 Contract::new(
124 Bytes::new(),
125 bytecode,
126 None,
127 crate::primitives::Address::default(),
128 crate::primitives::Address::default(),
129 U256::ZERO,
130 ),
131 0,
132 false,
133 )
134 }
135
136 pub(crate) fn load_eof_code(&mut self, idx: usize, pc: usize) {
138 let Bytecode::Eof(eof) = &self.contract.bytecode else {
140 panic!("Expected EOF bytecode")
141 };
142 let Some(code) = eof.body.code(idx) else {
143 panic!("Code not found")
144 };
145 self.bytecode = code.clone();
146 self.instruction_pointer = unsafe { self.bytecode.as_ptr().add(pc) };
147 }
148
149 pub fn insert_create_outcome(&mut self, create_outcome: CreateOutcome) {
175 self.instruction_result = InstructionResult::Continue;
176
177 let instruction_result = create_outcome.instruction_result();
178 self.return_data_buffer = if instruction_result.is_revert() {
179 create_outcome.output().to_owned()
181 } else {
182 Bytes::new()
184 };
185
186 match instruction_result {
187 return_ok!() => {
188 let address = create_outcome.address;
189 push_b256!(self, address.unwrap_or_default().into_word());
190 self.gas.erase_cost(create_outcome.gas().remaining());
191 self.gas.record_refund(create_outcome.gas().refunded());
192 }
193 return_revert!() => {
194 push!(self, U256::ZERO);
195 self.gas.erase_cost(create_outcome.gas().remaining());
196 }
197 InstructionResult::FatalExternalError => {
198 panic!("Fatal external error in insert_create_outcome");
199 }
200 _ => {
201 push!(self, U256::ZERO);
202 }
203 }
204 }
205
206 pub fn insert_eofcreate_outcome(&mut self, create_outcome: EOFCreateOutcome) {
207 let instruction_result = create_outcome.instruction_result();
208
209 self.return_data_buffer = if *instruction_result == InstructionResult::Revert {
210 create_outcome.output().to_owned()
212 } else {
213 Bytes::new()
215 };
216
217 match instruction_result {
218 InstructionResult::ReturnContract => {
219 push_b256!(self, create_outcome.address.into_word());
220 self.gas.erase_cost(create_outcome.gas().remaining());
221 self.gas.record_refund(create_outcome.gas().refunded());
222 }
223 return_revert!() => {
224 push!(self, U256::ZERO);
225 self.gas.erase_cost(create_outcome.gas().remaining());
226 }
227 InstructionResult::FatalExternalError => {
228 panic!("Fatal external error in insert_eofcreate_outcome");
229 }
230 _ => {
231 push!(self, U256::ZERO);
232 }
233 }
234 }
235
236 pub fn insert_call_outcome(
259 &mut self,
260 shared_memory: &mut SharedMemory,
261 call_outcome: CallOutcome,
262 ) {
263 self.instruction_result = InstructionResult::Continue;
264 self.return_data_buffer.clone_from(call_outcome.output());
265
266 let out_offset = call_outcome.memory_start();
267 let out_len = call_outcome.memory_length();
268
269 let target_len = min(out_len, self.return_data_buffer.len());
270 match call_outcome.instruction_result() {
271 return_ok!() => {
272 let remaining = call_outcome.gas().remaining();
274 let refunded = call_outcome.gas().refunded();
275 self.gas.erase_cost(remaining);
276 self.gas.record_refund(refunded);
277 shared_memory.set(out_offset, &self.return_data_buffer[..target_len]);
278 push!(self, U256::from(1));
279 }
280 return_revert!() => {
281 self.gas.erase_cost(call_outcome.gas().remaining());
282 shared_memory.set(out_offset, &self.return_data_buffer[..target_len]);
283 push!(self, U256::ZERO);
284 }
285 InstructionResult::FatalExternalError => {
286 panic!("Fatal external error in insert_call_outcome");
287 }
288 _ => {
289 push!(self, U256::ZERO);
290 }
291 }
292 }
293
294 #[inline]
296 pub fn current_opcode(&self) -> u8 {
297 unsafe { *self.instruction_pointer }
298 }
299
300 #[inline]
302 pub fn contract(&self) -> &Contract {
303 &self.contract
304 }
305
306 #[inline]
308 pub fn gas(&self) -> &Gas {
309 &self.gas
310 }
311
312 #[inline]
314 pub fn stack(&self) -> &Stack {
315 &self.stack
316 }
317
318 #[inline]
320 pub fn program_counter(&self) -> usize {
321 unsafe { self.instruction_pointer.offset_from(self.bytecode.as_ptr()) as usize }
324 }
325
326 #[inline]
330 pub(crate) fn step<FN, H: Host + ?Sized>(&mut self, instruction_table: &[FN; 256], host: &mut H)
331 where
332 FN: Fn(&mut Interpreter, &mut H),
333 {
334 let opcode = unsafe { *self.instruction_pointer };
336
337 self.instruction_pointer = unsafe { self.instruction_pointer.offset(1) };
341
342 (instruction_table[opcode as usize])(self, host)
344 }
345
346 pub fn take_memory(&mut self) -> SharedMemory {
348 core::mem::replace(&mut self.shared_memory, EMPTY_SHARED_MEMORY)
349 }
350
351 pub fn run<FN, H: Host + ?Sized>(
353 &mut self,
354 shared_memory: SharedMemory,
355 instruction_table: &[FN; 256],
356 host: &mut H,
357 ) -> InterpreterAction
358 where
359 FN: Fn(&mut Interpreter, &mut H),
360 {
361 self.next_action = InterpreterAction::None;
362 self.shared_memory = shared_memory;
363 while self.instruction_result == InstructionResult::Continue {
365 self.step(instruction_table, host);
366 }
367
368 if self.next_action.is_some() {
370 return core::mem::take(&mut self.next_action);
371 }
372 InterpreterAction::Return {
374 result: InterpreterResult {
375 result: self.instruction_result,
376 output: Bytes::new(),
378 gas: self.gas,
379 },
380 }
381 }
382}
383
384impl InterpreterResult {
385 #[inline]
387 pub const fn is_ok(&self) -> bool {
388 self.result.is_ok()
389 }
390
391 #[inline]
393 pub const fn is_revert(&self) -> bool {
394 self.result.is_revert()
395 }
396
397 #[inline]
399 pub const fn is_error(&self) -> bool {
400 self.result.is_error()
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::{opcode::InstructionTable, DummyHost};
408 use rtvm_primitives::CancunSpec;
409
410 #[test]
411 fn object_safety() {
412 let mut interp = Interpreter::new(Contract::default(), u64::MAX, false);
413
414 let mut host = crate::DummyHost::default();
415 let table: InstructionTable<DummyHost> =
416 crate::opcode::make_instruction_table::<DummyHost, CancunSpec>();
417 let _ = interp.run(EMPTY_SHARED_MEMORY, &table, &mut host);
418
419 let host: &mut dyn Host = &mut host as &mut dyn Host;
420 let table: InstructionTable<dyn Host> =
421 crate::opcode::make_instruction_table::<dyn Host, CancunSpec>();
422 let _ = interp.run(EMPTY_SHARED_MEMORY, &table, host);
423 }
424}