soroban_env_common/
vmcaller_env.rs

1#![allow(clippy::needless_lifetimes)]
2#[cfg(feature = "wasmi")]
3use crate::xdr::{ScErrorCode, ScErrorType};
4
5use super::{
6    AddressObject, Bool, BytesObject, DurationObject, Error, I128Object, I256Object, I256Val,
7    I64Object, MapObject, MuxedAddressObject, StorageType, StringObject, SymbolObject,
8    TimepointObject, U128Object, U256Object, U256Val, U32Val, U64Object, U64Val, Val, VecObject,
9    Void,
10};
11use crate::call_macro_with_all_host_functions;
12use crate::{CheckedEnvArg, EnvBase, Symbol};
13#[cfg(not(feature = "wasmi"))]
14use core::marker::PhantomData;
15
16/// The VmCallerEnv trait is similar to the Env trait -- it
17/// provides all the same-named methods -- but they have a form that takes an
18/// initial [`VmCaller`] argument by `&mut` that may or may-not wrap a
19/// `wasmi::Caller` structure, depending on whether it was invoked from a wasmi
20/// host-function wrapper.
21///
22/// There is a blanket `impl<T:VmCallerEnv> Env for T` so that any
23/// type (eg. `Host`) that implements `VmCallerEnv` automatically also
24/// implements `Env`, just by calling the corresponding
25/// `VmCallerEnv` method with the [`VmCaller::none()`] argument. This
26/// allows code to import and use `Env` directly (such as the native
27/// contract) to call host methods without having to write `VmCaller::none()`
28/// everywhere.
29#[cfg(feature = "wasmi")]
30pub struct VmCaller<'a, T>(pub Option<wasmi::Caller<'a, T>>);
31#[cfg(feature = "wasmi")]
32impl<'a, T> VmCaller<'a, T> {
33    pub fn none() -> Self {
34        VmCaller(None)
35    }
36    pub fn try_ref(&self) -> Result<&wasmi::Caller<'a, T>, Error> {
37        match &self.0 {
38            Some(caller) => Ok(caller),
39            None => Err(Error::from_type_and_code(
40                ScErrorType::Context,
41                ScErrorCode::InternalError,
42            )),
43        }
44    }
45    pub fn try_mut(&mut self) -> Result<&mut wasmi::Caller<'a, T>, Error> {
46        match &mut self.0 {
47            Some(caller) => Ok(caller),
48            None => Err(Error::from_type_and_code(
49                ScErrorType::Context,
50                ScErrorCode::InternalError,
51            )),
52        }
53    }
54}
55
56#[cfg(not(feature = "wasmi"))]
57pub struct VmCaller<'a, T> {
58    _nothing: PhantomData<&'a T>,
59}
60#[cfg(not(feature = "wasmi"))]
61impl<'a, T> VmCaller<'a, T> {
62    pub fn none() -> Self {
63        VmCaller {
64            _nothing: PhantomData,
65        }
66    }
67}
68
69///////////////////////////////////////////////////////////////////////////////
70/// X-macro use: defining trait VmCallerEnv
71///////////////////////////////////////////////////////////////////////////////
72//
73// This is a helper macro used only by generate_vmcaller_checked_env_trait
74// below. It consumes a token-tree of the form:
75//
76//  {fn $fn_id:ident $args:tt -> $ret:ty}
77//
78// and produces the the corresponding method declaration to be used in the Env
79// trait.
80macro_rules! host_function_helper {
81    {
82        $($min_proto:literal)?, $($max_proto:literal)?,
83        $(#[$attr:meta])*
84        fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty
85    }
86    =>
87    {
88        $(#[$attr])*
89        fn $fn_id(&self, vmcaller: &mut VmCaller<Self::VmUserState>, $($arg:$type),*) -> Result<$ret, Self::Error>;
90    };
91}
92
93// This is a callback macro that pattern-matches the token-tree passed by the
94// x-macro (call_macro_with_all_host_functions) and produces a suite of method
95// declarations, which it places in the body of the declaration of the
96// VmCallerEnv trait.
97macro_rules! generate_vmcaller_checked_env_trait {
98    {
99        $(
100            // This outer pattern matches a single 'mod' block of the token-tree
101            // passed from the x-macro to this macro. It is embedded in a `$()*`
102            // pattern-repetition matcher so that it will match all provided
103            // 'mod' blocks provided.
104            $(#[$mod_attr:meta])*
105            mod $mod_id:ident $mod_str:literal
106            {
107                $(
108                    // This inner pattern matches a single function description
109                    // inside a 'mod' block in the token-tree passed from the
110                    // x-macro to this macro. It is embedded in a `$()*`
111                    // pattern-repetition matcher so that it will match all such
112                    // descriptions.
113                    $(#[$fn_attr:meta])*
114                    { $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
115                )*
116            }
117        )*
118    }
119
120    => // The part of the macro above this line is a matcher; below is its expansion.
121
122    {
123        // This macro expands to a single item: the VmCallerEnv trait
124
125        /// This trait is a variant of the [Env](crate::Env) trait used to
126        /// define the interface implemented by Host. The wasmi VM dispatch
127        /// functions (in soroban_env_host::dispatch) call methods on
128        /// `VmCallerEnv`, passing a [`VmCaller`] that wraps the wasmi Caller
129        /// context, and then convert any `Result::Err(...)` return value into a
130        /// VM trap, halting VM execution.
131        ///
132        /// There is also a blanket `impl<T:VmCallerEnv> Env for T` that
133        /// implements the `Env` for any `VmCallerEnv` by passing
134        /// [`VmCaller::none()`] for the first argument, allowing user code such
135        /// as the native contract to avoid writing `VmCaller::none()`
136        /// everywhere.
137        pub trait VmCallerEnv: EnvBase
138        {
139            type VmUserState;
140            $(
141                $(
142                    // This invokes the host_function_helper! macro above
143                    // passing only the relevant parts of the declaration
144                    // matched by the inner pattern above. It is embedded in two
145                    // nested `$()*` pattern-repetition expanders that
146                    // correspond to the pattern-repetition matchers in the
147                    // match section, but we ignore the structure of the 'mod'
148                    // block repetition-level from the outer pattern in the
149                    // expansion, flattening all functions from all 'mod' blocks
150                    // into the VmCallerEnv trait.
151                    host_function_helper!{$($min_proto)?, $($max_proto)?, $(#[$fn_attr])* fn $fn_id $args -> $ret}
152                )*
153            )*
154        }
155    };
156}
157
158// Here we invoke the x-macro passing generate_env_trait as its callback macro.
159call_macro_with_all_host_functions! { generate_vmcaller_checked_env_trait }
160
161///////////////////////////////////////////////////////////////////////////////
162/// X-macro use: impl<E> Env for VmCallerEnv<E>
163///////////////////////////////////////////////////////////////////////////////
164//
165// This is a helper macro used only by
166// generate_impl_checked_env_for_vmcaller_checked_env below. It consumes a
167// token-tree of the form:
168//
169//  {fn $fn_id:ident $args:tt -> $ret:ty}
170//
171// and produces the the corresponding method declaration to be used in the Env
172// trait.
173macro_rules! vmcaller_none_function_helper {
174    {$($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
175    =>
176    {
177        // We call `augment_err_result` here to give the Env a chance to attach
178        // context (eg. a backtrace) to any error that was generated by code
179        // that didn't have an Env on hand when creating the error. This will at
180        // least localize the error to a given Env call.
181        fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
182            // Check the ledger protocol version against the function-specified
183            // boundaries, this prevents calling a host function using the host
184            // directly as `Env` (i.e. native mode) when the protocol version is
185            // out of bound.
186            $( self.check_protocol_version_lower_bound($min_proto)?; )?
187            $( self.check_protocol_version_upper_bound($max_proto)?; )?
188
189            #[cfg(all(not(target_family = "wasm"), feature = "tracy"))]
190            let _span = tracy_span!(core::stringify!($fn_id));
191            #[cfg(feature = "std")]
192            if self.tracing_enabled()
193            {
194                self.trace_env_call(&core::stringify!($fn_id), &[$(&$arg),*])?;
195            }
196            let res: Result<_, _> = self.augment_err_result(<Self as VmCallerEnv>::$fn_id(self, &mut VmCaller::none(), $($arg.check_env_arg(self)?),*));
197            let res = match res {
198                Ok(ok) => Ok(ok.check_env_arg(self)?),
199                Err(err) => Err(err)
200            };
201            #[cfg(feature = "std")]
202            if self.tracing_enabled()
203            {
204                let dyn_res: Result<&dyn core::fmt::Debug,&Self::Error> = match &res {
205                    Ok(ref ok) => Ok(ok),
206                    Err(err) => Err(err)
207                };
208                self.trace_env_ret(&core::stringify!($fn_id), &dyn_res)?;
209            }
210            res
211        }
212    };
213}
214
215// This is a callback macro that pattern-matches the token-tree passed by the
216// x-macro (call_macro_with_all_host_functions) and produces a suite of method
217// declarations, which it places in the body of the blanket impl of Env for
218// T:Env
219macro_rules! impl_env_for_vmcaller_env {
220    {
221        $(
222            // This outer pattern matches a single 'mod' block of the token-tree
223            // passed from the x-macro to this macro. It is embedded in a `$()*`
224            // pattern-repetition matcher so that it will match all provided
225            // 'mod' blocks provided.
226            $(#[$mod_attr:meta])*
227            mod $mod_id:ident $mod_str:literal
228            {
229                $(
230                    // This inner pattern matches a single function description
231                    // inside a 'mod' block in the token-tree passed from the
232                    // x-macro to this macro. It is embedded in a `$()*`
233                    // pattern-repetition matcher so that it will match all such
234                    // descriptions.
235                    $(#[$fn_attr:meta])*
236                    { $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
237                )*
238            }
239        )*
240    }
241
242    => // The part of the macro above this line is a matcher; below is its expansion.
243
244    {
245        // This macro expands to a single item: a blanket impl that makes all
246        // `VmCallerEnv` types automatically `Env` types, just
247        // passing [`VmCaller::none()`] as their first argument.
248        impl<T:VmCallerEnv> $crate::Env for T
249        {
250            $(
251                $(
252                    // This invokes the vmcaller_none_function_helper! macro above
253                    // passing only the relevant parts of the declaration
254                    // matched by the inner pattern above. It is embedded in two
255                    // nested `$()*` pattern-repetition expanders that
256                    // correspond to the pattern-repetition matchers in the
257                    // match section, but we ignore the structure of the 'mod'
258                    // block repetition-level from the outer pattern in the
259                    // expansion, flattening all functions from all 'mod' blocks
260                    // into the impl.
261                    vmcaller_none_function_helper!{$($min_proto)?, $($max_proto)?, fn $fn_id $args -> $ret}
262                )*
263            )*
264        }
265    };
266}
267
268// Here we invoke the x-macro passing
269// generate_checked_env_for_vmcaller_checked_env as its callback macro.
270call_macro_with_all_host_functions! { impl_env_for_vmcaller_env }