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 require_auth_recording(
990        &self,
991        host: &Host,
992        address: AddressObject,
993        function: AuthorizedFunction,
994        recording_info: &RecordingAuthInfo,
995    ) -> Result<(), HostError> {
996        // At first, try to find the tracker for this exact address
997        // object.
998        // This is a best-effort heuristic to come up with a reasonably
999        // looking recording tree for cases when multiple instances of
1000        // the same exact address are used.
1001        let address_obj_handle = address.get_handle();
1002        let existing_tracker_id = recording_info
1003            .try_borrow_tracker_by_address_handle(host)?
1004            .get(&address_obj_handle)
1005            .copied();
1006        if let Some(tracker_id) = existing_tracker_id {
1007            // The tracker should not be borrowed recursively in
1008            // recording mode, as we don't call `__check_auth` in this
1009            // flow.
1010            let trackers = self.try_borrow_account_trackers(host)?;
1011            let Some(trackercell) = trackers.get(tracker_id) else {
1012                return Err(host.err(
1013                    ScErrorType::Auth,
1014                    ScErrorCode::InternalError,
1015                    "bad index for existing tracker",
1016                    &[],
1017                ));
1018            };
1019            if let Ok(mut tracker) = trackercell.try_borrow_mut() {
1020                // The recording invariant is that trackers are created
1021                // with the first authorized invocation, which means
1022                // that when their stack no longer has authorized
1023                // invocation, then we've popped frames past its root
1024                // and hence need to create a new tracker.
1025                if !tracker.has_authorized_invocations_in_stack() {
1026                    recording_info
1027                        .try_borrow_tracker_by_address_handle_mut(host)?
1028                        .remove(&address_obj_handle);
1029                } else {
1030                    return tracker.record_invocation(host, function);
1031                }
1032            } else {
1033                return Err(host.err(
1034                    ScErrorType::Auth,
1035                    ScErrorCode::InternalError,
1036                    "unexpected recursive tracker borrow in recording mode",
1037                    &[],
1038                ));
1039            };
1040        }
1041        // If there is no active tracker for this exact address object,
1042        // try to find any matching active tracker for the address.
1043        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1044            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1045                if !host.compare(&tracker.address, &address)?.is_eq() {
1046                    continue;
1047                }
1048                // Take the first tracker that is still active (i.e. has
1049                // active authorizations in the current call stack) and
1050                // hasn't been used for this stack frame yet.
1051                if tracker.has_authorized_invocations_in_stack()
1052                    && !tracker.current_frame_is_already_matched()
1053                {
1054                    return tracker.record_invocation(host, function);
1055                }
1056            } else {
1057                return Err(host.err(
1058                    ScErrorType::Auth,
1059                    ScErrorCode::InternalError,
1060                    "unexpected borrowed tracker in recording auth mode",
1061                    &[],
1062                ));
1063            }
1064        }
1065        // At this stage there is no active tracker to which we could
1066        // match the current invocation, thus we need to create a new
1067        // tracker.
1068        // Alert the user in `disable_non_root_auth` mode if we're not
1069        // in the root stack frame.
1070        if recording_info.disable_non_root_auth && self.try_borrow_call_stack(host)?.len() != 1 {
1071            return Err(host.err(
1072                ScErrorType::Auth,
1073                ScErrorCode::InvalidAction,
1074                "[recording authorization only] encountered authorization not tied \
1075                to the root contract invocation for an address. Use `require_auth()` \
1076                in the top invocation or enable non-root authorization.",
1077                &[address.into()],
1078            ));
1079        }
1080        // If a tracker for the new tree doesn't exist yet, create
1081        // it and initialize with the current invocation.
1082        self.try_borrow_account_trackers_mut(host)?
1083            .push(RefCell::new(AccountAuthorizationTracker::new_recording(
1084                host,
1085                address,
1086                function,
1087                self.try_borrow_call_stack(host)?.len(),
1088            )?));
1089        recording_info
1090            .try_borrow_tracker_by_address_handle_mut(host)?
1091            .insert(
1092                address_obj_handle,
1093                self.try_borrow_account_trackers(host)?.len() - 1,
1094            );
1095        Ok(())
1096    }
1097
1098    // metering: covered
1099    fn require_auth_internal(
1100        &self,
1101        host: &Host,
1102        address: AddressObject,
1103        function: AuthorizedFunction,
1104    ) -> Result<(), HostError> {
1105        // First check the InvokerContractAuthorizationTrackers
1106        if self.maybe_check_invoker_contract_auth(host, address, &function)? {
1107            return Ok(());
1108        }
1109        // Then check the AccountAuthorizationTrackers
1110        match &self.mode {
1111            AuthorizationMode::Enforcing => self.require_auth_enforcing(host, address, &function),
1112            // metering: free for recording
1113            #[cfg(any(test, feature = "recording_mode"))]
1114            AuthorizationMode::Recording(recording_info) => {
1115                self.require_auth_recording(host, address, function, recording_info)
1116            }
1117        }
1118    }
1119
1120    // Returns a snapshot of `AuthorizationManager` to use for rollback.
1121    // metering: covered
1122    fn snapshot(&self, host: &Host) -> Result<AuthorizationManagerSnapshot, HostError> {
1123        let _span = tracy_span!("snapshot auth");
1124        let account_trackers_snapshot = match &self.mode {
1125            AuthorizationMode::Enforcing => {
1126                let len = self.try_borrow_account_trackers(host)?.len();
1127                let mut snapshots =
1128                    Vec::<Option<AccountAuthorizationTrackerSnapshot>>::with_metered_capacity(
1129                        len, host,
1130                    )?;
1131                for t in self.try_borrow_account_trackers(host)?.iter() {
1132                    let sp = if let Ok(tracker) = t.try_borrow() {
1133                        Some(tracker.snapshot(host.as_budget())?)
1134                    } else {
1135                        // If tracker is borrowed, snapshotting it is a no-op
1136                        // (it can't change until we release it higher up the
1137                        // stack).
1138                        None
1139                    };
1140                    snapshots.push(sp);
1141                }
1142                AccountTrackersSnapshot::Enforcing(snapshots)
1143            }
1144            #[cfg(any(test, feature = "recording_mode"))]
1145            AuthorizationMode::Recording(_) => {
1146                // All trackers should be available to borrow for copy as in
1147                // recording mode we can't have recursive authorization.
1148                // metering: free for recording
1149                AccountTrackersSnapshot::Recording(self.try_borrow_account_trackers(host)?.clone())
1150            }
1151        };
1152        let invoker_contract_tracker_root_snapshots = self
1153            .try_borrow_invoker_contract_trackers(host)?
1154            .iter()
1155            .map(|t| t.invocation_tracker.snapshot(host.as_budget()))
1156            .metered_collect::<Result<Vec<AuthorizedInvocationSnapshot>, HostError>>(host)??;
1157        #[cfg(any(test, feature = "recording_mode"))]
1158        let tracker_by_address_handle = match &self.mode {
1159            AuthorizationMode::Enforcing => None,
1160            AuthorizationMode::Recording(recording_info) => Some(
1161                // metering: free for recording
1162                recording_info
1163                    .try_borrow_tracker_by_address_handle(host)?
1164                    .clone(),
1165            ),
1166        };
1167        Ok(AuthorizationManagerSnapshot {
1168            account_trackers_snapshot,
1169            invoker_contract_tracker_root_snapshots,
1170            #[cfg(any(test, feature = "recording_mode"))]
1171            tracker_by_address_handle,
1172        })
1173    }
1174
1175    // Rolls back this `AuthorizationManager` to the snapshot state.
1176    // metering: covered
1177    fn rollback(
1178        &self,
1179        host: &Host,
1180        snapshot: AuthorizationManagerSnapshot,
1181    ) -> Result<(), HostError> {
1182        let _span = tracy_span!("rollback auth");
1183        match snapshot.account_trackers_snapshot {
1184            AccountTrackersSnapshot::Enforcing(trackers_snapshot) => {
1185                let trackers = self.try_borrow_account_trackers(host)?;
1186                if trackers.len() != trackers_snapshot.len() {
1187                    return Err(host.err(
1188                        ScErrorType::Auth,
1189                        ScErrorCode::InternalError,
1190                        "unexpected bad auth snapshot",
1191                        &[],
1192                    ));
1193                }
1194                for (i, tracker) in trackers.iter().enumerate() {
1195                    let Some(snapopt) = trackers_snapshot.get(i) else {
1196                        return Err(host.err(
1197                            ScErrorType::Auth,
1198                            ScErrorCode::InternalError,
1199                            "unexpected auth snapshot index",
1200                            &[],
1201                        ));
1202                    };
1203                    if let Some(tracker_snapshot) = snapopt {
1204                        tracker
1205                            .try_borrow_mut()
1206                            .map_err(|_| {
1207                                host.err(
1208                                    ScErrorType::Auth,
1209                                    ScErrorCode::InternalError,
1210                                    "unexpected bad auth borrow",
1211                                    &[],
1212                                )
1213                            })?
1214                            .rollback(&tracker_snapshot)?;
1215                    }
1216                }
1217            }
1218            #[cfg(any(test, feature = "recording_mode"))]
1219            AccountTrackersSnapshot::Recording(s) => {
1220                *self.try_borrow_account_trackers_mut(host)? = s;
1221            }
1222        }
1223
1224        let mut invoker_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1225        if invoker_trackers.len() != snapshot.invoker_contract_tracker_root_snapshots.len() {
1226            return Err(host.err(
1227                ScErrorType::Auth,
1228                ScErrorCode::InternalError,
1229                "unexpected bad auth snapshot",
1230                &[],
1231            ));
1232        }
1233        for (tracker, snapshot) in invoker_trackers
1234            .iter_mut()
1235            .zip(snapshot.invoker_contract_tracker_root_snapshots.iter())
1236        {
1237            tracker.invocation_tracker.rollback(snapshot)?;
1238        }
1239
1240        #[cfg(any(test, feature = "recording_mode"))]
1241        if let Some(tracker_by_address_handle) = snapshot.tracker_by_address_handle {
1242            match &self.mode {
1243                AuthorizationMode::Enforcing => (),
1244                AuthorizationMode::Recording(recording_info) => {
1245                    *recording_info.try_borrow_tracker_by_address_handle_mut(host)? =
1246                        tracker_by_address_handle;
1247                }
1248            }
1249        }
1250        Ok(())
1251    }
1252
1253    // metering: covered
1254    fn push_tracker_frame(&self, host: &Host) -> Result<(), HostError> {
1255        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1256            // Skip already borrowed trackers, these must be in the middle of
1257            // authentication and hence don't need stack to be updated.
1258            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1259                tracker.push_frame(host.as_budget())?;
1260            }
1261        }
1262        for tracker in self
1263            .try_borrow_invoker_contract_trackers_mut(host)?
1264            .iter_mut()
1265        {
1266            tracker.push_frame(host.as_budget())?;
1267        }
1268        Ok(())
1269    }
1270
1271    // metering: covered
1272    pub(crate) fn push_create_contract_host_fn_frame(
1273        &self,
1274        host: &Host,
1275        args: CreateContractArgsV2,
1276    ) -> Result<(), HostError> {
1277        Vec::<CreateContractArgsV2>::charge_bulk_init_cpy(1, host)?;
1278        self.try_borrow_call_stack_mut(host)?
1279            .push(AuthStackFrame::CreateContractHostFn(args));
1280        self.push_tracker_frame(host)
1281    }
1282
1283    // Records a new call stack frame and returns a snapshot for rolling
1284    // back this stack frame.
1285    // This should be called for every `Host` `push_frame`.
1286    // metering: covered
1287    pub(crate) fn push_frame(
1288        &self,
1289        host: &Host,
1290        frame: &Frame,
1291    ) -> Result<AuthorizationManagerSnapshot, HostError> {
1292        let _span = tracy_span!("push auth frame");
1293        let (contract_id, function_name) = match frame {
1294            Frame::ContractVM { vm, fn_name, .. } => {
1295                (vm.contract_id.metered_clone(host)?, *fn_name)
1296            }
1297            // Skip the top-level host function stack frames as they don't
1298            // contain all the necessary information.
1299            // Use the respective push (like
1300            // `push_create_contract_host_fn_frame`) functions instead to push
1301            // the frame with the required info.
1302            Frame::HostFunction(_) => return self.snapshot(host),
1303            Frame::StellarAssetContract(id, fn_name, ..) => (id.metered_clone(host)?, *fn_name),
1304            #[cfg(any(test, feature = "testutils"))]
1305            Frame::TestContract(tc) => (tc.id.metered_clone(host)?, tc.func),
1306        };
1307        let contract_address = host.add_host_object(ScAddress::Contract(contract_id))?;
1308        Vec::<ContractInvocation>::charge_bulk_init_cpy(1, host)?;
1309        self.try_borrow_call_stack_mut(host)?
1310            .push(AuthStackFrame::Contract(ContractInvocation {
1311                contract_address,
1312                function_name,
1313            }));
1314
1315        self.push_tracker_frame(host)?;
1316        self.snapshot(host)
1317    }
1318
1319    // Pops a call stack frame and maybe rolls back the internal
1320    // state according to the provided snapshot.
1321    // This should be called for every `Host` `pop_frame`.
1322    // metering: covered
1323    pub(crate) fn pop_frame(
1324        &self,
1325        host: &Host,
1326        snapshot: Option<AuthorizationManagerSnapshot>,
1327    ) -> Result<(), HostError> {
1328        let _span = tracy_span!("pop auth frame");
1329        // Important: rollback has to be performed before popping the frame
1330        // from the tracker. This ensures correct work of invoker contract
1331        // trackers for which snapshots are dependent on the current
1332        // call stack.
1333        if let Some(snapshot) = snapshot {
1334            self.rollback(host, snapshot)?;
1335        }
1336        {
1337            let mut call_stack = self.try_borrow_call_stack_mut(host)?;
1338            // Currently we don't push host function call frames, hence this may be
1339            // called with empty stack. We trust the Host to keep things correct,
1340            // i.e. that only host function frames are ignored this way.
1341            if call_stack.is_empty() {
1342                return Ok(());
1343            }
1344            call_stack.pop();
1345        }
1346        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1347            // Skip already borrowed trackers, these must be in the middle of
1348            // authentication and hence don't need stack to be updated.
1349            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1350                tracker.pop_frame();
1351            }
1352        }
1353
1354        let mut invoker_contract_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1355        for tracker in invoker_contract_trackers.iter_mut() {
1356            tracker.pop_frame();
1357        }
1358        // Pop invoker contract trackers that went out of scope. The invariant
1359        // is that tracker only exists for the next sub-contract call (or until
1360        // the tracker's frame itself is popped). Thus trackers form a stack
1361        // where the shorter lifetime trackers are at the top.
1362        while let Some(last) = invoker_contract_trackers.last() {
1363            // *Subtle*: there are two possible scenarios when tracker is considered
1364            // to be out of scope:
1365            // - When the sub-contract call is finished. For example, contract A creates
1366            // a tracker, then calls contract B. When we pop the frame of contract B,
1367            // the tracker's stack will be empty and thus considered to be out of scope.
1368            // - When the contract that created the tracker is going out of scope without
1369            // calling any sub-contracts. For example, contract A creates a tracker and
1370            // returns. When we pop the frame of contract A, the tracker's stack will be
1371            // empty (because it's created empty), and thus considered to be out of scope.
1372            // The invariant above is maintained in both scenarios.
1373            if last.is_out_of_scope() {
1374                invoker_contract_trackers.pop();
1375            } else {
1376                break;
1377            }
1378        }
1379        Ok(())
1380    }
1381
1382    // Returns the recorded per-address authorization payloads that would cover the
1383    // top-level contract function invocation in the enforcing mode.
1384    // Should only be called in the recording mode.
1385    // metering: free, recording mode
1386    #[cfg(any(test, feature = "recording_mode"))]
1387    pub(crate) fn get_recorded_auth_payloads(
1388        &self,
1389        host: &Host,
1390    ) -> Result<Vec<RecordedAuthPayload>, HostError> {
1391        match &self.mode {
1392            AuthorizationMode::Enforcing => Err(HostError::from((
1393                ScErrorType::Auth,
1394                ScErrorCode::InternalError,
1395            ))),
1396            AuthorizationMode::Recording(_) => Ok(self
1397                .try_borrow_account_trackers(host)?
1398                .iter()
1399                .map(|tracker| tracker.try_borrow_or_err()?.get_recorded_auth_payload(host))
1400                .collect::<Result<Vec<RecordedAuthPayload>, HostError>>()?),
1401        }
1402    }
1403
1404    // For recording mode, emulates authentication that would normally happen in
1405    // the enforcing mode.
1406    // This helps to build a more realistic footprint and produce more correct
1407    // meterting data for the recording mode.
1408    // No-op in the enforcing mode.
1409    // metering: covered
1410    #[cfg(any(test, feature = "recording_mode"))]
1411    pub(crate) fn maybe_emulate_authentication(&self, host: &Host) -> Result<(), HostError> {
1412        match &self.mode {
1413            AuthorizationMode::Enforcing => Ok(()),
1414            AuthorizationMode::Recording(_) => {
1415                for tracker in self.try_borrow_account_trackers(host)?.iter() {
1416                    tracker
1417                        .try_borrow_mut_or_err()?
1418                        .emulate_authentication(host)?;
1419                }
1420                Ok(())
1421            }
1422        }
1423    }
1424
1425    // Returns a 'reset' instance of `AuthorizationManager` that has the same
1426    // mode, but no data.
1427    // metering: free, testutils
1428    #[cfg(any(test, feature = "testutils"))]
1429    pub(crate) fn reset(&mut self) {
1430        *self = match &self.mode {
1431            AuthorizationMode::Enforcing => {
1432                AuthorizationManager::new_enforcing_without_authorizations()
1433            }
1434            AuthorizationMode::Recording(rec_info) => {
1435                AuthorizationManager::new_recording(rec_info.disable_non_root_auth)
1436            }
1437        }
1438    }
1439
1440    // Returns all authorizations that have been authenticated for the
1441    // last contract invocation.
1442    // metering: free, testutils
1443    #[cfg(any(test, feature = "testutils"))]
1444    pub(crate) fn get_authenticated_authorizations(
1445        &self,
1446        host: &Host,
1447    ) -> Vec<(ScAddress, xdr::SorobanAuthorizedInvocation)> {
1448        host.as_budget()
1449            .with_observable_shadow_mode(|| {
1450                self.account_trackers
1451                    .borrow()
1452                    .iter()
1453                    .filter(|t| t.borrow().verified)
1454                    .map(|t| {
1455                        (
1456                            host.scaddress_from_address(t.borrow().address).unwrap(),
1457                            t.borrow()
1458                                .invocation_tracker
1459                                .root_authorized_invocation
1460                                .to_xdr(host, true)
1461                                .unwrap(),
1462                        )
1463                    })
1464                    .metered_collect(host)
1465            })
1466            .unwrap()
1467    }
1468}
1469
1470// Some helper extensions to support test-observation.
1471#[allow(dead_code)]
1472impl AuthorizationManager {
1473    pub(crate) fn stack_size(&self) -> usize {
1474        if let Ok(call_stack) = self.call_stack.try_borrow() {
1475            call_stack.len()
1476        } else {
1477            0
1478        }
1479    }
1480
1481    pub(crate) fn stack_hash(&self, budget: &Budget) -> Result<u64, HostError> {
1482        use std::hash::Hasher;
1483        if let Ok(call_stack) = self.call_stack.try_borrow() {
1484            let mut state = CountingHasher::default();
1485            call_stack.metered_hash(&mut state, budget)?;
1486            Ok(state.finish())
1487        } else {
1488            Ok(0)
1489        }
1490    }
1491
1492    pub(crate) fn trackers_hash_and_size(
1493        &self,
1494        budget: &Budget,
1495    ) -> Result<(u64, usize), HostError> {
1496        use std::hash::Hasher;
1497        let mut size: usize = 0;
1498        let mut state = CountingHasher::default();
1499        self.mode.metered_hash(&mut state, budget)?;
1500        if let Ok(account_trackers) = self.account_trackers.try_borrow() {
1501            for tracker in account_trackers.iter() {
1502                if let Ok(tracker) = tracker.try_borrow() {
1503                    size = size.saturating_add(1);
1504                    tracker.metered_hash(&mut state, budget)?;
1505                }
1506            }
1507        }
1508        if let Ok(invoker_contract_trackers) = self.invoker_contract_trackers.try_borrow() {
1509            for tracker in invoker_contract_trackers.iter() {
1510                size = size.saturating_add(1);
1511                tracker.metered_hash(&mut state, budget)?;
1512            }
1513        }
1514        Ok((state.finish(), size))
1515    }
1516}
1517
1518impl InvocationTracker {
1519    // metering: covered by components
1520    fn from_xdr(
1521        host: &Host,
1522        root_invocation: xdr::SorobanAuthorizedInvocation,
1523    ) -> Result<Self, HostError> {
1524        Ok(Self {
1525            root_authorized_invocation: AuthorizedInvocation::from_xdr(host, root_invocation)?,
1526            match_stack: vec![],
1527            root_exhausted_frame: None,
1528            is_fully_processed: false,
1529        })
1530    }
1531
1532    // metering: free
1533    fn new(root_authorized_invocation: AuthorizedInvocation) -> Self {
1534        Self {
1535            root_authorized_invocation,
1536            match_stack: vec![],
1537            root_exhausted_frame: None,
1538            is_fully_processed: false,
1539        }
1540    }
1541
1542    // metering: free for recording
1543    #[cfg(any(test, feature = "recording_mode"))]
1544    fn new_recording(function: AuthorizedFunction, current_stack_len: usize) -> Self {
1545        // Create the stack of `MatchState::Unmatched` leading to the current invocation to
1546        // represent invocations that didn't need authorization on behalf of
1547        // the tracked address.
1548        let mut match_stack = vec![MatchState::Unmatched; current_stack_len - 1];
1549        // Add a MatchState for the current(root) invocation.
1550        match_stack.push(MatchState::RootMatch);
1551        let root_exhausted_frame = Some(match_stack.len() - 1);
1552        Self {
1553            root_authorized_invocation: AuthorizedInvocation::new_recording(function),
1554            match_stack,
1555            root_exhausted_frame,
1556            is_fully_processed: false,
1557        }
1558    }
1559
1560    // Walks a path in the tree defined by `match_stack` and
1561    // returns the last visited authorized node.
1562    // metering: free
1563    fn last_authorized_invocation_mut(
1564        &mut self,
1565    ) -> Result<Option<&mut AuthorizedInvocation>, HostError> {
1566        for (i, m) in self.match_stack.iter().enumerate() {
1567            match m {
1568                MatchState::RootMatch => {
1569                    return Ok(Some(
1570                        self.root_authorized_invocation
1571                            .last_authorized_invocation_mut(&self.match_stack, i + 1)?,
1572                    ));
1573                }
1574                MatchState::SubMatch { .. } => {
1575                    return Err((ScErrorType::Auth, ScErrorCode::InternalError).into())
1576                }
1577                MatchState::Unmatched => (),
1578            }
1579        }
1580        Ok(None)
1581    }
1582
1583    // metering: covered
1584    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1585        Vec::<usize>::charge_bulk_init_cpy(1, budget)?;
1586        self.match_stack.push(MatchState::Unmatched);
1587        Ok(())
1588    }
1589
1590    // metering: free
1591    fn pop_frame(&mut self) {
1592        self.match_stack.pop();
1593        if let Some(root_exhausted_frame) = self.root_exhausted_frame {
1594            if root_exhausted_frame >= self.match_stack.len() {
1595                self.is_fully_processed = true;
1596            }
1597        }
1598    }
1599
1600    // metering: free
1601    fn is_empty(&self) -> bool {
1602        self.match_stack.is_empty()
1603    }
1604
1605    // metering: free
1606    fn is_active(&self) -> bool {
1607        self.root_authorized_invocation.is_exhausted && !self.is_fully_processed
1608    }
1609
1610    // metering: free
1611    fn current_frame_is_already_matched(&self) -> bool {
1612        match self.match_stack.last() {
1613            Some(x) => x.is_matched(),
1614            _ => false,
1615        }
1616    }
1617
1618    // Tries to match the provided invocation as an extension of the last
1619    // currently-matched sub-invocation of the authorized invocation tree (or
1620    // the root, if there is no matched invocation yet). If matching succeeds,
1621    // it writes the match to the corresponding entry in
1622    // [`InvocationTracker::match_stack`].
1623    //
1624    // Returns `true` if the match has been found for the first time per current
1625    // frame.
1626    //
1627    // Metering: covered by components
1628    fn maybe_extend_invocation_match(
1629        &mut self,
1630        host: &Host,
1631        function: &AuthorizedFunction,
1632        allow_matching_root: bool,
1633    ) -> Result<bool, HostError> {
1634        if self.current_frame_is_already_matched() {
1635            return Ok(false);
1636        }
1637        let mut new_match_state = MatchState::Unmatched;
1638        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1639            for (index_in_parent, sub_invocation) in
1640                curr_invocation.sub_invocations.iter_mut().enumerate()
1641            {
1642                if !sub_invocation.is_exhausted
1643                    && host.compare(&sub_invocation.function, function)?.is_eq()
1644                {
1645                    new_match_state = MatchState::SubMatch { index_in_parent };
1646                    sub_invocation.is_exhausted = true;
1647                    break;
1648                }
1649            }
1650        } else if !self.root_authorized_invocation.is_exhausted
1651            && allow_matching_root
1652            && host
1653                .compare(&self.root_authorized_invocation.function, &function)?
1654                .is_eq()
1655        {
1656            new_match_state = MatchState::RootMatch;
1657            self.root_authorized_invocation.is_exhausted = true;
1658            self.root_exhausted_frame = Some(self.match_stack.len() - 1);
1659        }
1660        if new_match_state.is_matched() {
1661            *self.match_stack.last_mut().ok_or_else(|| {
1662                host.err(
1663                    ScErrorType::Auth,
1664                    ScErrorCode::InternalError,
1665                    "invalid match_stack",
1666                    &[],
1667                )
1668            })? = new_match_state;
1669        }
1670        Ok(new_match_state.is_matched())
1671    }
1672
1673    // Records the invocation in this tracker.
1674    // This is needed for the recording mode only.
1675    // This assumes that the address matching is correctly performed before
1676    // calling this.
1677    // metering: free for recording
1678    #[cfg(any(test, feature = "recording_mode"))]
1679    fn record_invocation(
1680        &mut self,
1681        host: &Host,
1682        function: AuthorizedFunction,
1683    ) -> Result<(), HostError> {
1684        if self.current_frame_is_already_matched() {
1685            return Err(host.err(
1686                ScErrorType::Auth,
1687                ScErrorCode::ExistingValue,
1688                "frame is already authorized",
1689                &[],
1690            ));
1691        }
1692        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1693            curr_invocation
1694                .sub_invocations
1695                .push(AuthorizedInvocation::new_recording(function));
1696            let index_in_parent = curr_invocation.sub_invocations.len() - 1;
1697            *self.match_stack.last_mut().unwrap() = MatchState::SubMatch { index_in_parent };
1698        } else {
1699            // This would be a bug
1700            return Err(host.err(
1701                ScErrorType::Auth,
1702                ScErrorCode::InternalError,
1703                "unexpected missing authorized invocation",
1704                &[],
1705            ));
1706        }
1707        Ok(())
1708    }
1709
1710    // metering: free
1711    #[cfg(any(test, feature = "recording_mode"))]
1712    fn has_matched_invocations_in_stack(&self) -> bool {
1713        self.match_stack.iter().any(|i| i.is_matched())
1714    }
1715
1716    // metering: covered
1717    fn snapshot(&self, budget: &Budget) -> Result<AuthorizedInvocationSnapshot, HostError> {
1718        self.root_authorized_invocation.snapshot(budget)
1719    }
1720
1721    // metering: covered
1722    fn rollback(&mut self, snapshot: &AuthorizedInvocationSnapshot) -> Result<(), HostError> {
1723        self.root_authorized_invocation.rollback(snapshot)?;
1724        // Invocation can only be rolled back from 'exhausted' to
1725        // 'non-exhausted' state (as there is no other way to go from
1726        // 'exhausted' state back to 'non-exhausted' state).
1727        if !self.root_authorized_invocation.is_exhausted {
1728            self.root_exhausted_frame = None;
1729            self.is_fully_processed = false;
1730        }
1731        Ok(())
1732    }
1733}
1734
1735impl AccountAuthorizationTracker {
1736    // Metering: covered by the host and components
1737    fn from_authorization_entry(
1738        host: &Host,
1739        auth_entry: SorobanAuthorizationEntry,
1740    ) -> Result<Self, HostError> {
1741        let (address, nonce, signature, is_transaction_source_account) =
1742            match auth_entry.credentials {
1743                SorobanCredentials::SourceAccount => (
1744                    host.source_account_address()?.ok_or_else(|| {
1745                        host.err(
1746                            ScErrorType::Auth,
1747                            ScErrorCode::InternalError,
1748                            "source account is missing when setting auth entries",
1749                            &[],
1750                        )
1751                    })?,
1752                    None,
1753                    Val::VOID.into(),
1754                    true,
1755                ),
1756                SorobanCredentials::Address(address_creds) => (
1757                    host.add_host_object(address_creds.address)?,
1758                    Some((
1759                        address_creds.nonce,
1760                        address_creds.signature_expiration_ledger,
1761                    )),
1762                    host.to_host_val(&address_creds.signature)?,
1763                    false,
1764                ),
1765            };
1766        Ok(Self {
1767            address,
1768            invocation_tracker: InvocationTracker::from_xdr(host, auth_entry.root_invocation)?,
1769            signature,
1770            verified: false,
1771            is_transaction_source_account,
1772            nonce,
1773        })
1774    }
1775
1776    // metering: free, since this is recording mode only
1777    #[cfg(any(test, feature = "recording_mode"))]
1778    fn new_recording(
1779        host: &Host,
1780        address: AddressObject,
1781        function: AuthorizedFunction,
1782        current_stack_len: usize,
1783    ) -> Result<Self, HostError> {
1784        if current_stack_len == 0 {
1785            // This would be a bug.
1786            return Err(host.err(
1787                ScErrorType::Auth,
1788                ScErrorCode::InternalError,
1789                "unexpected empty stack in recording auth",
1790                &[],
1791            ));
1792        }
1793        // Decide if we're tracking the transaction source account, and if so
1794        // don't bother with a nonce.
1795        let is_transaction_source_account =
1796            if let Some(source_acc) = host.source_account_address()? {
1797                host.compare(&source_acc, &address)?.is_eq()
1798            } else {
1799                false
1800            };
1801        let nonce = if !is_transaction_source_account {
1802            let random_nonce: i64 =
1803                host.with_recording_auth_nonce_prng(|p| Ok(p.gen_range(0..=i64::MAX)))?;
1804            // We use the `max_live_until_ledger` as the nonce lifetime here
1805            // in order to account for a maximum possible rent fee (given maximum
1806            // possible signature expiration). However, we don't want to actually
1807            // store that as nonce expiration ledger in the recording tracker,
1808            // as users are able (and encouraged) to customize the signature
1809            // expiration after simulation and before signing the auth payload.
1810            host.consume_nonce(address, random_nonce, host.max_live_until_ledger()?)?;
1811            Some((random_nonce, 0))
1812        } else {
1813            None
1814        };
1815        Ok(Self {
1816            address,
1817            invocation_tracker: InvocationTracker::new_recording(function, current_stack_len),
1818            signature: Val::VOID.into(),
1819            verified: true,
1820            is_transaction_source_account,
1821            nonce,
1822        })
1823    }
1824
1825    // Tries to find and enforce the provided invocation with this tracker and
1826    // lazily performs authentication when needed.
1827    // This is needed for the enforcing mode only.
1828    // This assumes that the address matching is correctly performed before
1829    // calling this.
1830    // Returns true/false based on whether the invocation is found in the
1831    // tracker. Returns error if invocation has been found, but the tracker
1832    // itself is not valid (failed authentication or nonce check).
1833    // metering: covered
1834    fn maybe_authorize_invocation(
1835        &mut self,
1836        host: &Host,
1837        function: &AuthorizedFunction,
1838        allow_matching_root: bool,
1839    ) -> Result<bool, HostError> {
1840        if !self.invocation_tracker.maybe_extend_invocation_match(
1841            host,
1842            function,
1843            allow_matching_root,
1844        )? {
1845            // The call isn't found in the currently tracked tree or is already
1846            // authorized in it.
1847            // That doesn't necessarily mean it's unauthorized (it can be
1848            // authorized in a different tracker).
1849            return Ok(false);
1850        }
1851        if !self.verified {
1852            let authenticate_res = self
1853                .authenticate(host)
1854                .map_err(|err| {
1855                    // Convert any recoverable errors to auth errors so that it's
1856                    // not possible to confuse them for the errors of the
1857                    // contract that has called `require_auth`.
1858                    // While there is no 'recovery' here, non-recoverable errors
1859                    // aren't really useful for decoration.
1860                    if err.is_recoverable() {
1861                        // Also log the original error for diagnostics.
1862                        host.err(
1863                            ScErrorType::Auth,
1864                            ScErrorCode::InvalidAction,
1865                            "failed account authentication with error",
1866                            &[self.address.into(), err.error.to_val()],
1867                        )
1868                    } else {
1869                        err
1870                    }
1871                })
1872                .and_then(|_| self.verify_and_consume_nonce(host));
1873            if let Some(err) = authenticate_res.err() {
1874                return Err(err);
1875            }
1876            self.verified = true;
1877        }
1878        Ok(true)
1879    }
1880
1881    // Records the invocation in this tracker.
1882    // This is needed for the recording mode only.
1883    // This assumes that the address matching is correctly performed before
1884    // calling this.
1885    // metering: free for recording
1886    #[cfg(any(test, feature = "recording_mode"))]
1887    fn record_invocation(
1888        &mut self,
1889        host: &Host,
1890        function: AuthorizedFunction,
1891    ) -> Result<(), HostError> {
1892        self.invocation_tracker.record_invocation(host, function)
1893    }
1894
1895    // Build the authorization payload from the invocations recorded in this
1896    // tracker.
1897    // metering: free for recording
1898    #[cfg(any(test, feature = "recording_mode"))]
1899    fn get_recorded_auth_payload(&self, host: &Host) -> Result<RecordedAuthPayload, HostError> {
1900        host.as_budget().with_observable_shadow_mode(|| {
1901            Ok(RecordedAuthPayload {
1902                address: if !self.is_transaction_source_account {
1903                    Some(host.visit_obj(self.address, |a: &ScAddress| a.metered_clone(host))?)
1904                } else {
1905                    None
1906                },
1907                invocation: self
1908                    .invocation_tracker
1909                    .root_authorized_invocation
1910                    .to_xdr(host, false)?,
1911                nonce: self.nonce.map(|(nonce, _)| nonce),
1912            })
1913        })
1914    }
1915
1916    // Checks if there is at least one authorized invocation in the current call
1917    // stack.
1918    // metering: free
1919    #[cfg(any(test, feature = "recording_mode"))]
1920    fn has_authorized_invocations_in_stack(&self) -> bool {
1921        self.invocation_tracker.has_matched_invocations_in_stack()
1922    }
1923
1924    // metering: covered
1925    fn root_invocation_to_xdr(
1926        &self,
1927        host: &Host,
1928    ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
1929        self.invocation_tracker
1930            .root_authorized_invocation
1931            .to_xdr(host, false)
1932    }
1933
1934    // metering: covered
1935    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1936        self.invocation_tracker.push_frame(budget)
1937    }
1938
1939    // metering: covered
1940    fn pop_frame(&mut self) {
1941        self.invocation_tracker.pop_frame();
1942    }
1943
1944    // metering: covered
1945    fn verify_and_consume_nonce(&mut self, host: &Host) -> Result<(), HostError> {
1946        if self.is_transaction_source_account {
1947            return Ok(());
1948        }
1949        if let Some((nonce, live_until_ledger)) = &self.nonce {
1950            let ledger_seq = host.with_ledger_info(|li| Ok(li.sequence_number))?;
1951            if ledger_seq > *live_until_ledger {
1952                return Err(host.err(
1953                    ScErrorType::Auth,
1954                    ScErrorCode::InvalidInput,
1955                    "signature has expired",
1956                    &[
1957                        self.address.into(),
1958                        ledger_seq.try_into_val(host)?,
1959                        live_until_ledger.try_into_val(host)?,
1960                    ],
1961                ));
1962            }
1963            let max_live_until_ledger = host.max_live_until_ledger()?;
1964            if *live_until_ledger > max_live_until_ledger {
1965                return Err(host.err(
1966                    ScErrorType::Auth,
1967                    ScErrorCode::InvalidInput,
1968                    "signature expiration is too late",
1969                    &[
1970                        self.address.into(),
1971                        max_live_until_ledger.try_into_val(host)?,
1972                        live_until_ledger.try_into_val(host)?,
1973                    ],
1974                ));
1975            }
1976
1977            return host.consume_nonce(self.address, *nonce, *live_until_ledger);
1978        }
1979        Err(host.err(
1980            ScErrorType::Auth,
1981            ScErrorCode::InternalError,
1982            "unexpected nonce verification state",
1983            &[],
1984        ))
1985    }
1986
1987    // Computes the payload that has to be signed in order to authenticate
1988    // the authorized invocation tree corresponding to this tracker.
1989    // metering: covered by components
1990    fn get_signature_payload(&self, host: &Host) -> Result<[u8; 32], HostError> {
1991        let (nonce, live_until_ledger) = self.nonce.ok_or_else(|| {
1992            host.err(
1993                ScErrorType::Auth,
1994                ScErrorCode::InternalError,
1995                "unexpected missing nonce",
1996                &[],
1997            )
1998        })?;
1999        let payload_preimage =
2000            HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization {
2001                network_id: Hash(host.with_ledger_info(|li| li.network_id.metered_clone(host))?),
2002                nonce,
2003                signature_expiration_ledger: live_until_ledger,
2004                invocation: self.root_invocation_to_xdr(host)?,
2005            });
2006
2007        host.metered_hash_xdr(&payload_preimage)
2008    }
2009
2010    // metering: covered by the hsot
2011    fn authenticate(&self, host: &Host) -> Result<(), HostError> {
2012        if self.is_transaction_source_account {
2013            return Ok(());
2014        }
2015
2016        let sc_addr = host.scaddress_from_address(self.address)?;
2017        // TODO: there should also be a mode where a dummy payload is used
2018        // instead (for enforcing mode preflight).
2019        let payload = self.get_signature_payload(host)?;
2020        match sc_addr {
2021            ScAddress::Account(acc) => {
2022                check_account_authentication(host, acc, &payload, self.signature)?;
2023            }
2024            ScAddress::Contract(acc_contract) => {
2025                check_account_contract_auth(
2026                    host,
2027                    &acc_contract,
2028                    &payload,
2029                    self.signature,
2030                    &self.invocation_tracker.root_authorized_invocation,
2031                )?;
2032            }
2033        }
2034        Ok(())
2035    }
2036
2037    // Emulates authentication for the recording mode.
2038    // metering: covered
2039    #[cfg(any(test, feature = "recording_mode"))]
2040    fn emulate_authentication(&mut self, host: &Host) -> Result<(), HostError> {
2041        if self.is_transaction_source_account {
2042            return Ok(());
2043        }
2044        let sc_addr = host.scaddress_from_address(self.address)?;
2045        match sc_addr {
2046            ScAddress::Account(acc) => {
2047                // Emulate verification of a single signature that belongs to this
2048                // account.
2049                // We could emulate more (up to 20) signature verifications, but
2050                // since signature verification is a pretty expensive operation, while
2051                // multisig in combination with Soroban auth is probably pretty rare,
2052                // multisig users should either use enforcing auth simulation, or
2053                // intentionally increase the instruction count on the recording result.
2054                let key_bytes = match &acc.0 {
2055                    PublicKey::PublicKeyTypeEd25519(k) => k.0,
2056                };
2057                let signature = AccountEd25519Signature {
2058                    public_key: BytesN::from_slice(host, &key_bytes)?,
2059                    signature: BytesN::from_slice(host, &[0_u8; 64])?,
2060                };
2061                let signatures = host_vec![host, signature]?;
2062                self.signature = signatures.into();
2063                // Authentication is expected to fail here after signature verification,
2064                // so we suppress the error and diagnostics.
2065                host.with_suppressed_diagnostic_events(|| {
2066                    let _ = self.authenticate(host);
2067                    Ok(())
2068                })?;
2069
2070                // Emulate a clone of the account, which serves 2 purposes:
2071                // - Account for metered clone in `get_signer_weight_from_account`
2072                // - Return budget error in case if it was suppressed above.
2073                let _ = acc.metered_clone(host.as_budget())?;
2074            }
2075            // We only know for sure that the contract instance and Wasm will be
2076            // loaded.
2077            ScAddress::Contract(contract_id) => {
2078                let instance_key = host.contract_instance_ledger_key(&contract_id)?;
2079                let entry = host
2080                    .try_borrow_storage_mut()?
2081                    .try_get(&instance_key, host, None)?;
2082                // In test scenarios we often may not have any actual instance, which is fine most
2083                // of the time, so we don't return any errors.
2084                // In simulation scenarios the instance will likely be there, and when it's
2085                // not, we still make our best effort and include at least the necessary instance key
2086                // into the footprint.
2087                let instance = if let Some(entry) = entry {
2088                    match &entry.data {
2089                        LedgerEntryData::ContractData(e) => match &e.val {
2090                            ScVal::ContractInstance(instance) => instance.metered_clone(host)?,
2091                            _ => {
2092                                return Ok(());
2093                            }
2094                        },
2095                        _ => {
2096                            return Ok(());
2097                        }
2098                    }
2099                } else {
2100                    return Ok(());
2101                };
2102
2103                match &instance.executable {
2104                    ContractExecutable::Wasm(wasm_hash) => {
2105                        let wasm_key = host.contract_code_ledger_key(wasm_hash)?;
2106                        let _ = host
2107                            .try_borrow_storage_mut()?
2108                            .try_get(&wasm_key, host, None)?;
2109                    }
2110                    ContractExecutable::StellarAsset => (),
2111                }
2112            }
2113        }
2114        Ok(())
2115    }
2116
2117    // metering: covered
2118    fn snapshot(&self, budget: &Budget) -> Result<AccountAuthorizationTrackerSnapshot, HostError> {
2119        Ok(AccountAuthorizationTrackerSnapshot {
2120            invocation_tracker_root_snapshot: self.invocation_tracker.snapshot(budget)?,
2121            // The verification status can be rolled back in case
2122            // of a contract failure. In case if the root call has
2123            // failed, the nonce will get 'un-consumed' due to storage
2124            // rollback. Thus we need to run verification and consume
2125            // it again in case if the call is retried. Another subtle
2126            // case where this behavior is important is the case when
2127            // custom account's authentication function depends on
2128            // some ledger state which might get modified in-between calls.
2129            verified: self.verified,
2130        })
2131    }
2132
2133    // metering: covered
2134    fn rollback(
2135        &mut self,
2136        snapshot: &AccountAuthorizationTrackerSnapshot,
2137    ) -> Result<(), HostError> {
2138        self.invocation_tracker
2139            .rollback(&snapshot.invocation_tracker_root_snapshot)?;
2140        self.verified = snapshot.verified;
2141        Ok(())
2142    }
2143
2144    // metering: free
2145    fn is_active(&self) -> bool {
2146        self.invocation_tracker.is_active()
2147    }
2148
2149    // metering: free
2150    fn current_frame_is_already_matched(&self) -> bool {
2151        self.invocation_tracker.current_frame_is_already_matched()
2152    }
2153}
2154
2155impl InvokerContractAuthorizationTracker {
2156    // metering: covered by components
2157    fn new_with_curr_contract_as_invoker(
2158        host: &Host,
2159        invoker_auth_entry: Val,
2160    ) -> Result<Self, HostError> {
2161        let invoker_sc_addr = ScAddress::Contract(host.get_current_contract_id_internal()?);
2162        let authorized_invocation = invoker_contract_auth_to_authorized_invocation(
2163            host,
2164            &invoker_sc_addr,
2165            invoker_auth_entry,
2166        )?;
2167        let invocation_tracker = InvocationTracker::new(authorized_invocation);
2168        Ok(Self {
2169            contract_address: host.add_host_object(invoker_sc_addr)?,
2170            invocation_tracker,
2171        })
2172    }
2173
2174    // metering: covered
2175    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
2176        self.invocation_tracker.push_frame(budget)
2177    }
2178
2179    // metering: covered
2180    fn pop_frame(&mut self) {
2181        self.invocation_tracker.pop_frame();
2182    }
2183
2184    // metering: free
2185    fn is_out_of_scope(&self) -> bool {
2186        self.invocation_tracker.is_empty()
2187    }
2188
2189    // metering: covered
2190    fn maybe_authorize_invocation(
2191        &mut self,
2192        host: &Host,
2193        function: &AuthorizedFunction,
2194    ) -> Result<bool, HostError> {
2195        // Authorization is successful if function is just matched by the
2196        // tracker. No authentication is needed.
2197        self.invocation_tracker
2198            .maybe_extend_invocation_match(host, function, true)
2199    }
2200}
2201
2202impl Host {
2203    // metering: covered by components
2204    fn consume_nonce(
2205        &self,
2206        address: AddressObject,
2207        nonce: i64,
2208        live_until_ledger: u32,
2209    ) -> Result<(), HostError> {
2210        let nonce_key_scval = ScVal::LedgerKeyNonce(ScNonceKey { nonce });
2211        let sc_address = self.scaddress_from_address(address)?;
2212        let nonce_key = self.storage_key_for_address(
2213            sc_address.metered_clone(self)?,
2214            nonce_key_scval.metered_clone(self)?,
2215            xdr::ContractDataDurability::Temporary,
2216        )?;
2217        let live_until_ledger = live_until_ledger
2218            .max(self.get_min_live_until_ledger(xdr::ContractDataDurability::Temporary)?);
2219        self.with_mut_storage(|storage| {
2220            if storage
2221                .has_with_host(&nonce_key, self, None)
2222                .map_err(|err| {
2223                    if err.error.is_type(ScErrorType::Storage)
2224                        && err.error.is_code(ScErrorCode::ExceededLimit)
2225                    {
2226                        return self.err(
2227                            ScErrorType::Storage,
2228                            ScErrorCode::ExceededLimit,
2229                            "trying to access nonce outside of footprint for address",
2230                            &[address.to_val()],
2231                        );
2232                    }
2233                    err
2234                })?
2235            {
2236                return Err(self.err(
2237                    ScErrorType::Auth,
2238                    ScErrorCode::ExistingValue,
2239                    "nonce already exists for address",
2240                    &[address.into()],
2241                ));
2242            }
2243            let data = LedgerEntryData::ContractData(ContractDataEntry {
2244                contract: sc_address,
2245                key: nonce_key_scval,
2246                val: ScVal::Void,
2247                durability: xdr::ContractDataDurability::Temporary,
2248                ext: xdr::ExtensionPoint::V0,
2249            });
2250            let entry = LedgerEntry {
2251                last_modified_ledger_seq: 0,
2252                data,
2253                ext: LedgerEntryExt::V0,
2254            };
2255            storage.put_with_host(
2256                &nonce_key,
2257                &Rc::metered_new(entry, self)?,
2258                Some(live_until_ledger),
2259                self,
2260                None,
2261            )
2262        })
2263    }
2264
2265    // Returns the recorded per-address authorization payloads that would cover the
2266    // top-level contract function invocation in the enforcing mode.
2267    // This should only be called in the recording authorization mode, i.e. only
2268    // if `switch_to_recording_auth` has been called.
2269    #[cfg(any(test, feature = "recording_mode"))]
2270    pub fn get_recorded_auth_payloads(&self) -> Result<Vec<RecordedAuthPayload>, HostError> {
2271        #[cfg(not(any(test, feature = "testutils")))]
2272        {
2273            self.try_borrow_authorization_manager()?
2274                .get_recorded_auth_payloads(self)
2275        }
2276        #[cfg(any(test, feature = "testutils"))]
2277        {
2278            let payloads = self
2279                .try_borrow_previous_authorization_manager()?
2280                .as_ref()
2281                .ok_or_else(|| {
2282                    self.err(
2283                        ScErrorType::Auth,
2284                        ScErrorCode::InvalidAction,
2285                        "previous invocation is missing - no auth data to get",
2286                        &[],
2287                    )
2288                })?
2289                .get_recorded_auth_payloads(self)?;
2290            Ok(payloads)
2291        }
2292    }
2293}
2294
2295#[cfg(any(test, feature = "testutils"))]
2296use crate::{host::frame::CallParams, xdr::SorobanAuthorizedInvocation};
2297
2298#[cfg(any(test, feature = "testutils"))]
2299impl Host {
2300    /// Invokes the reserved `__check_auth` function on a provided contract.
2301    ///
2302    /// This is useful for testing the custom account contracts. Otherwise, the
2303    /// host prohibits calling `__check_auth` outside of internal implementation
2304    /// of `require_auth[_for_args]` calls.
2305    pub fn call_account_contract_check_auth(
2306        &self,
2307        contract: AddressObject,
2308        args: VecObject,
2309    ) -> Result<Val, HostError> {
2310        let _invocation_meter_scope = self.maybe_meter_invocation()?;
2311
2312        use crate::builtin_contracts::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME;
2313        let contract_id = self.contract_id_from_address(contract)?;
2314        let args_vec = self.call_args_from_obj(args)?;
2315        let res = self.call_n_internal(
2316            &contract_id,
2317            ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(self)?,
2318            args_vec.as_slice(),
2319            CallParams::default_internal_call(),
2320        );
2321        if let Err(e) = &res {
2322            self.error(
2323                e.error,
2324                "check auth invocation for a custom account contract failed",
2325                &[contract.to_val(), args.to_val()],
2326            );
2327        }
2328        res
2329    }
2330
2331    /// Returns the current state of the authorization manager.
2332    ///
2333    /// Use this in conjunction with `set_auth_manager` to do authorized
2334    /// operations without breaking the current authorization state (useful for
2335    /// preserving the auth state while doing the generic test setup).
2336    pub fn snapshot_auth_manager(&self) -> Result<AuthorizationManager, HostError> {
2337        Ok(self.try_borrow_authorization_manager()?.clone())
2338    }
2339
2340    /// Switches host to the recording authorization mode and inherits the
2341    /// recording mode settings from the provided authorization manager settings
2342    /// in case if it used the recording mode.
2343    ///
2344    /// This is similar to `switch_to_recording_auth`, but should be preferred
2345    /// to use in conjunction with `snapshot_auth_manager`, such that the
2346    /// recording mode settings are not overridden.
2347    pub fn switch_to_recording_auth_inherited_from_snapshot(
2348        &self,
2349        auth_manager_snapshot: &AuthorizationManager,
2350    ) -> Result<(), HostError> {
2351        let disable_non_root_auth = match &auth_manager_snapshot.mode {
2352            AuthorizationMode::Enforcing => true,
2353            AuthorizationMode::Recording(recording_auth_info) => {
2354                recording_auth_info.disable_non_root_auth
2355            }
2356        };
2357        *self.try_borrow_authorization_manager_mut()? =
2358            AuthorizationManager::new_recording(disable_non_root_auth);
2359        Ok(())
2360    }
2361
2362    /// Replaces authorization manager with the provided new instance.
2363    ///
2364    /// Use this in conjunction with `snapshot_auth_manager` to do authorized
2365    /// operations without breaking the current authorization state (useful for
2366    /// preserving the auth state while doing the generic test setup).
2367    pub fn set_auth_manager(&self, auth_manager: AuthorizationManager) -> Result<(), HostError> {
2368        *self.try_borrow_authorization_manager_mut()? = auth_manager;
2369        Ok(())
2370    }
2371
2372    // Returns the authorizations that have been authenticated for the last
2373    // contract invocation.
2374    //
2375    // Authenticated means that either the authorization was authenticated using
2376    // the actual authorization logic for that authorization in enforced mode,
2377    // or that it was recorded in recording mode and authorization was assumed
2378    // successful.
2379    pub fn get_authenticated_authorizations(
2380        &self,
2381    ) -> Result<Vec<(ScAddress, SorobanAuthorizedInvocation)>, HostError> {
2382        Ok(self
2383            .try_borrow_previous_authorization_manager_mut()?
2384            .as_mut()
2385            .map(|am| am.get_authenticated_authorizations(self))
2386            // If no AuthorizationManager is setup, no authorizations could have
2387            // taken place so return an empty vec.
2388            .unwrap_or_default())
2389    }
2390}
2391
2392// metering: free for testutils
2393#[cfg(any(test, feature = "testutils"))]
2394impl PartialEq for RecordedAuthPayload {
2395    fn eq(&self, other: &Self) -> bool {
2396        self.address == other.address
2397            && self.invocation == other.invocation
2398            // Compare nonces only by presence of the value - recording mode
2399            // generates random nonces.
2400            && self.nonce.is_some() == other.nonce.is_some()
2401    }
2402}