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