Skip to main content

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}