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