soroban_env_host/
auth.rs

1//! # Auth conceptual overview
2//!
3//! This module is responsible for two separate tasks (both starting with
4//! "auth"):
5//!
6//!   - Authorization: deciding if some action should be allowed by some policy.
7//!   - Authentication: deciding if some credential or signature is authentic.
8//!
9//! As one would expect, authorization can (though doesn't always) depend on
10//! authentication: part of judging whether some action is allowed may depend on
11//! someone presenting a signed credential, at which point one must evaluate the
12//! credential's authenticity.
13//!
14//! Moreover this subsystem is responsible (as will be discussed in detail
15//! below) with facilitating two different _directions_ for each of these tasks:
16//!
17//!   - Contracts can _require_ auth services provided by this module.
18//!   - Contracts can _provide_ auth services required by this module.
19//!
20//! And again, in both directions the "auth services" required or provided may
21//! be _either_ of authorization, authentication, or both.
22//!
23//! All auth services reason about invocations and authorizations, so we
24//! next turn our attention to these.
25//!
26//! ## Invocations
27//!
28//! A Soroban transaction can be seen as a tree of _invocations_: these are
29//! usually invocations of contract functions, but may also be other host
30//! functions requiring authorization, such as the 'create contract' function.
31//!
32//! The "invocation tree" corresponds to the fact that each invocation may cause
33//! sub-invocations, each of which may have sub-sub-invocations, and so on.
34//! Contracts often invoke other contracts.
35//!
36//! Each invocation in the tree is mediated by the Soroban host, and typically
37//! represents a transition between trust domains (as different contracts are
38//! written by different authors), so invocations are the natural boundary at
39//! which to evaluate authorization.
40//!
41//! In other words: authorization happens _in terms of_ invocations; they are
42//! the conceptual units for which authorization is granted or denied.
43//!
44//! Note that invocations are _not_ function calls within a contract's own WASM
45//! bytecode. The host can't see a call from one WASM function to another inside
46//! a single WASM blob, and in general it does not concern itself with authorizing
47//! those. Invocations are bigger: what are often called "cross-contract calls",
48//! that transfer control from one WASM VM to another.
49//!
50//! ## Authorized Invocations
51//!
52//! Each invocation may -- usually early on -- call the host function
53//! `require_auth(Address)`: this is the main entrypoint to the auth module.
54//!
55//! The `require_auth` function takes an `Address` that the contract provides,
56//! that identifies some abstract entity responsible for _authorizing the
57//! current invocation_. The contract calling `require_auth` must therefore
58//! somehow select (directly or indirectly, perhaps from its own internal
59//! configuration or from some argument it was passed associated with the
60//! operation it's performing) _which entity_ it wishes to predicate its
61//! execution on the authorization of. As we'll see, there are multiple ways
62//! this entity may provide authorization. It may also require authorization
63//! from multiple entities!
64//!
65//! (There is also a secondary entrypoint called `require_auth_for_args` that
66//! allows customizing the invocation being authorized, in case the current
67//! contract invocation -- function name and argument list -- isn't quite the
68//! one desired, but this distinction is unimportant in this discussion.)
69//!
70//! For a given `Address`, the auth module maintains one or more tree-shaped
71//! data structures called the `AuthorizedInvocation`s of the `Address`, which
72//! are incrementally matched by each invocation's calls to
73//! `require_auth(Address)`.
74//!
75//! Each such tree essentially represents a _pattern_ of invocations the
76//! `Address` authorizes, that the _actual_ execution context of a running tree
77//! of contract invocations needs to match when it calls `require_auth`. Any
78//! pattern node that matches an invocation is then permanently _associated_
79//! with the actual invocation it matched, such that sub-patterns can only match
80//! at actual sub-invocations, allowing the authorizing party to globally
81//! restrict the _contexts_ in which a sub-invocation may match.
82//!
83//! Furthermore each pattern node is permanently invalidated as it matches so
84//! that it can never match more than once per transaction. If a user wishes to
85//! authorize two instances of the same pattern within a transaction, they must
86//! provide two separate copies.
87//!
88//! # Addresses
89//!
90//! As described above, `AuthorizedInvocation`s define the trees of invocations
91//! that are authorized by some `Address`. But what is an Address? Concretely it
92//! is either a Stellar `AccountID` or the `Hash` identity of some contract. But
93//! _conceptually_ the Address used to authorize an invocation may be one of 4
94//! different types.
95//!
96//!   1. The address of a contract that is an _invoker_. We say that if contract
97//!      C invokes contract D, then C authorized D. This is simple and requires
98//!      no credentials as the host literally observes the call from C to D. It
99//!      is a slight conceptual stretch but makes sense: if C didn't want to
100//!      authorize D, it wouldn't have invoked it! Further invoker-contract
101//!      authorizations for _indirect_ calls (C calls D calls E, C wants to
102//!      authorize sub-calls to E) can also be provided on the fly by contracts
103//!      calling `authorize_as_curr_contract`, passing a vector of the
104//!      Val-encoded type `InvokerContractAuthEntry`.
105//!
106//!   2. The address of a Stellar classic account, identified by `AccountID`,
107//!      that must supply `SorobanAddressCredentials` for any
108//!      `AuthorizedInvocation` it authorizes, satisfying the account's classic
109//!      multisig authorization to its medium threshold.
110//!
111//!   3. The address of a Stellar classic account that happens to be the
112//!      _transaction source account_. In this case we assume the transaction
113//!      signatures already met the requirements of the account before the
114//!      Soroban host was even instantiated, and so the `AuthorizedInvocation`
115//!      for such an address can be accompanied by the constant credential
116//!      `SOROBAN_CREDENTIALS_SOURCE_ACCOUNT` that's considered authentic by
117//!      assumption.
118//!
119//!   4. The address of a contract that is a _custom account_. In this case the
120//!      `AuthorizedInvocation` is still accompanied by
121//!      `SorobanAddressCredentials` but _interpreting_ those credentials (and
122//!      indeed interpreting the entire authorization request) is delegated to a
123//!      contract. The contract must export a function called `__check_auth` and
124//!      it will be passed the abstract, uninterpreted `Val` from the
125//!      credential's "signature" field, along with a hash of the material it
126//!      expects the signature to authenticate, and a structured summary of the
127//!      auth context. The `__check_auth` function may potentially re-enter the
128//!      auth module by calling `require_auth` on some other `Address`.
129//!
130//! Each of these 4 forms of address may be passed to `require_auth`, which will
131//! then serve as a key to look up an `AuthorizedInvocation` to match against
132//! the invocation being authorized, and potentially perform further
133//! authentication or custom-auth logic.
134//!
135//! The first type -- contract invoker address -- is associated with a set of
136//! `AuthorizedInvocation`s that is dynamic, evolves during execution of the
137//! transaction, and requires no credentials. The other 3 types are static, are
138//! provided as input to the transaction, and carry credentials that may require
139//! authentication. Therefore the first type and the latter 3 types are tracked
140//! in different data structures. But this is merely an implementation detail;
141//! addresses in all 4 conceptual roles can be passed to `require_auth` without
142//! any concern for which kind fulfils the requirement at runtime.
143//!
144//! In the cases with nontrivial `SorobanAddressCredentials` (2 and 4), the auth
145//! module takes care of evaluating signature expiration times and recording
146//! nonces to the ledger automatically, to prevent replay.
147//!
148use std::cell::RefCell;
149use std::rc::Rc;
150
151use crate::{
152    budget::{AsBudget, Budget},
153    builtin_contracts::{
154        account_contract::{check_account_authentication, check_account_contract_auth},
155        invoker_contract_auth::invoker_contract_auth_to_authorized_invocation,
156    },
157    host::{
158        metered_clone::{MeteredAlloc, MeteredClone, MeteredContainer, MeteredIterator},
159        metered_hash::{CountingHasher, MeteredHash},
160        Frame,
161    },
162    host_object::HostVec,
163    xdr::{
164        ContractDataEntry, CreateContractArgsV2, HashIdPreimage,
165        HashIdPreimageSorobanAuthorization, InvokeContractArgs, LedgerEntry, LedgerEntryData,
166        LedgerEntryExt, ScAddress, ScErrorCode, ScErrorType, ScNonceKey, ScVal,
167        SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials,
168    },
169    AddressObject, Compare, Host, HostError, Symbol, TryFromVal, TryIntoVal, Val, VecObject,
170};
171
172use super::xdr;
173use super::xdr::Hash;
174
175#[cfg(any(test, feature = "recording_mode"))]
176use crate::{
177    builtin_contracts::{account_contract::AccountEd25519Signature, base_types::BytesN},
178    host::error::TryBorrowOrErr,
179    xdr::{ContractExecutable, PublicKey},
180};
181#[cfg(any(test, feature = "recording_mode"))]
182use rand::Rng;
183#[cfg(any(test, feature = "recording_mode"))]
184use std::collections::BTreeMap;
185
186// Authorization manager encapsulates host-based authentication & authorization
187// framework.
188// This supports enforcing authentication & authorization of the contract
189// invocation trees as well as recording the authorization requirements in
190// simulated environments (such as tests or preflight).
191#[derive(Clone)]
192pub struct AuthorizationManager {
193    // Mode of operation of this AuthorizationManager. This can't be changed; in
194    // order to switch the mode a new instance of AuthorizationManager has to
195    // be created.
196    mode: AuthorizationMode,
197    // Per-address trackers of authorized invocations.
198    // Every tracker takes care about a single rooted invocation tree for some
199    // address. There can be multiple trackers per address.
200    // The internal structure of this field is build in such a way that trackers
201    // can be borrowed mutably independently, while still allowing for
202    // modification of the `account_trackers` vec itself.
203    account_trackers: RefCell<Vec<RefCell<AccountAuthorizationTracker>>>,
204    // Per-address trackers for authorization performed by the contracts at
205    // execution time (as opposed to signature-based authorization for accounts).
206    // Contract authorizations are always enforced independently of the `mode`,
207    // as they are self-contained and fully defined by the contract logic.
208    invoker_contract_trackers: RefCell<Vec<InvokerContractAuthorizationTracker>>,
209    // Call stack of relevant host function and contract invocations, moves mostly
210    // in lock step with context stack in the host.
211    call_stack: RefCell<Vec<AuthStackFrame>>,
212}
213
214macro_rules! impl_checked_borrow_helpers {
215    ($field:ident, $t:ty, $borrow:ident, $borrow_mut:ident) => {
216        impl AuthorizationManager {
217            #[allow(dead_code)]
218            fn $borrow(&self, host: &Host) -> Result<std::cell::Ref<'_, $t>, HostError> {
219                use crate::host::error::TryBorrowOrErr;
220                self.$field.try_borrow_or_err_with(
221                    host,
222                    concat!(
223                        "authorization_manager.",
224                        stringify!($field),
225                        ".try_borrow failed"
226                    ),
227                )
228            }
229
230            #[allow(dead_code)]
231            fn $borrow_mut(&self, host: &Host) -> Result<std::cell::RefMut<'_, $t>, HostError> {
232                use crate::host::error::TryBorrowOrErr;
233                self.$field.try_borrow_mut_or_err_with(
234                    host,
235                    concat!(
236                        "authorization_manager.",
237                        stringify!($field),
238                        ".try_borrow_mut failed"
239                    ),
240                )
241            }
242        }
243    };
244}
245
246impl_checked_borrow_helpers!(
247    account_trackers,
248    Vec<RefCell<AccountAuthorizationTracker>>,
249    try_borrow_account_trackers,
250    try_borrow_account_trackers_mut
251);
252
253impl_checked_borrow_helpers!(
254    invoker_contract_trackers,
255    Vec<InvokerContractAuthorizationTracker>,
256    try_borrow_invoker_contract_trackers,
257    try_borrow_invoker_contract_trackers_mut
258);
259
260impl_checked_borrow_helpers!(
261    call_stack,
262    Vec<AuthStackFrame>,
263    try_borrow_call_stack,
264    try_borrow_call_stack_mut
265);
266
267// The authorization payload recorded for an address in the recording
268// authorization mode.
269#[cfg(any(test, feature = "recording_mode"))]
270#[derive(Debug)]
271pub struct RecordedAuthPayload {
272    pub address: Option<ScAddress>,
273    pub nonce: Option<i64>,
274    pub invocation: xdr::SorobanAuthorizedInvocation,
275}
276
277// Snapshot of `AuthorizationManager` to use when performing the callstack
278// rollbacks.
279pub struct AuthorizationManagerSnapshot {
280    account_trackers_snapshot: AccountTrackersSnapshot,
281    invoker_contract_tracker_root_snapshots: Vec<AuthorizedInvocationSnapshot>,
282    #[cfg(any(test, feature = "recording_mode"))]
283    tracker_by_address_handle: Option<BTreeMap<u32, usize>>,
284}
285
286// Snapshot of the `account_trackers` in `AuthorizationManager`.
287enum AccountTrackersSnapshot {
288    // In enforcing mode we only need to snapshot the mutable part of the
289    // trackers.
290    // `None` means that the tracker is currently in authentication process and
291    // shouldn't be modified (as the tracker can't be used to authenticate
292    // itself).
293    Enforcing(Vec<Option<AccountAuthorizationTrackerSnapshot>>),
294    // In recording mode snapshot the whole vector, as we create trackers
295    // lazily and hence the outer vector itself might change.
296    #[cfg(any(test, feature = "recording_mode"))]
297    Recording(Vec<RefCell<AccountAuthorizationTracker>>),
298}
299
300// Additional AuthorizationManager fields needed only for the recording mode.
301#[cfg(any(test, feature = "recording_mode"))]
302#[derive(Clone)]
303struct RecordingAuthInfo {
304    // Maps the `Address` object identifiers to the respective tracker indices
305    // in `trackers`
306    // This allows to disambiguate between the addresses that have the same
307    // value, but are specified as two different objects (e.g. as two different
308    // contract function inputs).
309    tracker_by_address_handle: RefCell<BTreeMap<u32, usize>>,
310    // Whether to allow root authorized invocation to not match the root
311    // contract invocation.
312    disable_non_root_auth: bool,
313}
314
315#[derive(Clone, Hash)]
316enum AuthorizationMode {
317    Enforcing,
318    #[cfg(any(test, feature = "recording_mode"))]
319    Recording(RecordingAuthInfo),
320}
321
322#[cfg(any(test, feature = "recording_mode"))]
323impl std::hash::Hash for RecordingAuthInfo {
324    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
325        if let Ok(tracker_by_address_handle) = self.tracker_by_address_handle.try_borrow() {
326            tracker_by_address_handle.hash(state);
327        }
328        self.disable_non_root_auth.hash(state);
329    }
330}
331
332#[cfg(any(test, feature = "recording_mode"))]
333impl RecordingAuthInfo {
334    fn try_borrow_tracker_by_address_handle(
335        &self,
336        host: &Host,
337    ) -> Result<std::cell::Ref<'_, BTreeMap<u32, usize>>, HostError> {
338        self.tracker_by_address_handle.try_borrow_or_err_with(
339            host,
340            "recording_auth_info.tracker_by_address_handle.try_borrow failed",
341        )
342    }
343    fn try_borrow_tracker_by_address_handle_mut(
344        &self,
345        host: &Host,
346    ) -> Result<std::cell::RefMut<'_, BTreeMap<u32, usize>>, HostError> {
347        self.tracker_by_address_handle.try_borrow_mut_or_err_with(
348            host,
349            "recording_auth_info.tracker_by_address_handle.try_borrow_mut failed",
350        )
351    }
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
355enum MatchState {
356    Unmatched,
357    RootMatch,
358    SubMatch { index_in_parent: usize },
359}
360
361impl MatchState {
362    fn is_matched(&self) -> bool {
363        *self != MatchState::Unmatched
364    }
365}
366
367/// An `InvocationTracker` is responsible for incrementally matching a single
368/// [`AuthorizedInvocation`] tree against the actual invocation tree. In this
369/// way the nodes that make up the `AuthorizedInvocation` act as a pattern, and
370/// the `InvocationTracker` is an incremental pattern-matcher. The same
371/// `InvocationTracker` type is used as the pattern-matching sub-component of
372/// [`AccountAuthorizationTracker`] and [`InvokerContractAuthorizationTracker`],
373/// as the matching logic is the same in both cases.
374///
375/// The `InvocationTracker` maintains a [`InvocationTracker::match_stack`] of
376/// [`MatchState`] values that correspond to the frames in
377/// [`AuthorizationManager::call_stack`], pushing and popping those values as
378/// frames are are pushed and popped from that call stack. The values in the
379/// match stack are initially all [`MatchState::Unmatched`].
380///
381/// Matching is initiated by a contract calling
382/// [`AuthorizationManager::require_auth`], and the auth manager then works its
383/// way through many possible trackers asking each to try to match itself
384/// against the current [`AuthorizationManager::call_stack`] context, the last
385/// entry of which is the frame requesting the authorization.
386///
387/// A tracker may be active or inactive. If active, it means that the
388/// `InvocationTracker` has begun matching itself against the call stack
389/// already, and the frame requesting authorization will be matched against the
390/// _children_ of the last (deepest) already-matched node in the tracker's
391/// [`AuthorizedInvocation`]. If inactive, it means that the `InvocationTracker`
392/// has not yet begun matching and so the frame requesting authorization will be
393/// matched against the root node of the [`AuthorizedInvocation`]. Any
394/// successful match is recorded in the [`InvocationTracker::match_stack`] by
395/// overwriting the value at the match, changing it from
396/// [`MatchState::Unmatched`] to either [`MatchState::RootMatch`] or
397/// [`MatchState::SubMatch`] as appropriate. This match-extension logic is in
398/// [`InvocationTracker::maybe_extend_invocation_match`]
399///
400/// The active-ness of a tracker is defined by the
401/// [`AuthorizedInvocation::exhausted`] flag on the root node it's matching, as
402/// well as the continued presence of the [`MatchState::RootMatch`] node on the
403/// match stack. In other words: the tracker becomes "active" as soon as the
404/// root match is pushed on the match stack (which exhausts the root node), and
405/// the tracker stops being active when the root match is popped from the match
406/// stack. At this point the [`InvocationTracker::is_fully_processed`] flag is
407/// set.
408#[derive(Clone, Hash)]
409struct InvocationTracker {
410    // Root of the authorized invocation tree.
411    // The authorized invocation tree only contains the contract invocations
412    // that explicitly require authorization on behalf of the address.
413    root_authorized_invocation: AuthorizedInvocation,
414    // Stack that tracks the current match of the tree of authorized invocations
415    // against the actual invocations made by the host. There is one entry in
416    // this vector for each entry in [`AuthorizationManager::call_stack`]
417    // (unless the tracker has been temporary suppressed due to reentry).
418    //
419    // The values in the match stack are always initially
420    // `MatchState::Unmatched`. The match stack may (if the tracker is active)
421    // contain a subsequence of values in it beginning with
422    // `MatchState::RootMatch` and continuing with a mixture of
423    // `MatchState::SubMatch` and `MatchState::Unmatched` values, corresponding
424    // to frames in the call stack that match or are ignored (respectively) by
425    // nodes in the `AuthorizedInvocation` pattern tree. If this vector ever
426    // contains a subsequence starting with `MatchState::SubMatch` (i.e. without
427    // a root), or contains more than one `MatchState::RootMatch`, there is a
428    // logic error somewhere.
429    match_stack: Vec<MatchState>,
430    // If root invocation is exhausted, the index of the stack frame where it
431    // was exhausted (i.e. index in `match_stack`).
432    root_exhausted_frame: Option<usize>,
433    // Indicates whether this tracker is fully processed, i.e. the authorized
434    // root frame has been exhausted and then popped from the stack.
435    is_fully_processed: bool,
436}
437
438// Stores all the authorizations that are authorized by an address.
439// In the enforcing mode this performs authentication and makes sure that only
440// pre-authorized invocations can happen on behalf of the `address`.
441// In the recording mode this will record the invocations that are authorized
442// on behalf of the address.
443#[derive(Clone, Hash)]
444pub(crate) struct AccountAuthorizationTracker {
445    // Tracked address.
446    address: AddressObject,
447    // Helper for matching the tree that address authorized to the invocation
448    // tree.
449    invocation_tracker: InvocationTracker,
450    // Value representing the signature created by the address to authorize
451    // the invocations tracked here.
452    signature: Val,
453    // Indicates whether this is a tracker for the transaction source account.
454    is_transaction_source_account: bool,
455    // When `true`, indicates that the tracker has been successfully verified,
456    // specifically it has been authenticated and has nonce verified and
457    // consumed.
458    // When `false`, indicates that verification hasn't happened yet or
459    // that it hasn't been successful. The latter case is subtle - we don't cache
460    // the verification failures because a verification failure is not recoverable
461    // and thus is bound to be rolled back.
462    verified: bool,
463    // The value of nonce authorized by the address with its live_until ledger.
464    // Must not exist in the ledger.
465    nonce: Option<(i64, u32)>,
466}
467
468pub(crate) struct AccountAuthorizationTrackerSnapshot {
469    invocation_tracker_root_snapshot: AuthorizedInvocationSnapshot,
470    verified: bool,
471}
472
473// Stores all the authorizations performed by contracts at runtime.
474#[derive(Clone, Hash)]
475pub(crate) struct InvokerContractAuthorizationTracker {
476    contract_address: AddressObject,
477    invocation_tracker: InvocationTracker,
478}
479
480#[derive(Clone, Hash)]
481pub(crate) enum AuthStackFrame {
482    Contract(ContractInvocation),
483    CreateContractHostFn(CreateContractArgsV2),
484}
485
486#[derive(Clone, Hash)]
487pub(crate) struct ContractInvocation {
488    pub(crate) contract_address: AddressObject,
489    pub(crate) function_name: Symbol,
490}
491
492#[derive(Clone, Hash)]
493pub(crate) struct ContractFunction {
494    pub(crate) contract_address: AddressObject,
495    pub(crate) function_name: Symbol,
496    pub(crate) args: Vec<Val>,
497}
498
499#[derive(Clone, Hash)]
500pub(crate) enum AuthorizedFunction {
501    ContractFn(ContractFunction),
502    CreateContractHostFn(CreateContractArgsV2),
503}
504
505// A single node in the authorized invocation tree.
506// This represents an invocation and all it's authorized sub-invocations.
507#[derive(Clone, Hash)]
508pub(crate) struct AuthorizedInvocation {
509    pub(crate) function: AuthorizedFunction,
510    pub(crate) sub_invocations: Vec<AuthorizedInvocation>,
511    // Indicates that this invocation has been already used in the
512    // enforcing mode. Exhausted authorizations can't be reused.
513    // In the recording mode this is immediately set to `true` (as the
514    // authorizations are recorded when they actually happen).
515    is_exhausted: bool,
516}
517
518// Snapshot of `AuthorizedInvocation` that contains only mutable fields.
519pub(crate) struct AuthorizedInvocationSnapshot {
520    is_exhausted: bool,
521    sub_invocations: Vec<AuthorizedInvocationSnapshot>,
522}
523
524impl Compare<ContractFunction> for Host {
525    type Error = HostError;
526
527    // metering: covered by host
528    fn compare(
529        &self,
530        a: &ContractFunction,
531        b: &ContractFunction,
532    ) -> Result<std::cmp::Ordering, Self::Error> {
533        let ord = self.compare(&a.contract_address, &b.contract_address)?;
534        if !ord.is_eq() {
535            return Ok(ord);
536        }
537        let ord = self.compare(&a.function_name, &b.function_name)?;
538        if !ord.is_eq() {
539            return Ok(ord);
540        }
541        self.compare(&a.args, &b.args)
542    }
543}
544
545impl Compare<AuthorizedFunction> for Host {
546    type Error = HostError;
547
548    // metering: covered by components
549    fn compare(
550        &self,
551        a: &AuthorizedFunction,
552        b: &AuthorizedFunction,
553    ) -> Result<std::cmp::Ordering, Self::Error> {
554        match (a, b) {
555            (AuthorizedFunction::ContractFn(f1), AuthorizedFunction::ContractFn(f2)) => {
556                self.compare(f1, f2)
557            }
558            (
559                AuthorizedFunction::CreateContractHostFn(c1),
560                AuthorizedFunction::CreateContractHostFn(c2),
561            ) => self.compare(c1, c2),
562            (AuthorizedFunction::ContractFn(_), AuthorizedFunction::CreateContractHostFn(_)) => {
563                Ok(std::cmp::Ordering::Less)
564            }
565            (AuthorizedFunction::CreateContractHostFn(_), AuthorizedFunction::ContractFn(_)) => {
566                Ok(std::cmp::Ordering::Greater)
567            }
568        }
569    }
570}
571
572impl AuthStackFrame {
573    // metering: covered
574    fn to_authorized_function(
575        &self,
576        host: &Host,
577        args: Vec<Val>,
578    ) -> Result<AuthorizedFunction, HostError> {
579        match self {
580            AuthStackFrame::Contract(contract_frame) => {
581                Ok(AuthorizedFunction::ContractFn(ContractFunction {
582                    contract_address: contract_frame.contract_address,
583                    function_name: contract_frame.function_name.metered_clone(host)?,
584                    args,
585                }))
586            }
587            AuthStackFrame::CreateContractHostFn(args) => Ok(
588                AuthorizedFunction::CreateContractHostFn(args.metered_clone(host)?),
589            ),
590        }
591    }
592}
593
594impl AuthorizedFunction {
595    // metering: covered by the host
596    fn from_xdr(host: &Host, xdr_fn: SorobanAuthorizedFunction) -> Result<Self, HostError> {
597        Ok(match xdr_fn {
598            SorobanAuthorizedFunction::ContractFn(xdr_contract_fn) => {
599                AuthorizedFunction::ContractFn(ContractFunction {
600                    contract_address: host.add_host_object(xdr_contract_fn.contract_address)?,
601                    function_name: Symbol::try_from_val(host, &xdr_contract_fn.function_name)?,
602                    args: host.scvals_to_val_vec(xdr_contract_fn.args.as_slice())?,
603                })
604            }
605            SorobanAuthorizedFunction::CreateContractHostFn(xdr_args) => {
606                AuthorizedFunction::CreateContractHostFn(CreateContractArgsV2 {
607                    contract_id_preimage: xdr_args.contract_id_preimage,
608                    executable: xdr_args.executable,
609                    constructor_args: Default::default(),
610                })
611            }
612            SorobanAuthorizedFunction::CreateContractV2HostFn(xdr_args) => {
613                AuthorizedFunction::CreateContractHostFn(xdr_args)
614            }
615        })
616    }
617
618    // metering: covered by the host
619    fn to_xdr(&self, host: &Host) -> Result<SorobanAuthorizedFunction, HostError> {
620        match self {
621            AuthorizedFunction::ContractFn(contract_fn) => {
622                let function_name = host.scsymbol_from_symbol(contract_fn.function_name)?;
623                Ok(SorobanAuthorizedFunction::ContractFn(InvokeContractArgs {
624                    contract_address: host.scaddress_from_address(contract_fn.contract_address)?,
625                    function_name,
626                    args: host.vals_to_scval_vec(contract_fn.args.as_slice())?,
627                }))
628            }
629            AuthorizedFunction::CreateContractHostFn(create_contract_args) => {
630                Ok(SorobanAuthorizedFunction::CreateContractV2HostFn(
631                    create_contract_args.metered_clone(host)?,
632                ))
633            }
634        }
635    }
636}
637
638impl AuthorizedInvocation {
639    // metering: covered
640    fn from_xdr(
641        host: &Host,
642        xdr_invocation: xdr::SorobanAuthorizedInvocation,
643    ) -> Result<Self, HostError> {
644        let sub_invocations_xdr = xdr_invocation.sub_invocations.into_vec();
645        let sub_invocations = sub_invocations_xdr
646            .into_iter()
647            .map(|a| AuthorizedInvocation::from_xdr(host, a))
648            .metered_collect::<Result<Vec<_>, _>>(host)??;
649        Ok(Self {
650            function: AuthorizedFunction::from_xdr(host, xdr_invocation.function)?,
651            sub_invocations,
652            is_exhausted: false,
653        })
654    }
655
656    // metering: free
657    pub(crate) fn new(
658        function: AuthorizedFunction,
659        sub_invocations: Vec<AuthorizedInvocation>,
660    ) -> Self {
661        Self {
662            function,
663            sub_invocations,
664            is_exhausted: false,
665        }
666    }
667
668    // metering: free
669    #[cfg(any(test, feature = "recording_mode"))]
670    fn new_recording(function: AuthorizedFunction) -> Self {
671        Self {
672            function,
673            sub_invocations: vec![],
674            is_exhausted: true,
675        }
676    }
677
678    // metering: covered
679    fn to_xdr(
680        &self,
681        host: &Host,
682        exhausted_sub_invocations_only: bool,
683    ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
684        Ok(xdr::SorobanAuthorizedInvocation {
685            function: self.function.to_xdr(host)?,
686            sub_invocations: self
687                .sub_invocations
688                .iter()
689                .filter(|i| i.is_exhausted || !exhausted_sub_invocations_only)
690                .map(|i| i.to_xdr(host, exhausted_sub_invocations_only))
691                .metered_collect::<Result<Vec<xdr::SorobanAuthorizedInvocation>, HostError>>(host)??
692                .try_into()?,
693        })
694    }
695
696    // Walks a path in the tree defined by `match_stack` and
697    // returns the last visited authorized node.
698    // metering: free
699    fn last_authorized_invocation_mut(
700        &mut self,
701        match_stack: &Vec<MatchState>,
702        call_stack_id: usize,
703    ) -> Result<&mut AuthorizedInvocation, HostError> {
704        // Start walking the stack from `call_stack_id`. We trust the callers to
705        // hold the invariant that `match_stack[call_stack_id - 1]`
706        // corresponds to this invocation tree, so that the next non-`None` child
707        // corresponds to the child of the current tree.
708        for (i, m) in match_stack.iter().enumerate().skip(call_stack_id) {
709            match m {
710                MatchState::SubMatch { index_in_parent } => {
711                    // We trust the caller to have the correct sub-invocation
712                    // indices.
713                    if let Some(sub) = self.sub_invocations.get_mut(*index_in_parent) {
714                        return sub.last_authorized_invocation_mut(match_stack, i + 1);
715                    } else {
716                        return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
717                    }
718                }
719                MatchState::RootMatch => {
720                    return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
721                }
722                // Skip Unmatched invocations as they don't require authorization.
723                MatchState::Unmatched => (),
724            }
725        }
726        Ok(self)
727    }
728
729    // metering: covered
730    fn snapshot(&self, budget: &Budget) -> Result<AuthorizedInvocationSnapshot, HostError> {
731        Ok(AuthorizedInvocationSnapshot {
732            is_exhausted: self.is_exhausted,
733            sub_invocations: self
734                .sub_invocations
735                .iter()
736                .map(|i| i.snapshot(budget))
737                .metered_collect::<Result<Vec<AuthorizedInvocationSnapshot>, HostError>>(
738                    budget,
739                )??,
740        })
741    }
742
743    // metering: free
744    fn rollback(&mut self, snapshot: &AuthorizedInvocationSnapshot) -> Result<(), HostError> {
745        self.is_exhausted = snapshot.is_exhausted;
746        if self.sub_invocations.len() != snapshot.sub_invocations.len() {
747            // This would be a bug.
748            return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
749        }
750        for (sub, snap) in self
751            .sub_invocations
752            .iter_mut()
753            .zip(snapshot.sub_invocations.iter())
754        {
755            sub.rollback(snap)?
756        }
757        Ok(())
758    }
759}
760
761impl Default for AuthorizationManager {
762    fn default() -> Self {
763        Self::new_enforcing_without_authorizations()
764    }
765}
766
767impl AuthorizationManager {
768    // Creates a new enforcing `AuthorizationManager` from the given
769    // authorization entries.
770    // This should be created once per top-level invocation.
771    // metering: covered
772    pub(crate) fn new_enforcing(
773        host: &Host,
774        auth_entries: Vec<SorobanAuthorizationEntry>,
775    ) -> Result<Self, HostError> {
776        let mut trackers = Vec::<RefCell<AccountAuthorizationTracker>>::with_metered_capacity(
777            auth_entries.len(),
778            host,
779        )?;
780        for auth_entry in auth_entries {
781            trackers.push(RefCell::new(
782                AccountAuthorizationTracker::from_authorization_entry(host, auth_entry)?,
783            ));
784        }
785        Ok(Self {
786            mode: AuthorizationMode::Enforcing,
787            call_stack: RefCell::new(vec![]),
788            account_trackers: RefCell::new(trackers),
789            invoker_contract_trackers: RefCell::new(vec![]),
790        })
791    }
792
793    // Creates a new enforcing `AuthorizationManager` that doesn't allow any
794    // authorizations.
795    // This is useful as a safe default mode.
796    // metering: free
797    pub(crate) fn new_enforcing_without_authorizations() -> Self {
798        Self {
799            mode: AuthorizationMode::Enforcing,
800            call_stack: RefCell::new(vec![]),
801            account_trackers: RefCell::new(vec![]),
802            invoker_contract_trackers: RefCell::new(vec![]),
803        }
804    }
805
806    // Creates a new recording `AuthorizationManager`.
807    // All the authorization requirements will be recorded and can then be
808    // retrieved using `get_recorded_auth_payloads`.
809    // metering: free
810    #[cfg(any(test, feature = "recording_mode"))]
811    pub(crate) fn new_recording(disable_non_root_auth: bool) -> Self {
812        Self {
813            mode: AuthorizationMode::Recording(RecordingAuthInfo {
814                tracker_by_address_handle: Default::default(),
815                disable_non_root_auth,
816            }),
817            call_stack: RefCell::new(vec![]),
818            account_trackers: RefCell::new(vec![]),
819            invoker_contract_trackers: RefCell::new(vec![]),
820        }
821    }
822
823    // Require the `address` to have authorized the current contract invocation
824    // with provided args and within the current context (i.e. the current
825    // authorized call stack and for the current network).
826    // In the recording mode this stores the auth requirement instead of
827    // verifying it.
828    // metering: covered
829    pub(crate) fn require_auth(
830        &self,
831        host: &Host,
832        address: AddressObject,
833        args: Vec<Val>,
834    ) -> Result<(), HostError> {
835        let _span = tracy_span!("require auth");
836        let authorized_function = self
837            .try_borrow_call_stack(host)?
838            .last()
839            .ok_or_else(|| {
840                host.err(
841                    ScErrorType::Auth,
842                    ScErrorCode::InternalError,
843                    "unexpected require_auth outside of valid frame",
844                    &[],
845                )
846            })?
847            .to_authorized_function(host, args)?;
848
849        self.require_auth_internal(host, address, authorized_function)
850    }
851
852    // metering: covered
853    pub(crate) fn add_invoker_contract_auth_with_curr_contract_as_invoker(
854        &self,
855        host: &Host,
856        auth_entries: VecObject,
857    ) -> Result<(), HostError> {
858        let auth_entries =
859            host.visit_obj(auth_entries, |e: &HostVec| e.to_vec(host.budget_ref()))?;
860        let mut trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
861        Vec::<InvokerContractAuthorizationTracker>::charge_bulk_init_cpy(
862            auth_entries.len() as u64,
863            host,
864        )?;
865        trackers.reserve(auth_entries.len());
866        for e in auth_entries {
867            trackers.push(
868                InvokerContractAuthorizationTracker::new_with_curr_contract_as_invoker(host, e)?,
869            )
870        }
871        Ok(())
872    }
873
874    // metering: covered by components
875    fn maybe_check_invoker_contract_auth(
876        &self,
877        host: &Host,
878        address: AddressObject,
879        function: &AuthorizedFunction,
880    ) -> Result<bool, HostError> {
881        {
882            let call_stack = self.try_borrow_call_stack(host)?;
883            // If stack has just one call there can't be invoker.
884            if call_stack.len() < 2 {
885                return Ok(false);
886            }
887
888            // Try matching the direct invoker contract first. It is considered to
889            // have authorized any direct calls.
890            let Some(invoker_frame) = &call_stack.get(call_stack.len() - 2) else {
891                return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
892            };
893            if let AuthStackFrame::Contract(invoker_contract) = invoker_frame {
894                if host
895                    .compare(&invoker_contract.contract_address, &address)?
896                    .is_eq()
897                {
898                    return Ok(true);
899                }
900            }
901        }
902        let mut invoker_contract_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
903        // If there is no direct invoker, there still might be a valid
904        // sub-contract call authorization from another invoker higher up the
905        // stack. Note, that invoker contract trackers consider the direct frame
906        // to never require auth (any `require_auth` calls would be matched by
907        // logic above).
908        for tracker in invoker_contract_trackers.iter_mut() {
909            if host.compare(&tracker.contract_address, &address)?.is_eq()
910                && tracker.maybe_authorize_invocation(host, function)?
911            {
912                return Ok(true);
913            }
914        }
915
916        return Ok(false);
917    }
918
919    // metering: covered by components
920    fn require_auth_enforcing(
921        &self,
922        host: &Host,
923        address: AddressObject,
924        function: &AuthorizedFunction,
925    ) -> Result<(), HostError> {
926        // Find if there is already an active tracker for this address that has
927        // not been matched for the current frame. If there is such tracker,
928        // this authorization has to be matched with an already active tracker.
929        // This prevents matching sets of disjoint authorization entries to
930        // a tree of calls.
931        let mut has_active_tracker = false;
932        for tracker in self.try_borrow_account_trackers(host)?.iter() {
933            if let Ok(tracker) = tracker.try_borrow() {
934                // If address doesn't match, just skip the tracker.
935                if host.compare(&tracker.address, &address)?.is_eq()
936                    && tracker.is_active()
937                    && !tracker.current_frame_is_already_matched()
938                {
939                    has_active_tracker = true;
940                    break;
941                }
942            }
943        }
944
945        // Iterate all the trackers and try to find one that
946        // fulfills the authorization requirement.
947        for tracker in self.try_borrow_account_trackers(host)?.iter() {
948            // Tracker can only be borrowed by the authorization manager itself.
949            // The only scenario in which re-borrow might occur is when
950            // `require_auth` is called within `__check_auth` call. The tracker
951            // that called `__check_auth` would be already borrowed in such
952            // scenario.
953            // We allow such call patterns in general, but we don't allow using
954            // tracker to verify auth for itself, i.e. we don't allow something
955            // like address.require_auth()->address_contract.__check_auth()
956            // ->address.require_auth(). Thus we simply skip the trackers that
957            // have already been borrowed.
958            if let Ok(mut tracker) = tracker.try_borrow_mut() {
959                // If tracker has already been used for this frame or the address
960                // doesn't match, just skip the tracker.
961                if !host.compare(&tracker.address, &address)?.is_eq() {
962                    continue;
963                }
964                match tracker.maybe_authorize_invocation(host, function, !has_active_tracker) {
965                    // If tracker doesn't have a matching invocation,
966                    // just skip it (there could still be another
967                    // tracker  that matches it).
968                    Ok(false) => continue,
969                    // Found a matching authorization.
970                    Ok(true) => return Ok(()),
971                    // Found a matching authorization, but another
972                    // requirement hasn't been fulfilled (for
973                    // example, incorrect authentication or nonce).
974                    Err(e) => return Err(e),
975                }
976            }
977        }
978        // No matching tracker found, hence the invocation isn't
979        // authorized.
980        Err(host.err(
981            ScErrorType::Auth,
982            ScErrorCode::InvalidAction,
983            "Unauthorized function call for address",
984            &[address.to_val()],
985        ))
986    }
987
988    #[cfg(any(test, feature = "recording_mode"))]
989    fn is_invoker_contract(&self, host: &Host, address: AddressObject) -> Result<bool, HostError> {
990        let call_stack = self.try_borrow_call_stack(host)?;
991        for i in 0..(call_stack.len() - 1) {
992            match call_stack[i] {
993                AuthStackFrame::CreateContractHostFn(_) => (),
994                AuthStackFrame::Contract(ref contract_frame) => {
995                    if host
996                        .compare(&contract_frame.contract_address, &address)?
997                        .is_eq()
998                    {
999                        return Ok(true);
1000                    }
1001                }
1002            }
1003        }
1004        Ok(false)
1005    }
1006
1007    #[cfg(any(test, feature = "recording_mode"))]
1008    fn require_auth_recording(
1009        &self,
1010        host: &Host,
1011        address: AddressObject,
1012        function: AuthorizedFunction,
1013        recording_info: &RecordingAuthInfo,
1014    ) -> Result<(), HostError> {
1015        // At first, try to find the tracker for this exact address
1016        // object.
1017        // This is a best-effort heuristic to come up with a reasonably
1018        // looking recording tree for cases when multiple instances of
1019        // the same exact address are used.
1020        let address_obj_handle = address.get_handle();
1021        let existing_tracker_id = recording_info
1022            .try_borrow_tracker_by_address_handle(host)?
1023            .get(&address_obj_handle)
1024            .copied();
1025        if let Some(tracker_id) = existing_tracker_id {
1026            // The tracker should not be borrowed recursively in
1027            // recording mode, as we don't call `__check_auth` in this
1028            // flow.
1029            let trackers = self.try_borrow_account_trackers(host)?;
1030            let Some(trackercell) = trackers.get(tracker_id) else {
1031                return Err(host.err(
1032                    ScErrorType::Auth,
1033                    ScErrorCode::InternalError,
1034                    "bad index for existing tracker",
1035                    &[],
1036                ));
1037            };
1038            if let Ok(mut tracker) = trackercell.try_borrow_mut() {
1039                // The recording invariant is that trackers are created
1040                // with the first authorized invocation, which means
1041                // that when their stack no longer has authorized
1042                // invocation, then we've popped frames past its root
1043                // and hence need to create a new tracker.
1044                if !tracker.has_authorized_invocations_in_stack() {
1045                    recording_info
1046                        .try_borrow_tracker_by_address_handle_mut(host)?
1047                        .remove(&address_obj_handle);
1048                } else {
1049                    return tracker.record_invocation(host, function);
1050                }
1051            } else {
1052                return Err(host.err(
1053                    ScErrorType::Auth,
1054                    ScErrorCode::InternalError,
1055                    "unexpected recursive tracker borrow in recording mode",
1056                    &[],
1057                ));
1058            };
1059        }
1060        // If there is no active tracker for this exact address object,
1061        // try to find any matching active tracker for the address.
1062        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1063            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1064                if !host.compare(&tracker.address, &address)?.is_eq() {
1065                    continue;
1066                }
1067                // Take the first tracker that is still active (i.e. has
1068                // active authorizations in the current call stack) and
1069                // hasn't been used for this stack frame yet.
1070                if tracker.has_authorized_invocations_in_stack()
1071                    && !tracker.current_frame_is_already_matched()
1072                {
1073                    return tracker.record_invocation(host, function);
1074                }
1075            } else {
1076                return Err(host.err(
1077                    ScErrorType::Auth,
1078                    ScErrorCode::InternalError,
1079                    "unexpected borrowed tracker in recording auth mode",
1080                    &[],
1081                ));
1082            }
1083        }
1084        // At this stage there is no active tracker to which we could
1085        // match the current invocation, thus we need to create a new
1086        // tracker.
1087        // Alert the user in `disable_non_root_auth` mode if we're not
1088        // in the root stack frame.
1089        if recording_info.disable_non_root_auth && self.try_borrow_call_stack(host)?.len() != 1 {
1090            if self.is_invoker_contract(host, address)? {
1091                return Err(host.err(
1092                    ScErrorType::Auth,
1093                    ScErrorCode::InvalidAction,
1094                    "[recording authorization only] encountered unauthorized call for a \
1095                    contract earlier in the call stack, make sure that you have called \
1096                    `authorize_as_current_contract()` with the appropriate arguments for it.",
1097                    &[address.into()],
1098                ));
1099            }
1100            return Err(host.err(
1101                ScErrorType::Auth,
1102                ScErrorCode::InvalidAction,
1103                "[recording authorization only] encountered authorization not tied \
1104                to the root contract invocation for an address. Use `require_auth()` \
1105                in the top invocation or enable non-root authorization.",
1106                &[address.into()],
1107            ));
1108        }
1109        // If a tracker for the new tree doesn't exist yet, create
1110        // it and initialize with the current invocation.
1111        self.try_borrow_account_trackers_mut(host)?
1112            .push(RefCell::new(AccountAuthorizationTracker::new_recording(
1113                host,
1114                address,
1115                function,
1116                self.try_borrow_call_stack(host)?.len(),
1117            )?));
1118        recording_info
1119            .try_borrow_tracker_by_address_handle_mut(host)?
1120            .insert(
1121                address_obj_handle,
1122                self.try_borrow_account_trackers(host)?.len() - 1,
1123            );
1124        Ok(())
1125    }
1126
1127    // metering: covered
1128    fn require_auth_internal(
1129        &self,
1130        host: &Host,
1131        address: AddressObject,
1132        function: AuthorizedFunction,
1133    ) -> Result<(), HostError> {
1134        // First check the InvokerContractAuthorizationTrackers
1135        if self.maybe_check_invoker_contract_auth(host, address, &function)? {
1136            return Ok(());
1137        }
1138        // Then check the AccountAuthorizationTrackers
1139        match &self.mode {
1140            AuthorizationMode::Enforcing => self.require_auth_enforcing(host, address, &function),
1141            // metering: free for recording
1142            #[cfg(any(test, feature = "recording_mode"))]
1143            AuthorizationMode::Recording(recording_info) => {
1144                self.require_auth_recording(host, address, function, recording_info)
1145            }
1146        }
1147    }
1148
1149    // Returns a snapshot of `AuthorizationManager` to use for rollback.
1150    // metering: covered
1151    fn snapshot(&self, host: &Host) -> Result<AuthorizationManagerSnapshot, HostError> {
1152        let _span = tracy_span!("snapshot auth");
1153        let account_trackers_snapshot = match &self.mode {
1154            AuthorizationMode::Enforcing => {
1155                let len = self.try_borrow_account_trackers(host)?.len();
1156                let mut snapshots =
1157                    Vec::<Option<AccountAuthorizationTrackerSnapshot>>::with_metered_capacity(
1158                        len, host,
1159                    )?;
1160                for t in self.try_borrow_account_trackers(host)?.iter() {
1161                    let sp = if let Ok(tracker) = t.try_borrow() {
1162                        Some(tracker.snapshot(host.as_budget())?)
1163                    } else {
1164                        // If tracker is borrowed, snapshotting it is a no-op
1165                        // (it can't change until we release it higher up the
1166                        // stack).
1167                        None
1168                    };
1169                    snapshots.push(sp);
1170                }
1171                AccountTrackersSnapshot::Enforcing(snapshots)
1172            }
1173            #[cfg(any(test, feature = "recording_mode"))]
1174            AuthorizationMode::Recording(_) => {
1175                // All trackers should be available to borrow for copy as in
1176                // recording mode we can't have recursive authorization.
1177                // metering: free for recording
1178                AccountTrackersSnapshot::Recording(self.try_borrow_account_trackers(host)?.clone())
1179            }
1180        };
1181        let invoker_contract_tracker_root_snapshots = self
1182            .try_borrow_invoker_contract_trackers(host)?
1183            .iter()
1184            .map(|t| t.invocation_tracker.snapshot(host.as_budget()))
1185            .metered_collect::<Result<Vec<AuthorizedInvocationSnapshot>, HostError>>(host)??;
1186        #[cfg(any(test, feature = "recording_mode"))]
1187        let tracker_by_address_handle = match &self.mode {
1188            AuthorizationMode::Enforcing => None,
1189            AuthorizationMode::Recording(recording_info) => Some(
1190                // metering: free for recording
1191                recording_info
1192                    .try_borrow_tracker_by_address_handle(host)?
1193                    .clone(),
1194            ),
1195        };
1196        Ok(AuthorizationManagerSnapshot {
1197            account_trackers_snapshot,
1198            invoker_contract_tracker_root_snapshots,
1199            #[cfg(any(test, feature = "recording_mode"))]
1200            tracker_by_address_handle,
1201        })
1202    }
1203
1204    // Rolls back this `AuthorizationManager` to the snapshot state.
1205    // metering: covered
1206    fn rollback(
1207        &self,
1208        host: &Host,
1209        snapshot: AuthorizationManagerSnapshot,
1210    ) -> Result<(), HostError> {
1211        let _span = tracy_span!("rollback auth");
1212        match snapshot.account_trackers_snapshot {
1213            AccountTrackersSnapshot::Enforcing(trackers_snapshot) => {
1214                let trackers = self.try_borrow_account_trackers(host)?;
1215                if trackers.len() != trackers_snapshot.len() {
1216                    return Err(host.err(
1217                        ScErrorType::Auth,
1218                        ScErrorCode::InternalError,
1219                        "unexpected bad auth snapshot",
1220                        &[],
1221                    ));
1222                }
1223                for (i, tracker) in trackers.iter().enumerate() {
1224                    let Some(snapopt) = trackers_snapshot.get(i) else {
1225                        return Err(host.err(
1226                            ScErrorType::Auth,
1227                            ScErrorCode::InternalError,
1228                            "unexpected auth snapshot index",
1229                            &[],
1230                        ));
1231                    };
1232                    if let Some(tracker_snapshot) = snapopt {
1233                        tracker
1234                            .try_borrow_mut()
1235                            .map_err(|_| {
1236                                host.err(
1237                                    ScErrorType::Auth,
1238                                    ScErrorCode::InternalError,
1239                                    "unexpected bad auth borrow",
1240                                    &[],
1241                                )
1242                            })?
1243                            .rollback(&tracker_snapshot)?;
1244                    }
1245                }
1246            }
1247            #[cfg(any(test, feature = "recording_mode"))]
1248            AccountTrackersSnapshot::Recording(s) => {
1249                *self.try_borrow_account_trackers_mut(host)? = s;
1250            }
1251        }
1252
1253        let mut invoker_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1254
1255        if invoker_trackers.len() < snapshot.invoker_contract_tracker_root_snapshots.len() {
1256            return Err(host.err(
1257                ScErrorType::Auth,
1258                ScErrorCode::InternalError,
1259                "the number of invoker contract trackers is smaller than in the snapshot",
1260                &[],
1261            ));
1262        }
1263        // If there are more trackers than in the snapshot, then the trackers have been
1264        // created in the current (failed) frame, so we should remove them as a part
1265        // of rollback.
1266        invoker_trackers.truncate(snapshot.invoker_contract_tracker_root_snapshots.len());
1267
1268        for (tracker, snapshot) in invoker_trackers
1269            .iter_mut()
1270            .zip(snapshot.invoker_contract_tracker_root_snapshots.iter())
1271        {
1272            tracker.invocation_tracker.rollback(snapshot)?;
1273        }
1274
1275        #[cfg(any(test, feature = "recording_mode"))]
1276        if let Some(tracker_by_address_handle) = snapshot.tracker_by_address_handle {
1277            match &self.mode {
1278                AuthorizationMode::Enforcing => (),
1279                AuthorizationMode::Recording(recording_info) => {
1280                    *recording_info.try_borrow_tracker_by_address_handle_mut(host)? =
1281                        tracker_by_address_handle;
1282                }
1283            }
1284        }
1285        Ok(())
1286    }
1287
1288    // metering: covered
1289    fn push_tracker_frame(&self, host: &Host) -> Result<(), HostError> {
1290        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1291            // Skip already borrowed trackers, these must be in the middle of
1292            // authentication and hence don't need stack to be updated.
1293            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1294                tracker.push_frame(host.as_budget())?;
1295            }
1296        }
1297        for tracker in self
1298            .try_borrow_invoker_contract_trackers_mut(host)?
1299            .iter_mut()
1300        {
1301            tracker.push_frame(host.as_budget())?;
1302        }
1303        Ok(())
1304    }
1305
1306    // metering: covered
1307    pub(crate) fn push_create_contract_host_fn_frame(
1308        &self,
1309        host: &Host,
1310        args: CreateContractArgsV2,
1311    ) -> Result<(), HostError> {
1312        Vec::<CreateContractArgsV2>::charge_bulk_init_cpy(1, host)?;
1313        self.try_borrow_call_stack_mut(host)?
1314            .push(AuthStackFrame::CreateContractHostFn(args));
1315        self.push_tracker_frame(host)
1316    }
1317
1318    // Records a new call stack frame and returns a snapshot for rolling
1319    // back this stack frame.
1320    // This should be called for every `Host` `push_frame`.
1321    // metering: covered
1322    pub(crate) fn push_frame(
1323        &self,
1324        host: &Host,
1325        frame: &Frame,
1326    ) -> Result<AuthorizationManagerSnapshot, HostError> {
1327        let _span = tracy_span!("push auth frame");
1328        let (contract_id, function_name) = match frame {
1329            Frame::ContractVM { vm, fn_name, .. } => {
1330                (vm.contract_id.metered_clone(host)?, *fn_name)
1331            }
1332            // Skip the top-level host function stack frames as they don't
1333            // contain all the necessary information.
1334            // Use the respective push (like
1335            // `push_create_contract_host_fn_frame`) functions instead to push
1336            // the frame with the required info.
1337            Frame::HostFunction(_) => return self.snapshot(host),
1338            Frame::StellarAssetContract(id, fn_name, ..) => (id.metered_clone(host)?, *fn_name),
1339            #[cfg(any(test, feature = "testutils"))]
1340            Frame::TestContract(tc) => (tc.id.metered_clone(host)?, tc.func),
1341        };
1342        let contract_address = host.add_host_object(ScAddress::Contract(contract_id))?;
1343        Vec::<ContractInvocation>::charge_bulk_init_cpy(1, host)?;
1344        self.try_borrow_call_stack_mut(host)?
1345            .push(AuthStackFrame::Contract(ContractInvocation {
1346                contract_address,
1347                function_name,
1348            }));
1349
1350        self.push_tracker_frame(host)?;
1351        self.snapshot(host)
1352    }
1353
1354    // Pops a call stack frame and maybe rolls back the internal
1355    // state according to the provided snapshot.
1356    // This should be called for every `Host` `pop_frame`.
1357    // metering: covered
1358    pub(crate) fn pop_frame(
1359        &self,
1360        host: &Host,
1361        snapshot: Option<AuthorizationManagerSnapshot>,
1362    ) -> Result<(), HostError> {
1363        let _span = tracy_span!("pop auth frame");
1364        // Important: rollback has to be performed before popping the frame
1365        // from the tracker. This ensures correct work of invoker contract
1366        // trackers for which snapshots are dependent on the current
1367        // call stack.
1368        if let Some(snapshot) = snapshot {
1369            self.rollback(host, snapshot)?;
1370        }
1371        {
1372            let mut call_stack = self.try_borrow_call_stack_mut(host)?;
1373            // Currently we don't push host function call frames, hence this may be
1374            // called with empty stack. We trust the Host to keep things correct,
1375            // i.e. that only host function frames are ignored this way.
1376            if call_stack.is_empty() {
1377                return Ok(());
1378            }
1379            call_stack.pop();
1380        }
1381        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1382            // Skip already borrowed trackers, these must be in the middle of
1383            // authentication and hence don't need stack to be updated.
1384            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1385                tracker.pop_frame();
1386            }
1387        }
1388
1389        let mut invoker_contract_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1390        for tracker in invoker_contract_trackers.iter_mut() {
1391            tracker.pop_frame();
1392        }
1393        // Pop invoker contract trackers that went out of scope. The invariant
1394        // is that tracker only exists for the next sub-contract call (or until
1395        // the tracker's frame itself is popped). Thus trackers form a stack
1396        // where the shorter lifetime trackers are at the top.
1397        while let Some(last) = invoker_contract_trackers.last() {
1398            // *Subtle*: there are two possible scenarios when tracker is considered
1399            // to be out of scope:
1400            // - When the sub-contract call is finished. For example, contract A creates
1401            // a tracker, then calls contract B. When we pop the frame of contract B,
1402            // the tracker's stack will be empty and thus considered to be out of scope.
1403            // - When the contract that created the tracker is going out of scope without
1404            // calling any sub-contracts. For example, contract A creates a tracker and
1405            // returns. When we pop the frame of contract A, the tracker's stack will be
1406            // empty (because it's created empty), and thus considered to be out of scope.
1407            // The invariant above is maintained in both scenarios.
1408            if last.is_out_of_scope() {
1409                invoker_contract_trackers.pop();
1410            } else {
1411                break;
1412            }
1413        }
1414        Ok(())
1415    }
1416
1417    // Returns the recorded per-address authorization payloads that would cover the
1418    // top-level contract function invocation in the enforcing mode.
1419    // Should only be called in the recording mode.
1420    // metering: free, recording mode
1421    #[cfg(any(test, feature = "recording_mode"))]
1422    pub(crate) fn get_recorded_auth_payloads(
1423        &self,
1424        host: &Host,
1425    ) -> Result<Vec<RecordedAuthPayload>, HostError> {
1426        match &self.mode {
1427            AuthorizationMode::Enforcing => Err(HostError::from((
1428                ScErrorType::Auth,
1429                ScErrorCode::InternalError,
1430            ))),
1431            AuthorizationMode::Recording(_) => Ok(self
1432                .try_borrow_account_trackers(host)?
1433                .iter()
1434                .map(|tracker| tracker.try_borrow_or_err()?.get_recorded_auth_payload(host))
1435                .collect::<Result<Vec<RecordedAuthPayload>, HostError>>()?),
1436        }
1437    }
1438
1439    // For recording mode, emulates authentication that would normally happen in
1440    // the enforcing mode.
1441    // This helps to build a more realistic footprint and produce more correct
1442    // meterting data for the recording mode.
1443    // No-op in the enforcing mode.
1444    // metering: covered
1445    #[cfg(any(test, feature = "recording_mode"))]
1446    pub(crate) fn maybe_emulate_authentication(&self, host: &Host) -> Result<(), HostError> {
1447        match &self.mode {
1448            AuthorizationMode::Enforcing => Ok(()),
1449            AuthorizationMode::Recording(_) => {
1450                for tracker in self.try_borrow_account_trackers(host)?.iter() {
1451                    tracker
1452                        .try_borrow_mut_or_err()?
1453                        .emulate_authentication(host)?;
1454                }
1455                Ok(())
1456            }
1457        }
1458    }
1459
1460    // Returns a 'reset' instance of `AuthorizationManager` that has the same
1461    // mode, but no data.
1462    // metering: free, testutils
1463    #[cfg(any(test, feature = "testutils"))]
1464    pub(crate) fn reset(&mut self) {
1465        *self = match &self.mode {
1466            AuthorizationMode::Enforcing => {
1467                AuthorizationManager::new_enforcing_without_authorizations()
1468            }
1469            AuthorizationMode::Recording(rec_info) => {
1470                AuthorizationManager::new_recording(rec_info.disable_non_root_auth)
1471            }
1472        }
1473    }
1474
1475    // Returns all authorizations that have been authenticated for the
1476    // last contract invocation.
1477    // metering: free, testutils
1478    #[cfg(any(test, feature = "testutils"))]
1479    pub(crate) fn get_authenticated_authorizations(
1480        &self,
1481        host: &Host,
1482    ) -> Vec<(ScAddress, xdr::SorobanAuthorizedInvocation)> {
1483        host.as_budget()
1484            .with_observable_shadow_mode(|| {
1485                self.account_trackers
1486                    .borrow()
1487                    .iter()
1488                    .filter(|t| t.borrow().verified)
1489                    .map(|t| {
1490                        (
1491                            host.scaddress_from_address(t.borrow().address).unwrap(),
1492                            t.borrow()
1493                                .invocation_tracker
1494                                .root_authorized_invocation
1495                                .to_xdr(host, true)
1496                                .unwrap(),
1497                        )
1498                    })
1499                    .metered_collect(host)
1500            })
1501            .unwrap()
1502    }
1503}
1504
1505// Some helper extensions to support test-observation.
1506#[allow(dead_code)]
1507impl AuthorizationManager {
1508    pub(crate) fn stack_size(&self) -> usize {
1509        if let Ok(call_stack) = self.call_stack.try_borrow() {
1510            call_stack.len()
1511        } else {
1512            0
1513        }
1514    }
1515
1516    pub(crate) fn stack_hash(&self, budget: &Budget) -> Result<u64, HostError> {
1517        use std::hash::Hasher;
1518        if let Ok(call_stack) = self.call_stack.try_borrow() {
1519            let mut state = CountingHasher::default();
1520            call_stack.metered_hash(&mut state, budget)?;
1521            Ok(state.finish())
1522        } else {
1523            Ok(0)
1524        }
1525    }
1526
1527    pub(crate) fn trackers_hash_and_size(
1528        &self,
1529        budget: &Budget,
1530    ) -> Result<(u64, usize), HostError> {
1531        use std::hash::Hasher;
1532        let mut size: usize = 0;
1533        let mut state = CountingHasher::default();
1534        self.mode.metered_hash(&mut state, budget)?;
1535        if let Ok(account_trackers) = self.account_trackers.try_borrow() {
1536            for tracker in account_trackers.iter() {
1537                if let Ok(tracker) = tracker.try_borrow() {
1538                    size = size.saturating_add(1);
1539                    tracker.metered_hash(&mut state, budget)?;
1540                }
1541            }
1542        }
1543        if let Ok(invoker_contract_trackers) = self.invoker_contract_trackers.try_borrow() {
1544            for tracker in invoker_contract_trackers.iter() {
1545                size = size.saturating_add(1);
1546                tracker.metered_hash(&mut state, budget)?;
1547            }
1548        }
1549        Ok((state.finish(), size))
1550    }
1551}
1552
1553impl InvocationTracker {
1554    // metering: covered by components
1555    fn from_xdr(
1556        host: &Host,
1557        root_invocation: xdr::SorobanAuthorizedInvocation,
1558    ) -> Result<Self, HostError> {
1559        Ok(Self {
1560            root_authorized_invocation: AuthorizedInvocation::from_xdr(host, root_invocation)?,
1561            match_stack: vec![],
1562            root_exhausted_frame: None,
1563            is_fully_processed: false,
1564        })
1565    }
1566
1567    // metering: free
1568    fn new(root_authorized_invocation: AuthorizedInvocation) -> Self {
1569        Self {
1570            root_authorized_invocation,
1571            match_stack: vec![],
1572            root_exhausted_frame: None,
1573            is_fully_processed: false,
1574        }
1575    }
1576
1577    // metering: free for recording
1578    #[cfg(any(test, feature = "recording_mode"))]
1579    fn new_recording(function: AuthorizedFunction, current_stack_len: usize) -> Self {
1580        // Create the stack of `MatchState::Unmatched` leading to the current invocation to
1581        // represent invocations that didn't need authorization on behalf of
1582        // the tracked address.
1583        let mut match_stack = vec![MatchState::Unmatched; current_stack_len - 1];
1584        // Add a MatchState for the current(root) invocation.
1585        match_stack.push(MatchState::RootMatch);
1586        let root_exhausted_frame = Some(match_stack.len() - 1);
1587        Self {
1588            root_authorized_invocation: AuthorizedInvocation::new_recording(function),
1589            match_stack,
1590            root_exhausted_frame,
1591            is_fully_processed: false,
1592        }
1593    }
1594
1595    // Walks a path in the tree defined by `match_stack` and
1596    // returns the last visited authorized node.
1597    // metering: free
1598    fn last_authorized_invocation_mut(
1599        &mut self,
1600    ) -> Result<Option<&mut AuthorizedInvocation>, HostError> {
1601        for (i, m) in self.match_stack.iter().enumerate() {
1602            match m {
1603                MatchState::RootMatch => {
1604                    return Ok(Some(
1605                        self.root_authorized_invocation
1606                            .last_authorized_invocation_mut(&self.match_stack, i + 1)?,
1607                    ));
1608                }
1609                MatchState::SubMatch { .. } => {
1610                    return Err((ScErrorType::Auth, ScErrorCode::InternalError).into())
1611                }
1612                MatchState::Unmatched => (),
1613            }
1614        }
1615        Ok(None)
1616    }
1617
1618    // metering: covered
1619    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1620        Vec::<usize>::charge_bulk_init_cpy(1, budget)?;
1621        self.match_stack.push(MatchState::Unmatched);
1622        Ok(())
1623    }
1624
1625    // metering: free
1626    fn pop_frame(&mut self) {
1627        self.match_stack.pop();
1628        if let Some(root_exhausted_frame) = self.root_exhausted_frame {
1629            if root_exhausted_frame >= self.match_stack.len() {
1630                self.is_fully_processed = true;
1631            }
1632        }
1633    }
1634
1635    // metering: free
1636    fn is_empty(&self) -> bool {
1637        self.match_stack.is_empty()
1638    }
1639
1640    // metering: free
1641    fn is_active(&self) -> bool {
1642        self.root_authorized_invocation.is_exhausted && !self.is_fully_processed
1643    }
1644
1645    // metering: free
1646    fn current_frame_is_already_matched(&self) -> bool {
1647        match self.match_stack.last() {
1648            Some(x) => x.is_matched(),
1649            _ => false,
1650        }
1651    }
1652
1653    // Tries to match the provided invocation as an extension of the last
1654    // currently-matched sub-invocation of the authorized invocation tree (or
1655    // the root, if there is no matched invocation yet). If matching succeeds,
1656    // it writes the match to the corresponding entry in
1657    // [`InvocationTracker::match_stack`].
1658    //
1659    // Returns `true` if the match has been found for the first time per current
1660    // frame.
1661    //
1662    // Metering: covered by components
1663    fn maybe_extend_invocation_match(
1664        &mut self,
1665        host: &Host,
1666        function: &AuthorizedFunction,
1667        allow_matching_root: bool,
1668    ) -> Result<bool, HostError> {
1669        if self.current_frame_is_already_matched() {
1670            return Ok(false);
1671        }
1672        let mut new_match_state = MatchState::Unmatched;
1673        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1674            for (index_in_parent, sub_invocation) in
1675                curr_invocation.sub_invocations.iter_mut().enumerate()
1676            {
1677                if !sub_invocation.is_exhausted
1678                    && host.compare(&sub_invocation.function, function)?.is_eq()
1679                {
1680                    new_match_state = MatchState::SubMatch { index_in_parent };
1681                    sub_invocation.is_exhausted = true;
1682                    break;
1683                }
1684            }
1685        } else if !self.root_authorized_invocation.is_exhausted
1686            && allow_matching_root
1687            && host
1688                .compare(&self.root_authorized_invocation.function, &function)?
1689                .is_eq()
1690        {
1691            new_match_state = MatchState::RootMatch;
1692            self.root_authorized_invocation.is_exhausted = true;
1693            self.root_exhausted_frame = Some(self.match_stack.len() - 1);
1694        }
1695        if new_match_state.is_matched() {
1696            *self.match_stack.last_mut().ok_or_else(|| {
1697                host.err(
1698                    ScErrorType::Auth,
1699                    ScErrorCode::InternalError,
1700                    "invalid match_stack",
1701                    &[],
1702                )
1703            })? = new_match_state;
1704        }
1705        Ok(new_match_state.is_matched())
1706    }
1707
1708    // Records the invocation in this tracker.
1709    // This is needed for the recording mode only.
1710    // This assumes that the address matching is correctly performed before
1711    // calling this.
1712    // metering: free for recording
1713    #[cfg(any(test, feature = "recording_mode"))]
1714    fn record_invocation(
1715        &mut self,
1716        host: &Host,
1717        function: AuthorizedFunction,
1718    ) -> Result<(), HostError> {
1719        if self.current_frame_is_already_matched() {
1720            return Err(host.err(
1721                ScErrorType::Auth,
1722                ScErrorCode::ExistingValue,
1723                "frame is already authorized",
1724                &[],
1725            ));
1726        }
1727        // Emulate the function comparison that happens in the enforcing mode
1728        // when we're matching a `require_auth` invocation to the authorized
1729        // function from XDR.
1730        let function_for_comparison = AuthorizedFunction::from_xdr(host, function.to_xdr(host)?)?;
1731        let _ = host.compare(&function, &function_for_comparison)?;
1732        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1733            curr_invocation
1734                .sub_invocations
1735                .push(AuthorizedInvocation::new_recording(function));
1736            let index_in_parent = curr_invocation.sub_invocations.len() - 1;
1737            *self.match_stack.last_mut().unwrap() = MatchState::SubMatch { index_in_parent };
1738        } else {
1739            // This would be a bug
1740            return Err(host.err(
1741                ScErrorType::Auth,
1742                ScErrorCode::InternalError,
1743                "unexpected missing authorized invocation",
1744                &[],
1745            ));
1746        }
1747        Ok(())
1748    }
1749
1750    // metering: free
1751    #[cfg(any(test, feature = "recording_mode"))]
1752    fn has_matched_invocations_in_stack(&self) -> bool {
1753        self.match_stack.iter().any(|i| i.is_matched())
1754    }
1755
1756    // metering: covered
1757    fn snapshot(&self, budget: &Budget) -> Result<AuthorizedInvocationSnapshot, HostError> {
1758        self.root_authorized_invocation.snapshot(budget)
1759    }
1760
1761    // metering: covered
1762    fn rollback(&mut self, snapshot: &AuthorizedInvocationSnapshot) -> Result<(), HostError> {
1763        self.root_authorized_invocation.rollback(snapshot)?;
1764        // Invocation can only be rolled back from 'exhausted' to
1765        // 'non-exhausted' state (as there is no other way to go from
1766        // 'exhausted' state back to 'non-exhausted' state).
1767        if !self.root_authorized_invocation.is_exhausted {
1768            self.root_exhausted_frame = None;
1769            self.is_fully_processed = false;
1770        }
1771        Ok(())
1772    }
1773}
1774
1775impl AccountAuthorizationTracker {
1776    // Metering: covered by the host and components
1777    fn from_authorization_entry(
1778        host: &Host,
1779        auth_entry: SorobanAuthorizationEntry,
1780    ) -> Result<Self, HostError> {
1781        let (address, nonce, signature, is_transaction_source_account) =
1782            match auth_entry.credentials {
1783                SorobanCredentials::SourceAccount => (
1784                    host.source_account_address()?.ok_or_else(|| {
1785                        host.err(
1786                            ScErrorType::Auth,
1787                            ScErrorCode::InternalError,
1788                            "source account is missing when setting auth entries",
1789                            &[],
1790                        )
1791                    })?,
1792                    None,
1793                    Val::VOID.into(),
1794                    true,
1795                ),
1796                SorobanCredentials::Address(address_creds) => (
1797                    host.add_host_object(address_creds.address)?,
1798                    Some((
1799                        address_creds.nonce,
1800                        address_creds.signature_expiration_ledger,
1801                    )),
1802                    host.to_host_val(&address_creds.signature)?,
1803                    false,
1804                ),
1805            };
1806        Ok(Self {
1807            address,
1808            invocation_tracker: InvocationTracker::from_xdr(host, auth_entry.root_invocation)?,
1809            signature,
1810            verified: false,
1811            is_transaction_source_account,
1812            nonce,
1813        })
1814    }
1815
1816    // metering: free, since this is recording mode only
1817    #[cfg(any(test, feature = "recording_mode"))]
1818    fn new_recording(
1819        host: &Host,
1820        address: AddressObject,
1821        function: AuthorizedFunction,
1822        current_stack_len: usize,
1823    ) -> Result<Self, HostError> {
1824        if current_stack_len == 0 {
1825            // This would be a bug.
1826            return Err(host.err(
1827                ScErrorType::Auth,
1828                ScErrorCode::InternalError,
1829                "unexpected empty stack in recording auth",
1830                &[],
1831            ));
1832        }
1833        // Decide if we're tracking the transaction source account, and if so
1834        // don't bother with a nonce.
1835        let is_transaction_source_account =
1836            if let Some(source_acc) = host.source_account_address()? {
1837                host.compare(&source_acc, &address)?.is_eq()
1838            } else {
1839                false
1840            };
1841        let nonce = if !is_transaction_source_account {
1842            let random_nonce: i64 =
1843                host.with_recording_auth_nonce_prng(|p| Ok(p.gen_range(0..=i64::MAX)))?;
1844            // We use the `max_live_until_ledger` as the nonce lifetime here
1845            // in order to account for a maximum possible rent fee (given maximum
1846            // possible signature expiration). However, we don't want to actually
1847            // store that as nonce expiration ledger in the recording tracker,
1848            // as users are able (and encouraged) to customize the signature
1849            // expiration after simulation and before signing the auth payload.
1850            host.consume_nonce(address, random_nonce, host.max_live_until_ledger()?)?;
1851            Some((random_nonce, 0))
1852        } else {
1853            None
1854        };
1855        Ok(Self {
1856            address,
1857            invocation_tracker: InvocationTracker::new_recording(function, current_stack_len),
1858            signature: Val::VOID.into(),
1859            verified: true,
1860            is_transaction_source_account,
1861            nonce,
1862        })
1863    }
1864
1865    // Tries to find and enforce the provided invocation with this tracker and
1866    // lazily performs authentication when needed.
1867    // This is needed for the enforcing mode only.
1868    // This assumes that the address matching is correctly performed before
1869    // calling this.
1870    // Returns true/false based on whether the invocation is found in the
1871    // tracker. Returns error if invocation has been found, but the tracker
1872    // itself is not valid (failed authentication or nonce check).
1873    // metering: covered
1874    fn maybe_authorize_invocation(
1875        &mut self,
1876        host: &Host,
1877        function: &AuthorizedFunction,
1878        allow_matching_root: bool,
1879    ) -> Result<bool, HostError> {
1880        if !self.invocation_tracker.maybe_extend_invocation_match(
1881            host,
1882            function,
1883            allow_matching_root,
1884        )? {
1885            // The call isn't found in the currently tracked tree or is already
1886            // authorized in it.
1887            // That doesn't necessarily mean it's unauthorized (it can be
1888            // authorized in a different tracker).
1889            return Ok(false);
1890        }
1891        if !self.verified {
1892            let authenticate_res = self
1893                .authenticate(host)
1894                .map_err(|err| {
1895                    // Convert any recoverable errors to auth errors so that it's
1896                    // not possible to confuse them for the errors of the
1897                    // contract that has called `require_auth`.
1898                    // While there is no 'recovery' here, non-recoverable errors
1899                    // aren't really useful for decoration.
1900                    if err.is_recoverable() {
1901                        // Also log the original error for diagnostics.
1902                        host.err(
1903                            ScErrorType::Auth,
1904                            ScErrorCode::InvalidAction,
1905                            "failed account authentication with error",
1906                            &[self.address.into(), err.error.to_val()],
1907                        )
1908                    } else {
1909                        err
1910                    }
1911                })
1912                .and_then(|_| self.verify_and_consume_nonce(host));
1913            if let Some(err) = authenticate_res.err() {
1914                return Err(err);
1915            }
1916            self.verified = true;
1917        }
1918        Ok(true)
1919    }
1920
1921    // Records the invocation in this tracker.
1922    // This is needed for the recording mode only.
1923    // This assumes that the address matching is correctly performed before
1924    // calling this.
1925    // metering: free for recording
1926    #[cfg(any(test, feature = "recording_mode"))]
1927    fn record_invocation(
1928        &mut self,
1929        host: &Host,
1930        function: AuthorizedFunction,
1931    ) -> Result<(), HostError> {
1932        self.invocation_tracker.record_invocation(host, function)
1933    }
1934
1935    // Build the authorization payload from the invocations recorded in this
1936    // tracker.
1937    // metering: free for recording
1938    #[cfg(any(test, feature = "recording_mode"))]
1939    fn get_recorded_auth_payload(&self, host: &Host) -> Result<RecordedAuthPayload, HostError> {
1940        host.as_budget().with_observable_shadow_mode(|| {
1941            Ok(RecordedAuthPayload {
1942                address: if !self.is_transaction_source_account {
1943                    Some(host.visit_obj(self.address, |a: &ScAddress| a.metered_clone(host))?)
1944                } else {
1945                    None
1946                },
1947                invocation: self
1948                    .invocation_tracker
1949                    .root_authorized_invocation
1950                    .to_xdr(host, false)?,
1951                nonce: self.nonce.map(|(nonce, _)| nonce),
1952            })
1953        })
1954    }
1955
1956    // Checks if there is at least one authorized invocation in the current call
1957    // stack.
1958    // metering: free
1959    #[cfg(any(test, feature = "recording_mode"))]
1960    fn has_authorized_invocations_in_stack(&self) -> bool {
1961        self.invocation_tracker.has_matched_invocations_in_stack()
1962    }
1963
1964    // metering: covered
1965    fn root_invocation_to_xdr(
1966        &self,
1967        host: &Host,
1968    ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
1969        self.invocation_tracker
1970            .root_authorized_invocation
1971            .to_xdr(host, false)
1972    }
1973
1974    // metering: covered
1975    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1976        self.invocation_tracker.push_frame(budget)
1977    }
1978
1979    // metering: covered
1980    fn pop_frame(&mut self) {
1981        self.invocation_tracker.pop_frame();
1982    }
1983
1984    // metering: covered
1985    fn verify_and_consume_nonce(&mut self, host: &Host) -> Result<(), HostError> {
1986        if self.is_transaction_source_account {
1987            return Ok(());
1988        }
1989        if let Some((nonce, live_until_ledger)) = &self.nonce {
1990            let ledger_seq = host.with_ledger_info(|li| Ok(li.sequence_number))?;
1991            if ledger_seq > *live_until_ledger {
1992                return Err(host.err(
1993                    ScErrorType::Auth,
1994                    ScErrorCode::InvalidInput,
1995                    "signature has expired",
1996                    &[
1997                        self.address.into(),
1998                        ledger_seq.try_into_val(host)?,
1999                        live_until_ledger.try_into_val(host)?,
2000                    ],
2001                ));
2002            }
2003            let max_live_until_ledger = host.max_live_until_ledger()?;
2004            if *live_until_ledger > max_live_until_ledger {
2005                return Err(host.err(
2006                    ScErrorType::Auth,
2007                    ScErrorCode::InvalidInput,
2008                    "signature expiration is too late",
2009                    &[
2010                        self.address.into(),
2011                        max_live_until_ledger.try_into_val(host)?,
2012                        live_until_ledger.try_into_val(host)?,
2013                    ],
2014                ));
2015            }
2016
2017            return host.consume_nonce(self.address, *nonce, *live_until_ledger);
2018        }
2019        Err(host.err(
2020            ScErrorType::Auth,
2021            ScErrorCode::InternalError,
2022            "unexpected nonce verification state",
2023            &[],
2024        ))
2025    }
2026
2027    // Computes the payload that has to be signed in order to authenticate
2028    // the authorized invocation tree corresponding to this tracker.
2029    // metering: covered by components
2030    fn get_signature_payload(&self, host: &Host) -> Result<[u8; 32], HostError> {
2031        let (nonce, live_until_ledger) = self.nonce.ok_or_else(|| {
2032            host.err(
2033                ScErrorType::Auth,
2034                ScErrorCode::InternalError,
2035                "unexpected missing nonce",
2036                &[],
2037            )
2038        })?;
2039        let payload_preimage =
2040            HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization {
2041                network_id: Hash(host.with_ledger_info(|li| li.network_id.metered_clone(host))?),
2042                nonce,
2043                signature_expiration_ledger: live_until_ledger,
2044                invocation: self.root_invocation_to_xdr(host)?,
2045            });
2046
2047        host.metered_hash_xdr(&payload_preimage)
2048    }
2049
2050    // metering: covered by the hsot
2051    fn authenticate(&self, host: &Host) -> Result<(), HostError> {
2052        if self.is_transaction_source_account {
2053            return Ok(());
2054        }
2055
2056        let sc_addr = host.scaddress_from_address(self.address)?;
2057        // TODO: there should also be a mode where a dummy payload is used
2058        // instead (for enforcing mode preflight).
2059        let payload = self.get_signature_payload(host)?;
2060        match sc_addr {
2061            ScAddress::Account(acc) => {
2062                check_account_authentication(host, acc, &payload, self.signature)
2063            }
2064            ScAddress::Contract(acc_contract) => check_account_contract_auth(
2065                host,
2066                &acc_contract,
2067                &payload,
2068                self.signature,
2069                &self.invocation_tracker.root_authorized_invocation,
2070            ),
2071            _ => Err(host.err(
2072                ScErrorType::Object,
2073                ScErrorCode::InternalError,
2074                "unexpected address type in auth",
2075                &[self.address.into()],
2076            )),
2077        }
2078    }
2079
2080    // Emulates authentication for the recording mode.
2081    // metering: covered
2082    #[cfg(any(test, feature = "recording_mode"))]
2083    fn emulate_authentication(&mut self, host: &Host) -> Result<(), HostError> {
2084        if self.is_transaction_source_account {
2085            return Ok(());
2086        }
2087        let sc_addr = host.scaddress_from_address(self.address)?;
2088        match sc_addr {
2089            ScAddress::Account(acc) => {
2090                // Emulate verification of a single signature that belongs to this
2091                // account.
2092                // We could emulate more (up to 20) signature verifications, but
2093                // since signature verification is a pretty expensive operation, while
2094                // multisig in combination with Soroban auth is probably pretty rare,
2095                // multisig users should either use enforcing auth simulation, or
2096                // intentionally increase the instruction count on the recording result.
2097                let key_bytes = match &acc.0 {
2098                    PublicKey::PublicKeyTypeEd25519(k) => k.0,
2099                };
2100                let signature = AccountEd25519Signature {
2101                    public_key: BytesN::from_slice(host, &key_bytes)?,
2102                    signature: BytesN::from_slice(host, &[0_u8; 64])?,
2103                };
2104                let signatures = host_vec![host, signature]?;
2105                self.signature = signatures.into();
2106                // Authentication is expected to fail here after signature verification,
2107                // so we suppress the error and diagnostics.
2108                host.with_suppressed_diagnostic_events(|| {
2109                    let _ = self.authenticate(host);
2110                    Ok(())
2111                })?;
2112
2113                // Emulate a clone of the account, which serves 2 purposes:
2114                // - Account for metered clone in `get_signer_weight_from_account`
2115                // - Return budget error in case if it was suppressed above.
2116                let _ = acc.metered_clone(host.as_budget())?;
2117            }
2118            ScAddress::Contract(contract_id) => {
2119                let instance_key = host.contract_instance_ledger_key(&contract_id)?;
2120                let entry = host
2121                    .try_borrow_storage_mut()?
2122                    .try_get(&instance_key, host, None)?;
2123                // In test scenarios we often may not have any actual instance, which is fine most
2124                // of the time, so we don't return any errors.
2125                // In simulation scenarios the instance will likely be there, and when it's
2126                // not, we still make our best effort and include at least the necessary instance key
2127                // into the footprint.
2128                let instance = if let Some(entry) = entry {
2129                    match &entry.data {
2130                        LedgerEntryData::ContractData(e) => match &e.val {
2131                            ScVal::ContractInstance(instance) => instance.metered_clone(host)?,
2132                            _ => {
2133                                return Ok(());
2134                            }
2135                        },
2136                        _ => {
2137                            return Ok(());
2138                        }
2139                    }
2140                } else {
2141                    return Ok(());
2142                };
2143
2144                match &instance.executable {
2145                    ContractExecutable::Wasm(wasm_hash) => {
2146                        let wasm_key = host.contract_code_ledger_key(wasm_hash)?;
2147                        let _ = host
2148                            .try_borrow_storage_mut()?
2149                            .try_get(&wasm_key, host, None)?;
2150                    }
2151                    ContractExecutable::StellarAsset => (),
2152                }
2153            }
2154            _ => {
2155                return Err(host.err(
2156                    ScErrorType::Object,
2157                    ScErrorCode::InternalError,
2158                    "encountered unexpected ScAddress type",
2159                    &[],
2160                ));
2161            }
2162        }
2163        Ok(())
2164    }
2165
2166    // metering: covered
2167    fn snapshot(&self, budget: &Budget) -> Result<AccountAuthorizationTrackerSnapshot, HostError> {
2168        Ok(AccountAuthorizationTrackerSnapshot {
2169            invocation_tracker_root_snapshot: self.invocation_tracker.snapshot(budget)?,
2170            // The verification status can be rolled back in case
2171            // of a contract failure. In case if the root call has
2172            // failed, the nonce will get 'un-consumed' due to storage
2173            // rollback. Thus we need to run verification and consume
2174            // it again in case if the call is retried. Another subtle
2175            // case where this behavior is important is the case when
2176            // custom account's authentication function depends on
2177            // some ledger state which might get modified in-between calls.
2178            verified: self.verified,
2179        })
2180    }
2181
2182    // metering: covered
2183    fn rollback(
2184        &mut self,
2185        snapshot: &AccountAuthorizationTrackerSnapshot,
2186    ) -> Result<(), HostError> {
2187        self.invocation_tracker
2188            .rollback(&snapshot.invocation_tracker_root_snapshot)?;
2189        self.verified = snapshot.verified;
2190        Ok(())
2191    }
2192
2193    // metering: free
2194    fn is_active(&self) -> bool {
2195        self.invocation_tracker.is_active()
2196    }
2197
2198    // metering: free
2199    fn current_frame_is_already_matched(&self) -> bool {
2200        self.invocation_tracker.current_frame_is_already_matched()
2201    }
2202}
2203
2204impl InvokerContractAuthorizationTracker {
2205    // metering: covered by components
2206    fn new_with_curr_contract_as_invoker(
2207        host: &Host,
2208        invoker_auth_entry: Val,
2209    ) -> Result<Self, HostError> {
2210        let invoker_sc_addr = ScAddress::Contract(host.get_current_contract_id_internal()?);
2211        let authorized_invocation = invoker_contract_auth_to_authorized_invocation(
2212            host,
2213            &invoker_sc_addr,
2214            invoker_auth_entry,
2215        )?;
2216        let invocation_tracker = InvocationTracker::new(authorized_invocation);
2217        Ok(Self {
2218            contract_address: host.add_host_object(invoker_sc_addr)?,
2219            invocation_tracker,
2220        })
2221    }
2222
2223    // metering: covered
2224    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
2225        self.invocation_tracker.push_frame(budget)
2226    }
2227
2228    // metering: covered
2229    fn pop_frame(&mut self) {
2230        self.invocation_tracker.pop_frame();
2231    }
2232
2233    // metering: free
2234    fn is_out_of_scope(&self) -> bool {
2235        self.invocation_tracker.is_empty()
2236    }
2237
2238    // metering: covered
2239    fn maybe_authorize_invocation(
2240        &mut self,
2241        host: &Host,
2242        function: &AuthorizedFunction,
2243    ) -> Result<bool, HostError> {
2244        // Authorization is successful if function is just matched by the
2245        // tracker. No authentication is needed.
2246        self.invocation_tracker
2247            .maybe_extend_invocation_match(host, function, true)
2248    }
2249}
2250
2251impl Host {
2252    // metering: covered by components
2253    fn consume_nonce(
2254        &self,
2255        address: AddressObject,
2256        nonce: i64,
2257        live_until_ledger: u32,
2258    ) -> Result<(), HostError> {
2259        let nonce_key_scval = ScVal::LedgerKeyNonce(ScNonceKey { nonce });
2260        let sc_address = self.scaddress_from_address(address)?;
2261        let nonce_key = self.storage_key_for_address(
2262            sc_address.metered_clone(self)?,
2263            nonce_key_scval.metered_clone(self)?,
2264            xdr::ContractDataDurability::Temporary,
2265        )?;
2266        let live_until_ledger = live_until_ledger
2267            .max(self.get_min_live_until_ledger(xdr::ContractDataDurability::Temporary)?);
2268        self.with_mut_storage(|storage| {
2269            if storage.has(&nonce_key, self, None).map_err(|err| {
2270                if err.error.is_type(ScErrorType::Storage)
2271                    && err.error.is_code(ScErrorCode::ExceededLimit)
2272                {
2273                    return self.err(
2274                        ScErrorType::Storage,
2275                        ScErrorCode::ExceededLimit,
2276                        "trying to access nonce outside of footprint for address",
2277                        &[address.to_val()],
2278                    );
2279                }
2280                err
2281            })? {
2282                return Err(self.err(
2283                    ScErrorType::Auth,
2284                    ScErrorCode::ExistingValue,
2285                    "nonce already exists for address",
2286                    &[address.into()],
2287                ));
2288            }
2289            let data = LedgerEntryData::ContractData(ContractDataEntry {
2290                contract: sc_address,
2291                key: nonce_key_scval,
2292                val: ScVal::Void,
2293                durability: xdr::ContractDataDurability::Temporary,
2294                ext: xdr::ExtensionPoint::V0,
2295            });
2296            let entry = LedgerEntry {
2297                last_modified_ledger_seq: 0,
2298                data,
2299                ext: LedgerEntryExt::V0,
2300            };
2301            storage.put(
2302                &nonce_key,
2303                &Rc::metered_new(entry, self)?,
2304                Some(live_until_ledger),
2305                self,
2306                None,
2307            )
2308        })
2309    }
2310
2311    // Returns the recorded per-address authorization payloads that would cover the
2312    // top-level contract function invocation in the enforcing mode.
2313    // This should only be called in the recording authorization mode, i.e. only
2314    // if `switch_to_recording_auth` has been called.
2315    #[cfg(any(test, feature = "recording_mode"))]
2316    pub fn get_recorded_auth_payloads(&self) -> Result<Vec<RecordedAuthPayload>, HostError> {
2317        #[cfg(not(any(test, feature = "testutils")))]
2318        {
2319            self.try_borrow_authorization_manager()?
2320                .get_recorded_auth_payloads(self)
2321        }
2322        #[cfg(any(test, feature = "testutils"))]
2323        {
2324            let payloads = self
2325                .try_borrow_previous_authorization_manager()?
2326                .as_ref()
2327                .ok_or_else(|| {
2328                    self.err(
2329                        ScErrorType::Auth,
2330                        ScErrorCode::InvalidAction,
2331                        "previous invocation is missing - no auth data to get",
2332                        &[],
2333                    )
2334                })?
2335                .get_recorded_auth_payloads(self)?;
2336            Ok(payloads)
2337        }
2338    }
2339}
2340
2341#[cfg(any(test, feature = "testutils"))]
2342use crate::{host::frame::CallParams, xdr::SorobanAuthorizedInvocation};
2343
2344#[cfg(any(test, feature = "testutils"))]
2345impl Host {
2346    /// Invokes the reserved `__check_auth` function on a provided contract.
2347    ///
2348    /// This is useful for testing the custom account contracts. Otherwise, the
2349    /// host prohibits calling `__check_auth` outside of internal implementation
2350    /// of `require_auth[_for_args]` calls.
2351    pub fn call_account_contract_check_auth(
2352        &self,
2353        contract: AddressObject,
2354        args: VecObject,
2355    ) -> Result<Val, HostError> {
2356        let _invocation_meter_scope = self.maybe_meter_invocation(
2357            crate::host::invocation_metering::MeteringInvocation::check_auth_invocation(
2358                self, contract,
2359            ),
2360        );
2361
2362        use crate::builtin_contracts::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME;
2363        let contract_id = self.contract_id_from_address(contract)?;
2364        let args_vec = self.call_args_from_obj(args)?;
2365        let res = self.call_n_internal(
2366            &contract_id,
2367            ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(self)?,
2368            args_vec.as_slice(),
2369            CallParams::default_internal_call(),
2370        );
2371        if let Err(e) = &res {
2372            use crate::ErrorHandler;
2373            self.error(
2374                e.error,
2375                "check auth invocation for a custom account contract failed",
2376                &[contract.to_val(), args.to_val()],
2377            );
2378        }
2379        res
2380    }
2381
2382    /// Returns the current state of the authorization manager.
2383    ///
2384    /// Use this in conjunction with `set_auth_manager` to do authorized
2385    /// operations without breaking the current authorization state (useful for
2386    /// preserving the auth state while doing the generic test setup).
2387    pub fn snapshot_auth_manager(&self) -> Result<AuthorizationManager, HostError> {
2388        Ok(self.try_borrow_authorization_manager()?.clone())
2389    }
2390
2391    /// Switches host to the recording authorization mode and inherits the
2392    /// recording mode settings from the provided authorization manager settings
2393    /// in case if it used the recording mode.
2394    ///
2395    /// This is similar to `switch_to_recording_auth`, but should be preferred
2396    /// to use in conjunction with `snapshot_auth_manager`, such that the
2397    /// recording mode settings are not overridden.
2398    pub fn switch_to_recording_auth_inherited_from_snapshot(
2399        &self,
2400        auth_manager_snapshot: &AuthorizationManager,
2401    ) -> Result<(), HostError> {
2402        let disable_non_root_auth = match &auth_manager_snapshot.mode {
2403            AuthorizationMode::Enforcing => true,
2404            AuthorizationMode::Recording(recording_auth_info) => {
2405                recording_auth_info.disable_non_root_auth
2406            }
2407        };
2408        *self.try_borrow_authorization_manager_mut()? =
2409            AuthorizationManager::new_recording(disable_non_root_auth);
2410        Ok(())
2411    }
2412
2413    /// Replaces authorization manager with the provided new instance.
2414    ///
2415    /// Use this in conjunction with `snapshot_auth_manager` to do authorized
2416    /// operations without breaking the current authorization state (useful for
2417    /// preserving the auth state while doing the generic test setup).
2418    pub fn set_auth_manager(&self, auth_manager: AuthorizationManager) -> Result<(), HostError> {
2419        *self.try_borrow_authorization_manager_mut()? = auth_manager;
2420        Ok(())
2421    }
2422
2423    // Returns the authorizations that have been authenticated for the last
2424    // contract invocation.
2425    //
2426    // Authenticated means that either the authorization was authenticated using
2427    // the actual authorization logic for that authorization in enforced mode,
2428    // or that it was recorded in recording mode and authorization was assumed
2429    // successful.
2430    pub fn get_authenticated_authorizations(
2431        &self,
2432    ) -> Result<Vec<(ScAddress, SorobanAuthorizedInvocation)>, HostError> {
2433        Ok(self
2434            .try_borrow_previous_authorization_manager_mut()?
2435            .as_mut()
2436            .map(|am| am.get_authenticated_authorizations(self))
2437            // If no AuthorizationManager is setup, no authorizations could have
2438            // taken place so return an empty vec.
2439            .unwrap_or_default())
2440    }
2441}
2442
2443// metering: free for testutils
2444#[cfg(any(test, feature = "testutils"))]
2445impl PartialEq for RecordedAuthPayload {
2446    fn eq(&self, other: &Self) -> bool {
2447        self.address == other.address
2448            && self.invocation == other.invocation
2449            // Compare nonces only by presence of the value - recording mode
2450            // generates random nonces.
2451            && self.nonce.is_some() == other.nonce.is_some()
2452    }
2453}