fluentbase_runtime/runtime/contract_runtime.rs
1//! Contract execution runtime.
2//!
3//! This module implements the execution of user-deployed contracts
4//! in the rWasm environment. It is responsible for:
5//! - selecting the correct entrypoint (`main` vs. `deploy`),
6//! - wiring syscalls via the runtime syscall handler,
7//! - driving execution and resumption,
8//! - mediating access to linear memory, fuel, and runtime context.
9//!
10//! `ContractRuntime` is intentionally thin: most execution semantics
11//! are delegated to `StrategyDefinition` and `StrategyExecutor`.
12
13use crate::{syscall_handler::runtime_syscall_handler, RuntimeContext};
14use fluentbase_types::{STATE_DEPLOY, STATE_MAIN};
15use rwasm::{
16 ImportLinker, StoreTr, StrategyDefinition, StrategyExecutor, TrapCode, Value,
17 N_DEFAULT_MAX_MEMORY_PAGES,
18};
19use std::sync::Arc;
20
21/// Runtime responsible for executing a single contract invocation.
22///
23/// This runtime encapsulates a concrete execution `Strategy`
24/// (interpreter, AOT, JIT, etc.), a typed store holding the
25/// `RuntimeContext`, and the resolved entrypoint to invoke.
26///
27/// A single instance corresponds to one logical contract execution
28/// (call or deployment).
29pub struct ContractRuntime {
30 /// Typed store containing linear memory, globals, fuel state,
31 /// and the associated `RuntimeContext`.
32 executor: StrategyExecutor<RuntimeContext>,
33
34 /// Name of the entrypoint function to execute.
35 ///
36 /// Resolved at construction time based on the contract state
37 /// (`main` for calls, `deploy` for deployments).
38 entrypoint: &'static str,
39}
40
41impl ContractRuntime {
42 /// Creates a new contract runtime instance.
43 ///
44 /// This constructor:
45 /// - selects the appropriate entrypoint based on `ctx.state`,
46 /// - creates a new rWasm store bound to the provided execution strategy,
47 /// - wires the runtime syscall handler,
48 /// - configures fuel metering.
49 ///
50 /// # Panics
51 ///
52 /// Panics if the contract state is neither `STATE_MAIN` nor `STATE_DEPLOY`.
53 pub fn new(
54 strategy: StrategyDefinition,
55 import_linker: Arc<ImportLinker>,
56 ctx: RuntimeContext,
57 fuel_limit: Option<u64>,
58 ) -> Result<Self, TrapCode> {
59 let entrypoint = match ctx.state {
60 STATE_MAIN => "main",
61 STATE_DEPLOY => "deploy",
62 _ => unreachable!(),
63 };
64 let executor = strategy.create_executor(
65 import_linker,
66 ctx,
67 runtime_syscall_handler,
68 fuel_limit,
69 Some(N_DEFAULT_MAX_MEMORY_PAGES),
70 )?;
71 Ok(Self {
72 executor,
73 entrypoint,
74 })
75 }
76
77 /// Executes the contract entrypoint.
78 ///
79 /// Starts execution from the resolved entrypoint (`main` or `deploy`)
80 /// with no arguments and no direct return values.
81 ///
82 /// Any trap produced by execution is surfaced as a `TrapCode`.
83 pub fn execute(&mut self) -> Result<(), TrapCode> {
84 self.executor.execute(self.entrypoint, &[], &mut [])
85 }
86
87 /// Resumes contract execution after an external interruption.
88 ///
89 /// This is typically called after handling a syscall or delegated
90 /// execution. The provided `exit_code` is passed back into the runtime,
91 /// and `fuel_consumed` is charged before resuming execution.
92 pub fn resume(&mut self, exit_code: i32, fuel_consumed: u64) -> Result<(), TrapCode> {
93 self.executor.try_consume_fuel(fuel_consumed)?;
94 self.executor.resume(&[Value::I32(exit_code)], &mut [])
95 }
96
97 /// Writes data into the contract linear memory.
98 ///
99 /// Performs bounds checking according to the underlying memory model.
100 /// Out-of-bounds writes result in a trap.
101 pub fn memory_write(&mut self, offset: usize, data: &[u8]) -> Result<(), TrapCode> {
102 self.executor.memory_write(offset, data)
103 }
104
105 /// Reads data from the contract linear memory.
106 ///
107 /// Fills `buffer` with bytes starting at `offset`.
108 /// Traps if the read exceeds accessible memory.
109 pub fn memory_read(&mut self, offset: usize, buffer: &mut [u8]) -> Result<(), TrapCode> {
110 self.executor.memory_read(offset, buffer)
111 }
112
113 /// Returns the remaining execution fuel if fuel metering is enabled.
114 ///
115 /// Returns `None` if fuel accounting is disabled for this execution.
116 pub fn remaining_fuel(&self) -> Option<u64> {
117 self.executor.remaining_fuel()
118 }
119
120 /// Provides mutable access to the runtime context.
121 ///
122 /// This is the only supported way to mutate execution-scoped state
123 /// such as logs, gas accounting, call depth, or environment data.
124 pub fn context_mut(&mut self) -> &mut RuntimeContext {
125 self.executor.data_mut()
126 }
127
128 /// Provides immutable access to the runtime context.
129 ///
130 /// Intended for inspection and read-only queries.
131 pub fn context(&self) -> &RuntimeContext {
132 self.executor.data()
133 }
134}