soroban_env_host/host/
frame.rs

1use crate::{
2    auth::AuthorizationManagerSnapshot,
3    budget::AsBudget,
4    err,
5    host::{
6        metered_clone::{MeteredClone, MeteredContainer, MeteredIterator},
7        prng::Prng,
8    },
9    storage::{InstanceStorageMap, StorageMap},
10    xdr::{
11        ContractExecutable, ContractIdPreimage, CreateContractArgsV2, Hash, HostFunction,
12        HostFunctionType, ScAddress, ScContractInstance, ScErrorCode, ScErrorType, ScVal,
13    },
14    AddressObject, Error, Host, HostError, Object, Symbol, SymbolStr, TryFromVal, TryIntoVal, Val,
15    Vm, DEFAULT_HOST_DEPTH_LIMIT,
16};
17
18#[cfg(any(test, feature = "testutils"))]
19use core::cell::RefCell;
20use std::rc::Rc;
21
22/// Determines the re-entry mode for calling a contract.
23pub(crate) enum ContractReentryMode {
24    /// Re-entry is completely prohibited.
25    Prohibited,
26    /// Re-entry is allowed, but only directly into the same contract (i.e. it's
27    /// possible for a contract to do a self-call via host).
28    SelfAllowed,
29    /// Re-entry is fully allowed.
30    #[allow(dead_code)]
31    Allowed,
32}
33
34/// All the contract functions starting with double underscore are considered
35/// to be reserved by the Soroban host and can't be directly called by another
36/// contracts.
37const RESERVED_CONTRACT_FN_PREFIX: &str = "__";
38
39/// Saves host state (storage and objects) for rolling back a (sub-)transaction
40/// on error. A helper type used by [`FrameGuard`].
41// Notes on metering: `RollbackPoint` are metered under Frame operations
42// #[derive(Clone)]
43pub(super) struct RollbackPoint {
44    storage: StorageMap,
45    events: usize,
46    auth: AuthorizationManagerSnapshot,
47}
48
49#[cfg(any(test, feature = "testutils"))]
50pub trait ContractFunctionSet {
51    fn call(&self, func: &Symbol, host: &Host, args: &[Val]) -> Option<Val>;
52}
53
54#[cfg(any(test, feature = "testutils"))]
55#[derive(Debug, Clone)]
56pub(crate) struct TestContractFrame {
57    pub(crate) id: Hash,
58    pub(crate) func: Symbol,
59    pub(crate) args: Vec<Val>,
60    pub(crate) panic: Rc<RefCell<Option<Error>>>,
61    pub(crate) instance: ScContractInstance,
62}
63
64#[cfg(any(test, feature = "testutils"))]
65impl std::hash::Hash for TestContractFrame {
66    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
67        self.id.hash(state);
68        self.func.hash(state);
69        self.args.hash(state);
70        if let Some(panic) = self.panic.borrow().as_ref() {
71            panic.hash(state);
72        }
73        self.instance.hash(state);
74    }
75}
76
77#[cfg(any(test, feature = "testutils"))]
78impl TestContractFrame {
79    pub fn new(id: Hash, func: Symbol, args: Vec<Val>, instance: ScContractInstance) -> Self {
80        Self {
81            id,
82            func,
83            args,
84            panic: Rc::new(RefCell::new(None)),
85            instance,
86        }
87    }
88}
89
90/// Context pairs a variable-case [`Frame`] enum with state that's common to all
91/// cases (eg. a [`Prng`]).
92#[derive(Clone, Hash)]
93pub(crate) struct Context {
94    pub(crate) frame: Frame,
95    pub(crate) prng: Option<Prng>,
96    pub(crate) storage: Option<InstanceStorageMap>,
97}
98
99pub(crate) struct CallParams {
100    pub(crate) reentry_mode: ContractReentryMode,
101    pub(crate) internal_host_call: bool,
102    pub(crate) treat_missing_function_as_noop: bool,
103}
104
105impl CallParams {
106    pub(crate) fn default_external_call() -> Self {
107        Self {
108            reentry_mode: ContractReentryMode::Prohibited,
109            internal_host_call: false,
110            treat_missing_function_as_noop: false,
111        }
112    }
113
114    #[allow(unused)]
115    pub(crate) fn default_internal_call() -> Self {
116        Self {
117            reentry_mode: ContractReentryMode::Prohibited,
118            internal_host_call: true,
119            treat_missing_function_as_noop: false,
120        }
121    }
122}
123
124/// Holds contextual information about a single invocation, either
125/// a reference to a contract [`Vm`] or an enclosing [`HostFunction`]
126/// invocation.
127///
128/// Frames are arranged into a stack in [`HostImpl::context`], and are pushed
129/// with [`Host::push_frame`], which returns a [`FrameGuard`] that will
130/// pop the frame on scope-exit.
131///
132/// Frames are also the units of (sub-)transactions: each frame captures
133/// the host state when it is pushed, and the [`FrameGuard`] will either
134/// commit or roll back that state when it pops the stack.
135#[derive(Clone, Hash)]
136pub(crate) enum Frame {
137    ContractVM {
138        vm: Rc<Vm>,
139        fn_name: Symbol,
140        args: Vec<Val>,
141        instance: ScContractInstance,
142        relative_objects: Vec<Object>,
143    },
144    HostFunction(HostFunctionType),
145    StellarAssetContract(Hash, Symbol, Vec<Val>, ScContractInstance),
146    #[cfg(any(test, feature = "testutils"))]
147    TestContract(TestContractFrame),
148}
149
150impl Frame {
151    fn contract_id(&self) -> Option<&Hash> {
152        match self {
153            Frame::ContractVM { vm, .. } => Some(&vm.contract_id),
154            Frame::HostFunction(_) => None,
155            Frame::StellarAssetContract(id, ..) => Some(id),
156            #[cfg(any(test, feature = "testutils"))]
157            Frame::TestContract(tc) => Some(&tc.id),
158        }
159    }
160
161    fn instance(&self) -> Option<&ScContractInstance> {
162        match self {
163            Frame::ContractVM { instance, .. } => Some(instance),
164            Frame::HostFunction(_) => None,
165            Frame::StellarAssetContract(_, _, _, instance) => Some(instance),
166            #[cfg(any(test, feature = "testutils"))]
167            Frame::TestContract(tc) => Some(&tc.instance),
168        }
169    }
170    #[cfg(any(test, feature = "testutils"))]
171    fn is_contract_vm(&self) -> bool {
172        matches!(self, Frame::ContractVM { .. })
173    }
174}
175
176impl Host {
177    /// Returns if the host currently has a frame on the stack.
178    ///
179    /// A frame being on the stack usually indicates that a contract is currently
180    /// executing, or is in a state just-before or just-after executing.
181    pub fn has_frame(&self) -> Result<bool, HostError> {
182        self.with_current_frame_opt(|opt| Ok(opt.is_some()))
183    }
184
185    /// Helper function for [`Host::with_frame`] below. Pushes a new [`Context`]
186    /// on the context stack, returning a [`RollbackPoint`] such that if
187    /// operation fails, it can be used to roll the [`Host`] back to the state
188    /// it had before its associated [`Context`] was pushed.
189    pub(super) fn push_context(&self, ctx: Context) -> Result<RollbackPoint, HostError> {
190        let _span = tracy_span!("push context");
191        let auth_manager = self.try_borrow_authorization_manager()?;
192        let auth_snapshot = auth_manager.push_frame(self, &ctx.frame)?;
193        // Establish the rp first, since this might run out of gas and fail.
194        let rp = RollbackPoint {
195            storage: self.try_borrow_storage()?.map.metered_clone(self)?,
196            events: self.try_borrow_events()?.vec.len(),
197            auth: auth_snapshot,
198        };
199        // Charge for the push, which might also run out of gas.
200        Vec::<Context>::charge_bulk_init_cpy(1, self.as_budget())?;
201        // Finally commit to doing the push.
202        self.try_borrow_context_stack_mut()?.push(ctx);
203        Ok(rp)
204    }
205
206    /// Helper function for [`Host::with_frame`] below. Pops a [`Context`] off
207    /// the current context stack and optionally rolls back the [`Host`]'s objects
208    /// and storage map to the state in the provided [`RollbackPoint`].
209    pub(super) fn pop_context(&self, orp: Option<RollbackPoint>) -> Result<Context, HostError> {
210        let _span = tracy_span!("pop context");
211
212        let ctx = self.try_borrow_context_stack_mut()?.pop();
213
214        #[cfg(any(test, feature = "recording_mode"))]
215        if self.try_borrow_context_stack()?.is_empty() {
216            // When there are no contexts left, emulate authentication for the
217            // recording auth mode. This is a no-op for the enforcing mode.
218            self.try_borrow_authorization_manager()?
219                .maybe_emulate_authentication(self)?;
220            // See explanation for this line in [crate::vm::Vm::parse_module] -- it exists
221            // to add-back module-parsing costs that were suppressed during the invocation.
222            if self.in_storage_recording_mode()? {
223                if *self.try_borrow_need_to_build_module_cache()? {
224                    // Host function calls that upload Wasm and create contracts
225                    // don't use the module cache and thus don't need to have it
226                    // rebuilt.
227                    self.rebuild_module_cache()?;
228                }
229            }
230            // Reset the flag for building the module cache. This is only relevant
231            // for tests that keep reusing the same host for several invocations.
232            *(self.try_borrow_need_to_build_module_cache_mut()?) = false;
233        }
234        let mut auth_snapshot = None;
235        if let Some(rp) = orp {
236            self.try_borrow_storage_mut()?.map = rp.storage;
237            self.try_borrow_events_mut()?.rollback(rp.events)?;
238            auth_snapshot = Some(rp.auth);
239        }
240        self.try_borrow_authorization_manager()?
241            .pop_frame(self, auth_snapshot)?;
242        ctx.ok_or_else(|| {
243            self.err(
244                ScErrorType::Context,
245                ScErrorCode::InternalError,
246                "unmatched host context push/pop",
247                &[],
248            )
249        })
250    }
251
252    /// Applies a function to the top [`Frame`] of the context stack. Returns
253    /// [`HostError`] if the context stack is empty, otherwise returns result of
254    /// function call.
255    //
256    // Notes on metering: aquiring the current frame is cheap and not charged.
257    // Metering happens in the passed-in closure where actual work is being done.
258    pub(super) fn with_current_frame<F, U>(&self, f: F) -> Result<U, HostError>
259    where
260        F: FnOnce(&Frame) -> Result<U, HostError>,
261    {
262        let Ok(context_guard) = self.0.context_stack.try_borrow() else {
263            return Err(self.err(
264                ScErrorType::Context,
265                ScErrorCode::InternalError,
266                "context is already borrowed",
267                &[],
268            ));
269        };
270
271        if let Some(context) = context_guard.last() {
272            f(&context.frame)
273        } else {
274            drop(context_guard);
275            Err(self.err(
276                ScErrorType::Context,
277                ScErrorCode::InternalError,
278                "no contract running",
279                &[],
280            ))
281        }
282    }
283
284    /// Applies a function to a mutable reference to the top [`Context`] of the
285    /// context stack. Returns [`HostError`] if the context stack is empty,
286    /// otherwise returns result of function call.
287    //
288    // Notes on metering: aquiring the current frame is cheap and not charged.
289    // Metering happens in the passed-in closure where actual work is being done.
290    pub(super) fn with_current_context_mut<F, U>(&self, f: F) -> Result<U, HostError>
291    where
292        F: FnOnce(&mut Context) -> Result<U, HostError>,
293    {
294        let Ok(mut context_guard) = self.0.context_stack.try_borrow_mut() else {
295            return Err(self.err(
296                ScErrorType::Context,
297                ScErrorCode::InternalError,
298                "context is already borrowed",
299                &[],
300            ));
301        };
302        if let Some(context) = context_guard.last_mut() {
303            f(context)
304        } else {
305            drop(context_guard);
306            Err(self.err(
307                ScErrorType::Context,
308                ScErrorCode::InternalError,
309                "no contract running",
310                &[],
311            ))
312        }
313    }
314
315    /// Same as [`Self::with_current_frame`] but passes `None` when there is no current
316    /// frame, rather than failing with an error.
317    pub(crate) fn with_current_frame_opt<F, U>(&self, f: F) -> Result<U, HostError>
318    where
319        F: FnOnce(Option<&Frame>) -> Result<U, HostError>,
320    {
321        let Ok(context_guard) = self.0.context_stack.try_borrow() else {
322            return Err(self.err(
323                ScErrorType::Context,
324                ScErrorCode::InternalError,
325                "context is already borrowed",
326                &[],
327            ));
328        };
329        if let Some(context) = context_guard.last() {
330            f(Some(&context.frame))
331        } else {
332            drop(context_guard);
333            f(None)
334        }
335    }
336
337    pub(crate) fn with_current_frame_relative_object_table<F, U>(
338        &self,
339        f: F,
340    ) -> Result<U, HostError>
341    where
342        F: FnOnce(&mut Vec<Object>) -> Result<U, HostError>,
343    {
344        self.with_current_context_mut(|ctx| {
345            if let Frame::ContractVM {
346                relative_objects, ..
347            } = &mut ctx.frame
348            {
349                f(relative_objects)
350            } else {
351                Err(self.err(
352                    ScErrorType::Context,
353                    ScErrorCode::InternalError,
354                    "accessing relative object table in non-VM frame",
355                    &[],
356                ))
357            }
358        })
359    }
360
361    pub(crate) fn with_current_prng<F, U>(&self, f: F) -> Result<U, HostError>
362    where
363        F: FnOnce(&mut Prng) -> Result<U, HostError>,
364    {
365        // We mem::take the context's PRNG into a local variable and then put it
366        // back when we're done. This allows the callback to borrow the context
367        // to report errors if anything goes wrong in it. If the callback also
368        // installs a PRNG of its own (it shouldn't!) we notice when putting the
369        // context's PRNG back and fail with an internal error.
370        let curr_prng_opt =
371            self.with_current_context_mut(|ctx| Ok(std::mem::take(&mut ctx.prng)))?;
372
373        let mut curr_prng = match curr_prng_opt {
374            // There's already a context PRNG, so use it.
375            Some(prng) => prng,
376
377            // There's no context PRNG yet, seed one from the base PRNG (unless
378            // the base PRNG itself hasn't been seeded).
379            None => {
380                let mut base_guard = self.try_borrow_base_prng_mut()?;
381                if let Some(base) = base_guard.as_mut() {
382                    base.sub_prng(self.as_budget())?
383                } else {
384                    return Err(self.err(
385                        ScErrorType::Context,
386                        ScErrorCode::InternalError,
387                        "host base PRNG was not seeded",
388                        &[],
389                    ));
390                }
391            }
392        };
393
394        // Call the callback with the new-or-existing context PRNG.
395        let res: Result<U, HostError> = f(&mut curr_prng);
396
397        // Put the (possibly newly-initialized frame PRNG-option back)
398        self.with_current_context_mut(|ctx| {
399            if ctx.prng.is_some() {
400                return Err(self.err(
401                    ScErrorType::Context,
402                    ScErrorCode::InternalError,
403                    "callback re-entered with_current_prng",
404                    &[],
405                ));
406            }
407            ctx.prng = Some(curr_prng);
408            Ok(())
409        })?;
410        res
411    }
412
413    /// Pushes a [`Frame`], runs a closure, and then pops the frame, rolling back
414    /// if the closure returned an error. Returns the result that the closure
415    /// returned (or any error caused during the frame push/pop).
416    pub(crate) fn with_frame<F>(&self, frame: Frame, f: F) -> Result<Val, HostError>
417    where
418        F: FnOnce() -> Result<Val, HostError>,
419    {
420        let start_depth = self.try_borrow_context_stack()?.len();
421        if start_depth as u32 >= DEFAULT_HOST_DEPTH_LIMIT {
422            return Err(Error::from_type_and_code(
423                ScErrorType::Context,
424                ScErrorCode::ExceededLimit,
425            )
426            .into());
427        }
428        #[cfg(any(test, feature = "testutils"))]
429        {
430            if let Some(ctx) = self.try_borrow_context_stack()?.last() {
431                if frame.is_contract_vm() && ctx.frame.is_contract_vm() {
432                    if let Ok(mut scoreboard) = self.try_borrow_coverage_scoreboard_mut() {
433                        scoreboard.vm_to_vm_calls += 1;
434                    }
435                }
436            }
437        }
438        let ctx = Context {
439            frame,
440            prng: None,
441            storage: None,
442        };
443        let rp = self.push_context(ctx)?;
444        {
445            // We do this _after_ the context is pushed, in order to let the
446            // observation code assume a context exists
447            if let Some(ctx) = self.try_borrow_context_stack()?.last() {
448                self.call_any_lifecycle_hook(crate::host::TraceEvent::PushCtx(ctx))?;
449            }
450        }
451        #[cfg(any(test, feature = "testutils"))]
452        let mut is_top_contract_invocation = false;
453        #[cfg(any(test, feature = "testutils"))]
454        {
455            if self.try_borrow_context_stack()?.len() == 1 {
456                if let Some(ctx) = self.try_borrow_context_stack()?.first() {
457                    match ctx.frame {
458                        // Don't call the contract invocation hook for
459                        // the host functions.
460                        Frame::HostFunction(_) => (),
461                        // Everything else is some sort of contract call.
462                        _ => {
463                            is_top_contract_invocation = true;
464                            if let Some(contract_invocation_hook) =
465                                self.try_borrow_top_contract_invocation_hook()?.as_ref()
466                            {
467                                contract_invocation_hook(
468                                    self,
469                                    crate::host::ContractInvocationEvent::Start,
470                                );
471                            }
472                        }
473                    }
474                }
475            }
476        }
477
478        let res = f();
479        let mut res = if let Ok(v) = res {
480            // If a contract function happens to have signature Result<...,
481            // Code> its Wasm ABI encoding will be ambiguous: if it exits with
482            // Err(Code) it'll wind up exiting the Wasm VM "successfully" with a
483            // Val that's of type Error, we'll get Ok(Error) here. To allow this
484            // to work and avoid losing errors, we define _any_ successful
485            // return of Ok(Error) as "a contract failure"; contracts aren't
486            // allowed to return Ok(Error) and have it considered actually-ok.
487            //
488            // (If we were called from try_call, it will actually turn this
489            // Err(ScErrorType::Contract) back into Ok(ScErrorType::Contract)
490            // since that is a "recoverable" type of error.)
491            if let Ok(err) = Error::try_from(v) {
492                // Unfortunately there are still two sub-cases to consider. One
493                // is when a contract returns Ok(Error) with
494                // ScErrorType::Contract, which is allowed and legitimate and
495                // "how a contract would signal a Result::Err(Code) as described
496                // above". In this (good) case we propagate the Error they
497                // provided, just switching it from Ok(Error) to Err(Error)
498                // indicating that the contract "failed" with this Error.
499                //
500                // The second (bad) case is when the contract returns Ok(Error)
501                // with a non-ScErrorType::Contract. This might be some kind of
502                // mistake on their part but it might also be an attempt at
503                // spoofing error reporting, by claiming some subsystem of the
504                // host failed when it really didn't. In particular if a
505                // contract wants to forcibly fail a caller that did `try_call`,
506                // the contract could spoof-return an unrecoverable Error code
507                // like InternalError or BudgetExceeded. We want to deny all
508                // such cases, so we just define them as illegal returns, and
509                // report them all as a specific error type of and description
510                // our own choosing: not a contract's own logic failing, but a
511                // contract failing to live up to a postcondition we're
512                // enforcing of "never returning this sort of error code".
513                if err.is_type(ScErrorType::Contract) {
514                    Err(self.error(
515                        err,
516                        "escalating Ok(ScErrorType::Contract) frame-exit to Err",
517                        &[],
518                    ))
519                } else {
520                    Err(self.err(
521                        ScErrorType::Context,
522                        ScErrorCode::InvalidAction,
523                        "frame-exit with Ok(Error) carrying a non-ScErrorType::Contract Error",
524                        &[err.to_val()],
525                    ))
526                }
527            } else {
528                Ok(v)
529            }
530        } else {
531            res
532        };
533
534        // We try flushing instance storage at the end of the frame if nothing
535        // else failed. Unfortunately flushing instance storage is _itself_
536        // fallible in a variety of ways, and if it fails we want to roll back
537        // everything else.
538        if res.is_ok() {
539            if let Err(e) = self.persist_instance_storage() {
540                res = Err(e)
541            }
542        }
543        {
544            // We do this _before_ the context is popped, in order to let the
545            // observation code assume a context exists
546            if let Some(ctx) = self.try_borrow_context_stack()?.last() {
547                let res = match &res {
548                    Ok(v) => Ok(*v),
549                    Err(ref e) => Err(e),
550                };
551                self.call_any_lifecycle_hook(crate::host::TraceEvent::PopCtx(&ctx, &res))?;
552            }
553        }
554        if res.is_err() {
555            // Pop and rollback on error.
556            self.pop_context(Some(rp))?
557        } else {
558            // Just pop on success.
559            self.pop_context(None)?
560        };
561        // Every push and pop should be matched; if not there is a bug.
562        let end_depth = self.try_borrow_context_stack()?.len();
563        if start_depth != end_depth {
564            return Err(err!(
565                self,
566                (ScErrorType::Context, ScErrorCode::InternalError),
567                "frame-depth mismatch",
568                start_depth,
569                end_depth
570            ));
571        }
572        #[cfg(any(test, feature = "testutils"))]
573        if end_depth == 0 {
574            // Empty call stack in tests means that some contract function call
575            // has been finished and hence the authorization manager can be reset.
576            // In non-test scenarios, there should be no need to ever reset
577            // the authorization manager as the host instance shouldn't be
578            // shared between the contract invocations.
579            *self.try_borrow_previous_authorization_manager_mut()? =
580                Some(self.try_borrow_authorization_manager()?.clone());
581            self.try_borrow_authorization_manager_mut()?.reset();
582
583            // Call the contract invocation hook for contract invocations only.
584            if is_top_contract_invocation {
585                if let Some(top_contract_invocation_hook) =
586                    self.try_borrow_top_contract_invocation_hook()?.as_ref()
587                {
588                    top_contract_invocation_hook(
589                        self,
590                        crate::host::ContractInvocationEvent::Finish,
591                    );
592                }
593            }
594        }
595        res
596    }
597
598    /// Inspects the frame at the top of the context and returns the contract ID
599    /// if it exists. Returns `Ok(None)` if the context stack is empty or has a
600    /// non-contract frame on top.
601    pub(crate) fn get_current_contract_id_opt_internal(&self) -> Result<Option<Hash>, HostError> {
602        self.with_current_frame_opt(|opt_frame| match opt_frame {
603            Some(frame) => frame
604                .contract_id()
605                .map(|id| id.metered_clone(self))
606                .transpose(),
607            None => Ok(None),
608        })
609    }
610
611    /// Returns [`Hash`] contract ID from the VM frame at the top of the context
612    /// stack, or a [`HostError`] if the context stack is empty or has a non-VM
613    /// frame at its top.
614    pub(crate) fn get_current_contract_id_internal(&self) -> Result<Hash, HostError> {
615        if let Some(id) = self.get_current_contract_id_opt_internal()? {
616            Ok(id)
617        } else {
618            // This should only ever happen if we try to access the contract ID
619            // from a HostFunction frame (meaning before a contract is running).
620            // Doing so is a logic bug on our part. If we simply run out of
621            // budget while cloning the Hash we won't get here, the `?` above
622            // will propagate the budget error.
623            Err(self.err(
624                ScErrorType::Context,
625                ScErrorCode::InternalError,
626                "Current context has no contract ID",
627                &[],
628            ))
629        }
630    }
631
632    /// Pushes a test contract [`Frame`], runs a closure, and then pops the
633    /// frame, rolling back if the closure returned an error. Returns the result
634    /// that the closure returned (or any error caused during the frame
635    /// push/pop). Used for testing.
636    #[cfg(any(test, feature = "testutils"))]
637    pub fn with_test_contract_frame<F>(
638        &self,
639        id: Hash,
640        func: Symbol,
641        f: F,
642    ) -> Result<Val, HostError>
643    where
644        F: FnOnce() -> Result<Val, HostError>,
645    {
646        let _invocation_meter_scope = self.maybe_meter_invocation()?;
647        self.with_frame(
648            Frame::TestContract(self.create_test_contract_frame(id, func, vec![])?),
649            f,
650        )
651    }
652
653    #[cfg(any(test, feature = "testutils"))]
654    fn create_test_contract_frame(
655        &self,
656        id: Hash,
657        func: Symbol,
658        args: Vec<Val>,
659    ) -> Result<TestContractFrame, HostError> {
660        let instance_key = self.contract_instance_ledger_key(&id)?;
661        let instance = self.retrieve_contract_instance_from_storage(&instance_key)?;
662        Ok(TestContractFrame::new(id, func, args.to_vec(), instance))
663    }
664
665    // Notes on metering: this is covered by the called components.
666    fn call_contract_fn(
667        &self,
668        id: &Hash,
669        func: &Symbol,
670        args: &[Val],
671        treat_missing_function_as_noop: bool,
672    ) -> Result<Val, HostError> {
673        // Create key for storage
674        let storage_key = self.contract_instance_ledger_key(id)?;
675        let instance = self.retrieve_contract_instance_from_storage(&storage_key)?;
676        Vec::<Val>::charge_bulk_init_cpy(args.len() as u64, self.as_budget())?;
677        let args_vec = args.to_vec();
678        match &instance.executable {
679            ContractExecutable::Wasm(wasm_hash) => {
680                let vm = self.instantiate_vm(id, wasm_hash)?;
681                let relative_objects = Vec::new();
682                self.with_frame(
683                    Frame::ContractVM {
684                        vm: Rc::clone(&vm),
685                        fn_name: *func,
686                        args: args_vec,
687                        instance,
688                        relative_objects,
689                    },
690                    || vm.invoke_function_raw(self, func, args, treat_missing_function_as_noop),
691                )
692            }
693            ContractExecutable::StellarAsset => self.with_frame(
694                Frame::StellarAssetContract(id.metered_clone(self)?, *func, args_vec, instance),
695                || {
696                    use crate::builtin_contracts::{BuiltinContract, StellarAssetContract};
697                    StellarAssetContract.call(func, self, args)
698                },
699            ),
700        }
701    }
702
703    fn instantiate_vm(&self, id: &Hash, wasm_hash: &Hash) -> Result<Rc<Vm>, HostError> {
704        #[cfg(any(test, feature = "recording_mode"))]
705        {
706            if !self.in_storage_recording_mode()? {
707                self.build_module_cache_if_needed()?;
708            } else {
709                *(self.try_borrow_need_to_build_module_cache_mut()?) = true;
710            }
711        }
712        #[cfg(not(any(test, feature = "recording_mode")))]
713        self.build_module_cache_if_needed()?;
714        let contract_id = id.metered_clone(self)?;
715        let parsed_module = if let Some(cache) = &*self.try_borrow_module_cache()? {
716            // Check that storage thinks the entry exists before
717            // checking the cache: this seems like overkill but it
718            // provides some future-proofing, see below.
719            let wasm_key = self.contract_code_ledger_key(wasm_hash)?;
720            if self
721                .try_borrow_storage_mut()?
722                .has_with_host(&wasm_key, self, None)?
723            {
724                cache.get_module(self, wasm_hash)?
725            } else {
726                None
727            }
728        } else {
729            None
730        };
731        if let Some(module) = parsed_module {
732            return Vm::from_parsed_module(self, contract_id, module);
733        };
734        // We can get here a few ways:
735        //
736        //   1. We are running/replaying a protocol that has no
737        //      module cache.
738        //
739        //   2. We have a module cache, but it somehow doesn't have
740        //      the module requested. This in turn has two
741        //      sub-cases:
742        //
743        //     - User invoked us with bad input, eg. calling a
744        //       contract that wasn't provided in footprint/storage.
745        //
746        //     - User uploaded the wasm _in this transaction_ so we
747        //       didn't cache it when starting the transaction (and
748        //       couldn't due to wasmi locking its engine while
749        //       running).
750        //
751        //   3. Even more pathological: the module cache was built,
752        //      and contained the module, but someone _removed_ the
753        //      wasm from storage after the the cache was built
754        //      (this is not currently possible from guest code, but
755        //      we do some future-proofing here in case it becomes
756        //      possible). This is the case we handle above with the
757        //      early check for storage.has(wasm_key) before
758        //      checking the cache as well.
759        //
760        // In all these cases, we want to try accessing storage, and
761        // if it has the wasm, make a _throwaway_ module with its
762        // own engine. If it doesn't have the wasm, we want to fail
763        // with a storage error.
764
765        let (code, costs) = self.retrieve_wasm_from_storage(&wasm_hash)?;
766
767        #[cfg(any(test, feature = "recording_mode"))]
768        // In recording mode: if a contract was present in the initial snapshot image, it is part of
769        // the set of contracts that would have been built into a module cache in enforcing mode;
770        // we want to defer the cost of parsing those (simulating them as cache hits) and then charge
771        // once for each such contract the simulated-module-cache-build that happens at the end of
772        // the frame in [`Self::pop_context`].
773        //
774        // If a contract is _not_ in the initial snapshot image, it's because someone just uploaded
775        // it during execution. Those would be cache misses in enforcing mode, and so it is right to
776        // continue to charge for them as such (charging the parse cost on each call) in recording.
777        let cost_mode = if self.in_storage_recording_mode()? {
778            let contact_code_key = self
779                .budget_ref()
780                .with_observable_shadow_mode(|| self.contract_code_ledger_key(wasm_hash))?;
781            if self
782                .try_borrow_storage()?
783                .get_snapshot_value(self, &contact_code_key)?
784                .is_some()
785            {
786                crate::vm::ModuleParseCostMode::PossiblyDeferredIfRecording
787            } else {
788                crate::vm::ModuleParseCostMode::Normal
789            }
790        } else {
791            crate::vm::ModuleParseCostMode::Normal
792        };
793        #[cfg(not(any(test, feature = "recording_mode")))]
794        let cost_mode = crate::vm::ModuleParseCostMode::Normal;
795
796        Vm::new_with_cost_inputs(self, contract_id, code.as_slice(), costs, cost_mode)
797    }
798
799    pub(crate) fn get_contract_protocol_version(
800        &self,
801        contract_id: &Hash,
802    ) -> Result<u32, HostError> {
803        #[cfg(any(test, feature = "testutils"))]
804        if self.is_test_contract_executable(contract_id)? {
805            return self.get_ledger_protocol_version();
806        }
807        let storage_key = self.contract_instance_ledger_key(contract_id)?;
808        let instance = self.retrieve_contract_instance_from_storage(&storage_key)?;
809        match &instance.executable {
810            ContractExecutable::Wasm(wasm_hash) => {
811                let vm = self.instantiate_vm(contract_id, wasm_hash)?;
812                Ok(vm.module.proto_version)
813            }
814            ContractExecutable::StellarAsset => self.get_ledger_protocol_version(),
815        }
816    }
817
818    // Notes on metering: this is covered by the called components.
819    pub(crate) fn call_n_internal(
820        &self,
821        id: &Hash,
822        func: Symbol,
823        args: &[Val],
824        call_params: CallParams,
825    ) -> Result<Val, HostError> {
826        // Internal host calls may call some special functions that otherwise
827        // aren't allowed to be called.
828        if !call_params.internal_host_call
829            && SymbolStr::try_from_val(self, &func)?
830                .to_string()
831                .as_str()
832                .starts_with(RESERVED_CONTRACT_FN_PREFIX)
833        {
834            return Err(self.err(
835                ScErrorType::Context,
836                ScErrorCode::InvalidAction,
837                "can't invoke a reserved function directly",
838                &[func.to_val()],
839            ));
840        }
841
842        if !matches!(call_params.reentry_mode, ContractReentryMode::Allowed) {
843            let reentry_distance = self
844                .try_borrow_context_stack()?
845                .iter()
846                .rev()
847                .filter_map(|c| c.frame.contract_id())
848                .position(|caller| caller == id);
849
850            match (call_params.reentry_mode, reentry_distance) {
851                // Non-reentrant calls, or calls in Allowed mode,
852                // or immediate-reentry calls in SelfAllowed mode
853                // are all acceptable.
854                (_, None)
855                | (ContractReentryMode::Allowed, _)
856                | (ContractReentryMode::SelfAllowed, Some(0)) => (),
857
858                // But any non-immediate-reentry in SelfAllowed mode,
859                // or any reentry at all in Prohibited mode, are errors.
860                (ContractReentryMode::SelfAllowed, Some(_))
861                | (ContractReentryMode::Prohibited, Some(_)) => {
862                    return Err(self.err(
863                        ScErrorType::Context,
864                        ScErrorCode::InvalidAction,
865                        "Contract re-entry is not allowed",
866                        &[],
867                    ));
868                }
869            }
870        }
871
872        self.fn_call_diagnostics(id, &func, args);
873
874        // Try dispatching the contract to the compiled-in registred
875        // implmentation. Only the contracts with the special (empty) executable
876        // are dispatched in this way, so that it's possible to switch the
877        // compiled-in implementation back to Wasm via
878        // `update_current_contract_wasm`.
879        // "testutils" is not covered by budget metering.
880        #[cfg(any(test, feature = "testutils"))]
881        if self.is_test_contract_executable(id)? {
882            // This looks a little un-idiomatic, but this avoids maintaining a borrow of
883            // self.0.contracts. Implementing it as
884            //
885            //     if let Some(cfs) = self.try_borrow_contracts()?.get(&id).cloned() { ... }
886            //
887            // maintains a borrow of self.0.contracts, which can cause borrow errors.
888            let cfs_option = self.try_borrow_contracts()?.get(&id).cloned();
889            if let Some(cfs) = cfs_option {
890                let frame = self.create_test_contract_frame(id.clone(), func, args.to_vec())?;
891                let panic = frame.panic.clone();
892                return self.with_frame(Frame::TestContract(frame), || {
893                    use std::any::Any;
894                    use std::panic::AssertUnwindSafe;
895                    type PanicVal = Box<dyn Any + Send>;
896
897                    // We're directly invoking a native rust contract here,
898                    // which we allow only in local testing scenarios, and we
899                    // want it to behave as close to the way it would behave if
900                    // the contract were actually compiled to WASM and running
901                    // in a VM.
902                    //
903                    // In particular: if the contract function panics, if it
904                    // were WASM it would cause the VM to trap, so we do
905                    // something "as similar as we can" in the native case here,
906                    // catch the native panic and attempt to continue by
907                    // translating the panic back to an error, so that
908                    // `with_frame` will rollback the host to its pre-call state
909                    // (as best it can) and propagate the error to its caller
910                    // (which might be another contract doing try_call).
911                    //
912                    // This is somewhat best-effort, but it's compiled-out when
913                    // building a host for production use, so we're willing to
914                    // be a bit forgiving.
915                    let closure = AssertUnwindSafe(move || cfs.call(&func, self, args));
916                    let res: Result<Option<Val>, PanicVal> =
917                        crate::testutils::call_with_suppressed_panic_hook(closure);
918                    match res {
919                        Ok(Some(val)) => {
920                            self.fn_return_diagnostics(id, &func, &val);
921                            Ok(val)
922                        }
923                        Ok(None) => {
924                            if call_params.treat_missing_function_as_noop {
925                                Ok(Val::VOID.into())
926                            } else {
927                                Err(self.err(
928                                    ScErrorType::Context,
929                                    ScErrorCode::MissingValue,
930                                    "calling unknown contract function",
931                                    &[func.to_val()],
932                                ))
933                            }
934                        }
935                        Err(panic_payload) => {
936                            // Return an error indicating the contract function
937                            // panicked.
938                            //
939                            // If it was a panic generated by a Env-upgraded
940                            // HostError, it had its `Error` captured by
941                            // `VmCallerEnv::escalate_error_to_panic`: fish the
942                            // `Error` stored in the frame back out and
943                            // propagate it.
944                            //
945                            // If it was a panic generated by user code calling
946                            // panic!(...) we won't retrieve such a stored
947                            // `Error`. Since we're trying to emulate
948                            // what-the-VM-would-do here, and the VM traps with
949                            // an unreachable error on contract panic, we
950                            // generate same error (by converting a wasm
951                            // trap-unreachable code). It's a little weird
952                            // because we're not actually running a VM, but we
953                            // prioritize emulation fidelity over honesty here.
954                            let mut error: Error =
955                                Error::from(wasmi::core::TrapCode::UnreachableCodeReached);
956
957                            let mut recovered_error_from_panic_refcell = false;
958                            if let Ok(panic) = panic.try_borrow() {
959                                if let Some(err) = *panic {
960                                    recovered_error_from_panic_refcell = true;
961                                    error = err;
962                                }
963                            }
964
965                            // If we didn't manage to recover a structured error
966                            // code from the frame's refcell, and we're allowed
967                            // to record dynamic strings (which happens when
968                            // diagnostics are active), and we got a panic
969                            // payload of a simple string, log that panic
970                            // payload into the diagnostic event buffer. This
971                            // code path will get hit when contracts do
972                            // `panic!("some string")` in native testing mode.
973                            if !recovered_error_from_panic_refcell {
974                                self.with_debug_mode(|| {
975                                    if let Some(str) = panic_payload.downcast_ref::<&str>() {
976                                        let msg: String = format!(
977                                            "caught panic '{}' from contract function '{:?}'",
978                                            str, func
979                                        );
980                                        let _ = self.log_diagnostics(&msg, args);
981                                    } else if let Some(str) = panic_payload.downcast_ref::<String>()
982                                    {
983                                        let msg: String = format!(
984                                            "caught panic '{}' from contract function '{:?}'",
985                                            str, func
986                                        );
987                                        let _ = self.log_diagnostics(&msg, args);
988                                    };
989                                    Ok(())
990                                })
991                            }
992                            Err(self.error(error, "caught error from function", &[]))
993                        }
994                    }
995                });
996            }
997        }
998
999        let res =
1000            self.call_contract_fn(id, &func, args, call_params.treat_missing_function_as_noop);
1001
1002        match &res {
1003            Ok(res) => self.fn_return_diagnostics(id, &func, res),
1004            Err(_err) => {}
1005        }
1006
1007        res
1008    }
1009
1010    // Notes on metering: covered by the called components.
1011    fn invoke_function_and_return_val(&self, hf: HostFunction) -> Result<Val, HostError> {
1012        let hf_type = hf.discriminant();
1013        let frame = Frame::HostFunction(hf_type);
1014        match hf {
1015            HostFunction::InvokeContract(invoke_args) => {
1016                self.with_frame(frame, || {
1017                    // Metering: conversions to host objects are covered.
1018                    let ScAddress::Contract(ref contract_id) = invoke_args.contract_address else {
1019                        return Err(self.err(
1020                            ScErrorType::Value,
1021                            ScErrorCode::UnexpectedType,
1022                            "invoked address doesn't belong to a contract",
1023                            &[],
1024                        ));
1025                    };
1026                    let function_name: Symbol = invoke_args.function_name.try_into_val(self)?;
1027                    let args = self.scvals_to_val_vec(invoke_args.args.as_slice())?;
1028                    self.call_n_internal(
1029                        contract_id,
1030                        function_name,
1031                        args.as_slice(),
1032                        CallParams::default_external_call(),
1033                    )
1034                })
1035            }
1036            HostFunction::CreateContract(args) => self.with_frame(frame, || {
1037                let deployer: Option<AddressObject> = match &args.contract_id_preimage {
1038                    ContractIdPreimage::Address(preimage_from_addr) => {
1039                        Some(self.add_host_object(preimage_from_addr.address.metered_clone(self)?)?)
1040                    }
1041                    ContractIdPreimage::Asset(_) => None,
1042                };
1043                self.create_contract_internal(
1044                    deployer,
1045                    CreateContractArgsV2 {
1046                        contract_id_preimage: args.contract_id_preimage,
1047                        executable: args.executable,
1048                        constructor_args: Default::default(),
1049                    },
1050                    vec![],
1051                )
1052                .map(<Val>::from)
1053            }),
1054            HostFunction::CreateContractV2(args) => self.with_frame(frame, || {
1055                let deployer: Option<AddressObject> = match &args.contract_id_preimage {
1056                    ContractIdPreimage::Address(preimage_from_addr) => {
1057                        Some(self.add_host_object(preimage_from_addr.address.metered_clone(self)?)?)
1058                    }
1059                    ContractIdPreimage::Asset(_) => None,
1060                };
1061                let arg_vals = self.scvals_to_val_vec(args.constructor_args.as_slice())?;
1062                self.create_contract_internal(deployer, args, arg_vals)
1063                    .map(<Val>::from)
1064            }),
1065            HostFunction::UploadContractWasm(wasm) => self.with_frame(frame, || {
1066                self.upload_contract_wasm(wasm.to_vec()).map(<Val>::from)
1067            }),
1068        }
1069    }
1070
1071    // Notes on metering: covered by the called components.
1072    pub fn invoke_function(&self, hf: HostFunction) -> Result<ScVal, HostError> {
1073        #[cfg(any(test, feature = "testutils"))]
1074        let _invocation_meter_scope = self.maybe_meter_invocation()?;
1075
1076        let rv = self.invoke_function_and_return_val(hf)?;
1077        self.from_host_val(rv)
1078    }
1079
1080    pub(crate) fn maybe_init_instance_storage(&self, ctx: &mut Context) -> Result<(), HostError> {
1081        // Lazily initialize the storage on first access - it's not free and
1082        // not every contract will use it.
1083        if ctx.storage.is_some() {
1084            return Ok(());
1085        }
1086
1087        let Some(instance) = ctx.frame.instance() else {
1088            return Err(self.err(
1089                ScErrorType::Context,
1090                ScErrorCode::InternalError,
1091                "access to instance in frame without instance",
1092                &[],
1093            ));
1094        };
1095
1096        ctx.storage = Some(InstanceStorageMap::from_map(
1097            instance.storage.as_ref().map_or_else(
1098                || Ok(vec![]),
1099                |m| {
1100                    m.iter()
1101                        .map(|i| {
1102                            Ok((
1103                                self.to_valid_host_val(&i.key)?,
1104                                self.to_valid_host_val(&i.val)?,
1105                            ))
1106                        })
1107                        .metered_collect::<Result<Vec<(Val, Val)>, HostError>>(self)?
1108                },
1109            )?,
1110            self,
1111        )?);
1112        Ok(())
1113    }
1114
1115    // Make the in-memory instance storage persist into the `Storage` by writing
1116    // its updated contents into corresponding `ContractData` ledger entry.
1117    fn persist_instance_storage(&self) -> Result<(), HostError> {
1118        let updated_instance_storage = self.with_current_context_mut(|ctx| {
1119            if let Some(storage) = &ctx.storage {
1120                if !storage.is_modified {
1121                    return Ok(None);
1122                }
1123                Ok(Some(self.host_map_to_scmap(&storage.map)?))
1124            } else {
1125                Ok(None)
1126            }
1127        })?;
1128        if updated_instance_storage.is_some() {
1129            let contract_id = self.get_current_contract_id_internal()?;
1130            let key = self.contract_instance_ledger_key(&contract_id)?;
1131
1132            self.store_contract_instance(None, updated_instance_storage, contract_id, &key)?;
1133        }
1134        Ok(())
1135    }
1136}