soroban_env_host/host/
frame.rs

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