Skip to main content

fluentbase_runtime/syscall_handler/host/
exec.rs

1/// Syscall entry points for deferred execution (exec) within the runtime.
2use crate::{
3    executor::{default_runtime_executor, RuntimeExecutor},
4    RuntimeContext,
5};
6use alloc::borrow::Cow;
7use fluentbase_types::{
8    byteorder::{ByteOrder, LittleEndian},
9    BytecodeOrHash, ExitCode, SyscallInvocationParams, B256, CALL_STACK_LIMIT,
10};
11use rwasm::{StoreTr, TrapCode, Value};
12use std::{
13    cmp::min,
14    fmt::{Debug, Display, Formatter},
15};
16
17#[derive(Clone)]
18/// Holds parameters required to resume a deferred exec and whether it originated at root depth.
19pub struct InterruptionHolder {
20    /// Encoded invocation parameters (target code hash, input, fuel, state, pointers).
21    pub params: SyscallInvocationParams,
22    /// True if the interruption happened at depth 0.
23    pub is_root: bool,
24}
25
26impl Debug for InterruptionHolder {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        write!(f, "runtime resume error")
29    }
30}
31
32impl Display for InterruptionHolder {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        write!(f, "runtime resume error")
35    }
36}
37
38/// Dispatches the exec syscall: validates fuel, captures parameters, and triggers an interruption.
39pub fn syscall_exec_handler(
40    caller: &mut impl StoreTr<RuntimeContext>,
41    params: &[Value],
42    _result: &mut [Value],
43) -> Result<(), TrapCode> {
44    let remaining_fuel = caller.remaining_fuel().unwrap_or(u64::MAX);
45    let (hash32_ptr, input_ptr, input_len, fuel16_ptr, state) = (
46        params[0].i32().unwrap() as usize,
47        params[1].i32().unwrap() as usize,
48        params[2].i32().unwrap() as usize,
49        params[3].i32().unwrap() as usize,
50        params[4].i32().unwrap() as u32,
51    );
52    // make sure we have enough fuel for this call
53    let fuel_limit = if fuel16_ptr > 0 {
54        let mut fuel_buffer = [0u8; 16];
55        caller.memory_read(fuel16_ptr, &mut fuel_buffer)?;
56        let fuel_limit = LittleEndian::read_i64(&fuel_buffer[..8]) as u64;
57        let _fuel_refund = LittleEndian::read_i64(&fuel_buffer[8..]);
58        if fuel_limit > 0 {
59            min(fuel_limit, remaining_fuel)
60        } else {
61            0
62        }
63    } else {
64        remaining_fuel
65    };
66    let mut code_hash = [0u8; 32];
67    caller.memory_read(hash32_ptr, &mut code_hash)?;
68    let code_hash = B256::from(code_hash);
69    let is_root = caller.data().call_depth == 0;
70    let params = SyscallInvocationParams {
71        code_hash,
72        input: input_ptr..(input_ptr + input_len),
73        fuel_limit,
74        state,
75        fuel16_ptr: fuel16_ptr as u32,
76    };
77    // return resumable error
78    caller.data_mut().resumable_context = Some(InterruptionHolder { params, is_root });
79    Err(TrapCode::InterruptionCalled)
80}
81
82/// Continues an exec after an interruption, executing the delegated call.
83pub fn syscall_exec_continue(
84    _caller: &mut impl StoreTr<RuntimeContext>,
85    _context: &InterruptionHolder,
86) -> (u64, i64, i32) {
87    unimplemented!("runtime: not supported until we finish zkVM");
88    // let fuel_limit = context.params.fuel_limit;
89    // let (fuel_consumed, fuel_refunded, exit_code) = caller.context_mut(|ctx| {
90    //     syscall_exec_impl(
91    //         ctx,
92    //         context.params.code_hash,
93    //         BytesOrRef::Ref(context.params.input.as_ref()),
94    //         fuel_limit,
95    //         context.params.state,
96    //     )
97    // });
98    // (fuel_consumed, fuel_refunded, exit_code)
99}
100
101/// Executes a nested runtime with the given parameters and merges the result into ctx.
102pub fn syscall_exec_impl<I: Into<BytecodeOrHash>>(
103    ctx: &mut RuntimeContext,
104    code_hash: I,
105    input: Cow<'_, [u8]>,
106    fuel_limit: u64,
107    state: u32,
108) -> (u64, i64, i32) {
109    // check call depth overflow
110    if ctx.call_depth >= CALL_STACK_LIMIT {
111        return (fuel_limit, 0, ExitCode::CallDepthOverflow.into_i32());
112    }
113    // create a new runtime instance with the context
114    let ctx2 = RuntimeContext::default()
115        .with_fuel_limit(fuel_limit)
116        .with_input(input.into_owned())
117        .with_state(state)
118        .with_call_depth(ctx.call_depth + 1);
119
120    let result = default_runtime_executor().execute(code_hash.into(), ctx2);
121    ctx.execution_result.return_data = result.output;
122    (result.fuel_consumed, result.fuel_refunded, result.exit_code)
123}