gear_core_backend/
env.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! sp-sandbox environment for running a module.
20
21use crate::{
22    BackendExternalities,
23    error::{
24        ActorTerminationReason, BackendAllocSyscallError, BackendSyscallError, RunFallibleError,
25        TerminationReason,
26    },
27    funcs::FuncsHandler,
28    memory::{BackendMemory, ExecutorMemory},
29    state::{HostState, State},
30};
31use alloc::{collections::BTreeSet, format, string::String};
32use core::{fmt::Debug, marker::Send};
33use gear_core::{
34    env::{Externalities, WasmEntryPoint},
35    gas::GasAmount,
36    message::DispatchKind,
37    pages::WasmPagesAmount,
38};
39use gear_lazy_pages_common::{GlobalsAccessConfig, GlobalsAccessMod};
40use gear_sandbox::{
41    AsContextExt, HostFuncType, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance,
42    SandboxMemory, SandboxStore, TryFromValue, Value,
43    default_executor::{EnvironmentDefinitionBuilder, Instance, Store},
44};
45use gear_wasm_instrument::{
46    GLOBAL_NAME_GAS,
47    syscalls::SyscallName::{self, *},
48};
49#[cfg(feature = "std")]
50use {
51    gear_core::limited::LimitedStr, gear_core::memory::HostPointer,
52    gear_lazy_pages_common::GlobalsAccessError, gear_lazy_pages_common::GlobalsAccessor,
53};
54
55// we have requirement to pass function pointer for `gear_sandbox`
56// so the only reason this macro exists is const function pointers are not stabilized yet
57// so we create non-capturing closure that can be coerced into function pointer
58#[rustfmt::skip]
59macro_rules! wrap_syscall {
60    ($func:ident, $syscall:ident) => {
61        |caller, args| FuncsHandler::execute(caller, args, FuncsHandler::$func, $syscall)
62    };
63}
64
65fn store_host_state_mut<Ext: Send + 'static>(
66    store: &mut Store<HostState<Ext, BackendMemory<ExecutorMemory>>>,
67) -> &mut State<Ext, BackendMemory<ExecutorMemory>> {
68    store.data_mut().as_mut().unwrap_or_else(|| {
69        let err_msg =
70            "store_host_state_mut: State is not set, but it must be set in `Environment::new`";
71
72        log::error!("{err_msg}");
73        unreachable!("{err_msg}")
74    })
75}
76
77pub type EnvironmentExecutionResult<Ext> = Result<BackendReport<Ext>, EnvironmentError>;
78
79#[derive(Debug, derive_more::Display)]
80pub enum EnvironmentError {
81    #[display("Actor backend error: {_1}")]
82    Actor(GasAmount, String),
83    #[display("System backend error: {_0}")]
84    System(SystemEnvironmentError),
85}
86
87#[derive(Debug, derive_more::Display)]
88pub enum SystemEnvironmentError {
89    #[display("Failed to create env memory: {_0:?}")]
90    CreateEnvMemory(gear_sandbox::Error),
91    #[display("Gas counter not found or has wrong type")]
92    WrongInjectedGas,
93}
94
95/// Environment to run one module at a time providing Ext.
96pub struct Environment<Ext, EntryPoint = DispatchKind>
97where
98    Ext: BackendExternalities,
99    EntryPoint: WasmEntryPoint,
100{
101    instance: Instance<HostState<Ext, BackendMemory<ExecutorMemory>>>,
102    entries: BTreeSet<DispatchKind>,
103    entry_point: EntryPoint,
104    store: Store<HostState<Ext, BackendMemory<ExecutorMemory>>>,
105    memory: BackendMemory<ExecutorMemory>,
106}
107
108pub struct BackendReport<Ext>
109where
110    Ext: Externalities + 'static,
111{
112    pub termination_reason: TerminationReason,
113    pub store: Store<HostState<Ext, BackendMemory<ExecutorMemory>>>,
114    pub memory: BackendMemory<ExecutorMemory>,
115    pub ext: Ext,
116}
117
118// A helping wrapper for `EnvironmentDefinitionBuilder` and `forbidden_funcs`.
119// It makes adding functions to `EnvironmentDefinitionBuilder` shorter.
120struct EnvBuilder<Ext: BackendExternalities> {
121    env_def_builder: EnvironmentDefinitionBuilder<HostState<Ext, BackendMemory<ExecutorMemory>>>,
122    funcs_count: usize,
123}
124
125impl<Ext> EnvBuilder<Ext>
126where
127    Ext: BackendExternalities + Send + 'static,
128    Ext::UnrecoverableError: BackendSyscallError,
129    RunFallibleError: From<Ext::FallibleError>,
130    Ext::AllocError: BackendAllocSyscallError<ExtError = Ext::UnrecoverableError>,
131{
132    fn add_func(
133        &mut self,
134        name: SyscallName,
135        f: HostFuncType<HostState<Ext, BackendMemory<ExecutorMemory>>>,
136    ) {
137        self.env_def_builder.add_host_func("env", name.to_str(), f);
138
139        self.funcs_count += 1;
140    }
141
142    fn add_memory(&mut self, memory: BackendMemory<ExecutorMemory>) {
143        self.env_def_builder
144            .add_memory("env", "memory", memory.into_inner());
145    }
146}
147
148impl<Ext: BackendExternalities> From<EnvBuilder<Ext>>
149    for EnvironmentDefinitionBuilder<HostState<Ext, BackendMemory<ExecutorMemory>>>
150{
151    fn from(builder: EnvBuilder<Ext>) -> Self {
152        builder.env_def_builder
153    }
154}
155
156impl<Ext, EntryPoint> Environment<Ext, EntryPoint>
157where
158    Ext: BackendExternalities + Send + 'static,
159    Ext::UnrecoverableError: BackendSyscallError,
160    RunFallibleError: From<Ext::FallibleError>,
161    Ext::AllocError: BackendAllocSyscallError<ExtError = Ext::UnrecoverableError>,
162    EntryPoint: WasmEntryPoint,
163{
164    #[rustfmt::skip]
165    fn bind_funcs(builder: &mut EnvBuilder<Ext>) {
166        macro_rules! add_function {
167            ($syscall:ident, $func:ident) => {
168                builder.add_func($syscall, wrap_syscall!($func, $syscall));
169            };
170        }
171
172        add_function!(EnvVars, env_vars);
173        add_function!(BlockHeight, block_height);
174        add_function!(BlockTimestamp, block_timestamp);
175        add_function!(CreateProgram, create_program);
176        add_function!(CreateProgramWGas, create_program_wgas);
177        add_function!(Debug, debug);
178        add_function!(Panic, panic);
179        add_function!(OomPanic, oom_panic);
180        add_function!(Exit, exit);
181        add_function!(ReplyCode, reply_code);
182        add_function!(SignalCode, signal_code);
183        add_function!(ReserveGas, reserve_gas);
184        add_function!(ReplyDeposit, reply_deposit);
185        add_function!(UnreserveGas, unreserve_gas);
186        add_function!(GasAvailable, gas_available);
187        add_function!(Leave, leave);
188        add_function!(MessageId, message_id);
189        add_function!(ProgramId, program_id);
190        add_function!(Random, random);
191        add_function!(Read, read);
192        add_function!(Reply, reply);
193        add_function!(ReplyCommit, reply_commit);
194        add_function!(ReplyCommitWGas, reply_commit_wgas);
195        add_function!(ReplyPush, reply_push);
196        add_function!(ReplyTo, reply_to);
197        add_function!(SignalFrom, signal_from);
198        add_function!(ReplyWGas, reply_wgas);
199        add_function!(ReplyInput, reply_input);
200        add_function!(ReplyPushInput, reply_push_input);
201        add_function!(ReplyInputWGas, reply_input_wgas);
202        add_function!(Send, send);
203        add_function!(SendCommit, send_commit);
204        add_function!(SendCommitWGas, send_commit_wgas);
205        add_function!(SendInit, send_init);
206        add_function!(SendPush, send_push);
207        add_function!(SendWGas, send_wgas);
208        add_function!(SendInput, send_input);
209        add_function!(SendPushInput, send_push_input);
210        add_function!(SendInputWGas, send_input_wgas);
211        add_function!(Size, size);
212        add_function!(Source, source);
213        add_function!(Value, value);
214        add_function!(ValueAvailable, value_available);
215        add_function!(Wait, wait);
216        add_function!(WaitFor, wait_for);
217        add_function!(WaitUpTo, wait_up_to);
218        add_function!(Wake, wake);
219        add_function!(SystemReserveGas, system_reserve_gas);
220        add_function!(ReservationReply, reservation_reply);
221        add_function!(ReservationReplyCommit, reservation_reply_commit);
222        add_function!(ReservationSend, reservation_send);
223        add_function!(ReservationSendCommit, reservation_send_commit);
224        add_function!(SystemBreak, system_break);
225
226        add_function!(Alloc, alloc);
227        add_function!(Free, free);
228        add_function!(FreeRange, free_range);
229    }
230}
231
232#[cfg(feature = "std")]
233struct GlobalsAccessProvider<Ext: Externalities> {
234    instance: Instance<HostState<Ext, BackendMemory<ExecutorMemory>>>,
235    store: Option<Store<HostState<Ext, BackendMemory<ExecutorMemory>>>>,
236}
237
238#[cfg(feature = "std")]
239impl<Ext: Externalities + Send + 'static> GlobalsAccessor for GlobalsAccessProvider<Ext> {
240    fn get_i64(&mut self, name: &LimitedStr) -> Result<i64, GlobalsAccessError> {
241        let store = self.store.as_mut().ok_or(GlobalsAccessError)?;
242        self.instance
243            .get_global_val(store, name.as_str())
244            .and_then(i64::try_from_value)
245            .ok_or(GlobalsAccessError)
246    }
247
248    fn set_i64(&mut self, name: &LimitedStr, value: i64) -> Result<(), GlobalsAccessError> {
249        let store = self.store.as_mut().ok_or(GlobalsAccessError)?;
250        self.instance
251            .set_global_val(store, name.as_str(), Value::I64(value))
252            .map_err(|_| GlobalsAccessError)
253    }
254
255    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
256        self
257    }
258}
259
260impl<EnvExt, EntryPoint> Environment<EnvExt, EntryPoint>
261where
262    EnvExt: BackendExternalities + Send + 'static,
263    EnvExt::UnrecoverableError: BackendSyscallError,
264    RunFallibleError: From<EnvExt::FallibleError>,
265    EnvExt::AllocError: BackendAllocSyscallError<ExtError = EnvExt::UnrecoverableError>,
266    EntryPoint: WasmEntryPoint,
267{
268    pub fn new(
269        ext: EnvExt,
270        binary: &[u8],
271        entry_point: EntryPoint,
272        entries: BTreeSet<DispatchKind>,
273        mem_size: WasmPagesAmount,
274    ) -> Result<Self, EnvironmentError> {
275        use EnvironmentError::*;
276        use SystemEnvironmentError::*;
277
278        let mut store = Store::new(None);
279
280        let mut builder = EnvBuilder::<EnvExt> {
281            env_def_builder: EnvironmentDefinitionBuilder::new(),
282            funcs_count: 0,
283        };
284
285        let memory: BackendMemory<ExecutorMemory> =
286            match ExecutorMemory::new(&mut store, mem_size.into(), None) {
287                Ok(mem) => mem.into(),
288                Err(e) => return Err(System(CreateEnvMemory(e))),
289            };
290
291        builder.add_memory(memory.clone());
292
293        Self::bind_funcs(&mut builder);
294
295        // Check that we have implementations for all the syscalls.
296        // This is intended to panic during any testing, when the
297        // condition is not met.
298        assert_eq!(
299            builder.funcs_count,
300            SyscallName::all().count(),
301            "Not all existing syscalls were added to the module's env."
302        );
303
304        let env_builder: EnvironmentDefinitionBuilder<_> = builder.into();
305
306        *store.data_mut() = Some(State {
307            ext,
308            memory: memory.clone(),
309            termination_reason: ActorTerminationReason::Success.into(),
310        });
311
312        let instance = Instance::new(&mut store, binary, &env_builder).map_err(|e| {
313            Actor(
314                store_host_state_mut(&mut store).ext.gas_amount(),
315                format!("{e:?}"),
316            )
317        })?;
318
319        Ok(Self {
320            instance,
321            entries,
322            entry_point,
323            store,
324            memory,
325        })
326    }
327
328    pub fn execute(
329        self,
330        prepare_memory: impl FnOnce(
331            &mut Store<HostState<EnvExt, BackendMemory<ExecutorMemory>>>,
332            &mut BackendMemory<ExecutorMemory>,
333            GlobalsAccessConfig,
334        ),
335    ) -> EnvironmentExecutionResult<EnvExt> {
336        use EnvironmentError::*;
337        use SystemEnvironmentError::*;
338
339        let Self {
340            mut instance,
341            entries,
342            entry_point,
343            mut store,
344            mut memory,
345        } = self;
346
347        let gas = store_host_state_mut(&mut store)
348            .ext
349            .define_current_counter();
350
351        instance
352            .set_global_val(&mut store, GLOBAL_NAME_GAS, Value::I64(gas as i64))
353            .map_err(|_| System(WrongInjectedGas))?;
354
355        #[cfg(feature = "std")]
356        let mut globals_provider = GlobalsAccessProvider {
357            instance: instance.clone(),
358            store: None,
359        };
360        #[cfg(feature = "std")]
361        let globals_provider_dyn_ref = &mut globals_provider as &mut dyn GlobalsAccessor;
362
363        // Pointer to the globals access provider is valid until the end of `invoke` method.
364        // So, we can safely use it inside lazy-pages and be sure that it points to the valid object.
365        // We cannot guaranty that `store` (and so globals also) will be in a valid state,
366        // because executor mut-borrows `store` during execution. But if it's in a valid state
367        // each moment when protect memory signal can occur, than this trick is pretty safe.
368        #[cfg(feature = "std")]
369        let globals_access_ptr = &globals_provider_dyn_ref as *const _ as HostPointer;
370
371        #[cfg(feature = "std")]
372        let globals_config = GlobalsAccessConfig {
373            access_ptr: globals_access_ptr,
374            access_mod: GlobalsAccessMod::NativeRuntime,
375        };
376
377        #[cfg(not(feature = "std"))]
378        let globals_config = GlobalsAccessConfig {
379            access_ptr: instance.get_instance_ptr(),
380            access_mod: GlobalsAccessMod::WasmRuntime,
381        };
382
383        prepare_memory(&mut store, &mut memory, globals_config);
384
385        let needs_execution = entry_point
386            .try_into_kind()
387            .map(|kind| entries.contains(&kind))
388            .unwrap_or(true);
389
390        let res = if needs_execution {
391            #[cfg(feature = "std")]
392            let res = {
393                let store_option = &mut globals_provider_dyn_ref
394                    .as_any_mut()
395                    .downcast_mut::<GlobalsAccessProvider<EnvExt>>()
396                    // Provider is `GlobalsAccessProvider`, so panic is impossible.
397                    .unwrap_or_else(|| {
398                        let err_msg =
399                            "Environment::execute: Provider must be `GlobalsAccessProvider`";
400
401                        log::error!("{err_msg}");
402                        unreachable!("{err_msg}")
403                    })
404                    .store;
405
406                store_option.replace(store);
407
408                let store_ref = store_option
409                    .as_mut()
410                    // We set store above, so panic is impossible.
411                    .unwrap_or_else(|| {
412                        let err_msg = "Environment::execute: Store must be set before";
413
414                        log::error!("{err_msg}");
415                        unreachable!("{err_msg}")
416                    });
417
418                let res = instance.invoke(store_ref, entry_point.as_entry(), &[]);
419
420                store = globals_provider.store.take().unwrap();
421
422                res
423            };
424
425            #[cfg(not(feature = "std"))]
426            let res = instance.invoke(&mut store, entry_point.as_entry(), &[]);
427
428            res
429        } else {
430            Ok(ReturnValue::Unit)
431        };
432
433        // Fetching global value.
434        let gas = instance
435            .get_global_val(&mut store, GLOBAL_NAME_GAS)
436            .and_then(i64::try_from_value)
437            .ok_or(System(WrongInjectedGas))? as u64;
438
439        let state = store.data_mut().take().unwrap_or_else(|| {
440            let err_msg = "Environment::execute: State must be set";
441
442            log::error!("{err_msg}");
443            unreachable!("{err_msg}")
444        });
445
446        let (ext, termination_reason) = state.terminate(res, gas);
447
448        Ok(BackendReport {
449            termination_reason,
450            store,
451            memory,
452            ext,
453        })
454    }
455}