soroban_env_host_zephyr/
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, CreateContractArgs, HashIdPreimage, HashIdPreimageSorobanAuthorization,
165        InvokeContractArgs, LedgerEntry, LedgerEntryData, LedgerEntryExt, ScAddress, ScErrorCode,
166        ScErrorType, ScNonceKey, ScVal, SorobanAuthorizationEntry, SorobanAuthorizedFunction,
167        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::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(CreateContractArgs),
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(CreateContractArgs),
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(xdr_args)
607            }
608        })
609    }
610
611    // metering: covered by the host
612    fn to_xdr(&self, host: &Host) -> Result<SorobanAuthorizedFunction, HostError> {
613        match self {
614            AuthorizedFunction::ContractFn(contract_fn) => {
615                let function_name = host.scsymbol_from_symbol(contract_fn.function_name)?;
616                Ok(SorobanAuthorizedFunction::ContractFn(InvokeContractArgs {
617                    contract_address: host.scaddress_from_address(contract_fn.contract_address)?,
618                    function_name,
619                    args: host.vals_to_scval_vec(contract_fn.args.as_slice())?,
620                }))
621            }
622            AuthorizedFunction::CreateContractHostFn(create_contract_args) => {
623                Ok(SorobanAuthorizedFunction::CreateContractHostFn(
624                    create_contract_args.metered_clone(host)?,
625                ))
626            }
627        }
628    }
629}
630
631impl AuthorizedInvocation {
632    // metering: covered
633    fn from_xdr(
634        host: &Host,
635        xdr_invocation: xdr::SorobanAuthorizedInvocation,
636    ) -> Result<Self, HostError> {
637        let sub_invocations_xdr = xdr_invocation.sub_invocations.into_vec();
638        let sub_invocations = sub_invocations_xdr
639            .into_iter()
640            .map(|a| AuthorizedInvocation::from_xdr(host, a))
641            .metered_collect::<Result<Vec<_>, _>>(host)??;
642        Ok(Self {
643            function: AuthorizedFunction::from_xdr(host, xdr_invocation.function)?,
644            sub_invocations,
645            is_exhausted: false,
646        })
647    }
648
649    // metering: free
650    pub(crate) fn new(
651        function: AuthorizedFunction,
652        sub_invocations: Vec<AuthorizedInvocation>,
653    ) -> Self {
654        Self {
655            function,
656            sub_invocations,
657            is_exhausted: false,
658        }
659    }
660
661    // metering: free
662    #[cfg(any(test, feature = "recording_mode"))]
663    fn new_recording(function: AuthorizedFunction) -> Self {
664        Self {
665            function,
666            sub_invocations: vec![],
667            is_exhausted: true,
668        }
669    }
670
671    // metering: covered
672    fn to_xdr(
673        &self,
674        host: &Host,
675        exhausted_sub_invocations_only: bool,
676    ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
677        Ok(xdr::SorobanAuthorizedInvocation {
678            function: self.function.to_xdr(host)?,
679            sub_invocations: self
680                .sub_invocations
681                .iter()
682                .filter(|i| i.is_exhausted || !exhausted_sub_invocations_only)
683                .map(|i| i.to_xdr(host, exhausted_sub_invocations_only))
684                .metered_collect::<Result<Vec<xdr::SorobanAuthorizedInvocation>, HostError>>(host)??
685                .try_into()?,
686        })
687    }
688
689    // Walks a path in the tree defined by `match_stack` and
690    // returns the last visited authorized node.
691    // metering: free
692    fn last_authorized_invocation_mut(
693        &mut self,
694        match_stack: &Vec<MatchState>,
695        call_stack_id: usize,
696    ) -> Result<&mut AuthorizedInvocation, HostError> {
697        // Start walking the stack from `call_stack_id`. We trust the callers to
698        // hold the invariant that `match_stack[call_stack_id - 1]`
699        // corresponds to this invocation tree, so that the next non-`None` child
700        // corresponds to the child of the current tree.
701        for (i, m) in match_stack.iter().enumerate().skip(call_stack_id) {
702            match m {
703                MatchState::SubMatch { index_in_parent } => {
704                    // We trust the caller to have the correct sub-invocation
705                    // indices.
706                    if let Some(sub) = self.sub_invocations.get_mut(*index_in_parent) {
707                        return sub.last_authorized_invocation_mut(match_stack, i + 1);
708                    } else {
709                        return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
710                    }
711                }
712                MatchState::RootMatch => {
713                    return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
714                }
715                // Skip Unmatched invocations as they don't require authorization.
716                MatchState::Unmatched => (),
717            }
718        }
719        Ok(self)
720    }
721
722    // metering: covered
723    fn snapshot(&self, budget: &Budget) -> Result<AuthorizedInvocationSnapshot, HostError> {
724        Ok(AuthorizedInvocationSnapshot {
725            is_exhausted: self.is_exhausted,
726            sub_invocations: self
727                .sub_invocations
728                .iter()
729                .map(|i| i.snapshot(budget))
730                .metered_collect::<Result<Vec<AuthorizedInvocationSnapshot>, HostError>>(
731                    budget,
732                )??,
733        })
734    }
735
736    // metering: free
737    fn rollback(&mut self, snapshot: &AuthorizedInvocationSnapshot) -> Result<(), HostError> {
738        self.is_exhausted = snapshot.is_exhausted;
739        if self.sub_invocations.len() != snapshot.sub_invocations.len() {
740            // This would be a bug.
741            return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
742        }
743        for (sub, snap) in self
744            .sub_invocations
745            .iter_mut()
746            .zip(snapshot.sub_invocations.iter())
747        {
748            sub.rollback(snap)?
749        }
750        Ok(())
751    }
752}
753
754impl Default for AuthorizationManager {
755    fn default() -> Self {
756        Self::new_enforcing_without_authorizations()
757    }
758}
759
760impl AuthorizationManager {
761    // Creates a new enforcing `AuthorizationManager` from the given
762    // authorization entries.
763    // This should be created once per top-level invocation.
764    // metering: covered
765    pub(crate) fn new_enforcing(
766        host: &Host,
767        auth_entries: Vec<SorobanAuthorizationEntry>,
768    ) -> Result<Self, HostError> {
769        let mut trackers = Vec::<RefCell<AccountAuthorizationTracker>>::with_metered_capacity(
770            auth_entries.len(),
771            host,
772        )?;
773        for auth_entry in auth_entries {
774            trackers.push(RefCell::new(
775                AccountAuthorizationTracker::from_authorization_entry(host, auth_entry)?,
776            ));
777        }
778        Ok(Self {
779            mode: AuthorizationMode::Enforcing,
780            call_stack: RefCell::new(vec![]),
781            account_trackers: RefCell::new(trackers),
782            invoker_contract_trackers: RefCell::new(vec![]),
783        })
784    }
785
786    // Creates a new enforcing `AuthorizationManager` that doesn't allow any
787    // authorizations.
788    // This is useful as a safe default mode.
789    // metering: free
790    pub(crate) fn new_enforcing_without_authorizations() -> Self {
791        Self {
792            mode: AuthorizationMode::Enforcing,
793            call_stack: RefCell::new(vec![]),
794            account_trackers: RefCell::new(vec![]),
795            invoker_contract_trackers: RefCell::new(vec![]),
796        }
797    }
798
799    // Creates a new recording `AuthorizationManager`.
800    // All the authorization requirements will be recorded and can then be
801    // retrieved using `get_recorded_auth_payloads`.
802    // metering: free
803    #[cfg(any(test, feature = "recording_mode"))]
804    pub(crate) fn new_recording(disable_non_root_auth: bool) -> Self {
805        Self {
806            mode: AuthorizationMode::Recording(RecordingAuthInfo {
807                tracker_by_address_handle: Default::default(),
808                disable_non_root_auth,
809            }),
810            call_stack: RefCell::new(vec![]),
811            account_trackers: RefCell::new(vec![]),
812            invoker_contract_trackers: RefCell::new(vec![]),
813        }
814    }
815
816    // Require the `address` to have authorized the current contract invocation
817    // with provided args and within the current context (i.e. the current
818    // authorized call stack and for the current network).
819    // In the recording mode this stores the auth requirement instead of
820    // verifying it.
821    // metering: covered
822    pub(crate) fn require_auth(
823        &self,
824        host: &Host,
825        address: AddressObject,
826        args: Vec<Val>,
827    ) -> Result<(), HostError> {
828        let _span = tracy_span!("require auth");
829        let authorized_function = self
830            .try_borrow_call_stack(host)?
831            .last()
832            .ok_or_else(|| {
833                host.err(
834                    ScErrorType::Auth,
835                    ScErrorCode::InternalError,
836                    "unexpected require_auth outside of valid frame",
837                    &[],
838                )
839            })?
840            .to_authorized_function(host, args)?;
841
842        self.require_auth_internal(host, address, authorized_function)
843    }
844
845    // metering: covered
846    pub(crate) fn add_invoker_contract_auth_with_curr_contract_as_invoker(
847        &self,
848        host: &Host,
849        auth_entries: VecObject,
850    ) -> Result<(), HostError> {
851        let auth_entries =
852            host.visit_obj(auth_entries, |e: &HostVec| e.to_vec(host.budget_ref()))?;
853        let mut trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
854        Vec::<InvokerContractAuthorizationTracker>::charge_bulk_init_cpy(
855            auth_entries.len() as u64,
856            host,
857        )?;
858        trackers.reserve(auth_entries.len());
859        for e in auth_entries {
860            trackers.push(
861                InvokerContractAuthorizationTracker::new_with_curr_contract_as_invoker(host, e)?,
862            )
863        }
864        Ok(())
865    }
866
867    // metering: covered by components
868    fn maybe_check_invoker_contract_auth(
869        &self,
870        host: &Host,
871        address: AddressObject,
872        function: &AuthorizedFunction,
873    ) -> Result<bool, HostError> {
874        {
875            let call_stack = self.try_borrow_call_stack(host)?;
876            // If stack has just one call there can't be invoker.
877            if call_stack.len() < 2 {
878                return Ok(false);
879            }
880
881            // Try matching the direct invoker contract first. It is considered to
882            // have authorized any direct calls.
883            let Some(invoker_frame) = &call_stack.get(call_stack.len() - 2) else {
884                return Err((ScErrorType::Auth, ScErrorCode::InternalError).into());
885            };
886            if let AuthStackFrame::Contract(invoker_contract) = invoker_frame {
887                if host
888                    .compare(&invoker_contract.contract_address, &address)?
889                    .is_eq()
890                {
891                    return Ok(true);
892                }
893            }
894        }
895        let mut invoker_contract_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
896        // If there is no direct invoker, there still might be a valid
897        // sub-contract call authorization from another invoker higher up the
898        // stack. Note, that invoker contract trackers consider the direct frame
899        // to never require auth (any `require_auth` calls would be matched by
900        // logic above).
901        for tracker in invoker_contract_trackers.iter_mut() {
902            if host.compare(&tracker.contract_address, &address)?.is_eq()
903                && tracker.maybe_authorize_invocation(host, function)?
904            {
905                return Ok(true);
906            }
907        }
908
909        return Ok(false);
910    }
911
912    // metering: covered by components
913    fn require_auth_enforcing(
914        &self,
915        host: &Host,
916        address: AddressObject,
917        function: &AuthorizedFunction,
918    ) -> Result<(), HostError> {
919        // Find if there is already an active tracker for this address that has
920        // not been matched for the current frame. If there is such tracker,
921        // this authorization has to be matched with an already active tracker.
922        // This prevents matching sets of disjoint authorization entries to
923        // a tree of calls.
924        let mut has_active_tracker = false;
925        for tracker in self.try_borrow_account_trackers(host)?.iter() {
926            if let Ok(tracker) = tracker.try_borrow() {
927                // If address doesn't match, just skip the tracker.
928                if host.compare(&tracker.address, &address)?.is_eq()
929                    && tracker.is_active()
930                    && !tracker.current_frame_is_already_matched()
931                {
932                    has_active_tracker = true;
933                    break;
934                }
935            }
936        }
937
938        // Iterate all the trackers and try to find one that
939        // fulfills the authorization requirement.
940        for tracker in self.try_borrow_account_trackers(host)?.iter() {
941            // Tracker can only be borrowed by the authorization manager itself.
942            // The only scenario in which re-borrow might occur is when
943            // `require_auth` is called within `__check_auth` call. The tracker
944            // that called `__check_auth` would be already borrowed in such
945            // scenario.
946            // We allow such call patterns in general, but we don't allow using
947            // tracker to verify auth for itself, i.e. we don't allow something
948            // like address.require_auth()->address_contract.__check_auth()
949            // ->address.require_auth(). Thus we simply skip the trackers that
950            // have already been borrowed.
951            if let Ok(mut tracker) = tracker.try_borrow_mut() {
952                // If tracker has already been used for this frame or the address
953                // doesn't match, just skip the tracker.
954                if !host.compare(&tracker.address, &address)?.is_eq() {
955                    continue;
956                }
957                match tracker.maybe_authorize_invocation(host, function, !has_active_tracker) {
958                    // If tracker doesn't have a matching invocation,
959                    // just skip it (there could still be another
960                    // tracker  that matches it).
961                    Ok(false) => continue,
962                    // Found a matching authorization.
963                    Ok(true) => return Ok(()),
964                    // Found a matching authorization, but another
965                    // requirement hasn't been fulfilled (for
966                    // example, incorrect authentication or nonce).
967                    Err(e) => return Err(e),
968                }
969            }
970        }
971        // No matching tracker found, hence the invocation isn't
972        // authorized.
973        Err(host.err(
974            ScErrorType::Auth,
975            ScErrorCode::InvalidAction,
976            "Unauthorized function call for address",
977            &[address.to_val()],
978        ))
979    }
980
981    #[cfg(any(test, feature = "recording_mode"))]
982    fn require_auth_recording(
983        &self,
984        host: &Host,
985        address: AddressObject,
986        function: AuthorizedFunction,
987        recording_info: &RecordingAuthInfo,
988    ) -> Result<(), HostError> {
989        // At first, try to find the tracker for this exact address
990        // object.
991        // This is a best-effort heuristic to come up with a reasonably
992        // looking recording tree for cases when multiple instances of
993        // the same exact address are used.
994        let address_obj_handle = address.get_handle();
995        let existing_tracker_id = recording_info
996            .try_borrow_tracker_by_address_handle(host)?
997            .get(&address_obj_handle)
998            .copied();
999        if let Some(tracker_id) = existing_tracker_id {
1000            // The tracker should not be borrowed recursively in
1001            // recording mode, as we don't call `__check_auth` in this
1002            // flow.
1003            let trackers = self.try_borrow_account_trackers(host)?;
1004            let Some(trackercell) = trackers.get(tracker_id) else {
1005                return Err(host.err(
1006                    ScErrorType::Auth,
1007                    ScErrorCode::InternalError,
1008                    "bad index for existing tracker",
1009                    &[],
1010                ));
1011            };
1012            if let Ok(mut tracker) = trackercell.try_borrow_mut() {
1013                // The recording invariant is that trackers are created
1014                // with the first authorized invocation, which means
1015                // that when their stack no longer has authorized
1016                // invocation, then we've popped frames past its root
1017                // and hence need to create a new tracker.
1018                if !tracker.has_authorized_invocations_in_stack() {
1019                    recording_info
1020                        .try_borrow_tracker_by_address_handle_mut(host)?
1021                        .remove(&address_obj_handle);
1022                } else {
1023                    return tracker.record_invocation(host, function);
1024                }
1025            } else {
1026                return Err(host.err(
1027                    ScErrorType::Auth,
1028                    ScErrorCode::InternalError,
1029                    "unexpected recursive tracker borrow in recording mode",
1030                    &[],
1031                ));
1032            };
1033        }
1034        // If there is no active tracker for this exact address object,
1035        // try to find any matching active tracker for the address.
1036        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1037            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1038                if !host.compare(&tracker.address, &address)?.is_eq() {
1039                    continue;
1040                }
1041                // Take the first tracker that is still active (i.e. has
1042                // active authorizations in the current call stack) and
1043                // hasn't been used for this stack frame yet.
1044                if tracker.has_authorized_invocations_in_stack()
1045                    && !tracker.current_frame_is_already_matched()
1046                {
1047                    return tracker.record_invocation(host, function);
1048                }
1049            } else {
1050                return Err(host.err(
1051                    ScErrorType::Auth,
1052                    ScErrorCode::InternalError,
1053                    "unexpected borrowed tracker in recording auth mode",
1054                    &[],
1055                ));
1056            }
1057        }
1058        // At this stage there is no active tracker to which we could
1059        // match the current invocation, thus we need to create a new
1060        // tracker.
1061        // Alert the user in `disable_non_root_auth` mode if we're not
1062        // in the root stack frame.
1063        if recording_info.disable_non_root_auth && self.try_borrow_call_stack(host)?.len() != 1 {
1064            return Err(host.err(
1065                ScErrorType::Auth,
1066                ScErrorCode::InvalidAction,
1067                "[recording authorization only] encountered authorization not tied \
1068                to the root contract invocation for an address. Use `require_auth()` \
1069                in the top invocation or enable non-root authorization.",
1070                &[address.into()],
1071            ));
1072        }
1073        // If a tracker for the new tree doesn't exist yet, create
1074        // it and initialize with the current invocation.
1075        self.try_borrow_account_trackers_mut(host)?
1076            .push(RefCell::new(AccountAuthorizationTracker::new_recording(
1077                host,
1078                address,
1079                function,
1080                self.try_borrow_call_stack(host)?.len(),
1081            )?));
1082        recording_info
1083            .try_borrow_tracker_by_address_handle_mut(host)?
1084            .insert(
1085                address_obj_handle,
1086                self.try_borrow_account_trackers(host)?.len() - 1,
1087            );
1088        Ok(())
1089    }
1090
1091    // metering: covered
1092    fn require_auth_internal(
1093        &self,
1094        host: &Host,
1095        address: AddressObject,
1096        function: AuthorizedFunction,
1097    ) -> Result<(), HostError> {
1098        // First check the InvokerContractAuthorizationTrackers
1099        if self.maybe_check_invoker_contract_auth(host, address, &function)? {
1100            return Ok(());
1101        }
1102        // Then check the AccountAuthorizationTrackers
1103        match &self.mode {
1104            AuthorizationMode::Enforcing => self.require_auth_enforcing(host, address, &function),
1105            // metering: free for recording
1106            #[cfg(any(test, feature = "recording_mode"))]
1107            AuthorizationMode::Recording(recording_info) => {
1108                self.require_auth_recording(host, address, function, recording_info)
1109            }
1110        }
1111    }
1112
1113    // Returns a snapshot of `AuthorizationManager` to use for rollback.
1114    // metering: covered
1115    fn snapshot(&self, host: &Host) -> Result<AuthorizationManagerSnapshot, HostError> {
1116        let _span = tracy_span!("snapshot auth");
1117        let account_trackers_snapshot = match &self.mode {
1118            AuthorizationMode::Enforcing => {
1119                let len = self.try_borrow_account_trackers(host)?.len();
1120                let mut snapshots =
1121                    Vec::<Option<AccountAuthorizationTrackerSnapshot>>::with_metered_capacity(
1122                        len, host,
1123                    )?;
1124                for t in self.try_borrow_account_trackers(host)?.iter() {
1125                    let sp = if let Ok(tracker) = t.try_borrow() {
1126                        Some(tracker.snapshot(host.as_budget())?)
1127                    } else {
1128                        // If tracker is borrowed, snapshotting it is a no-op
1129                        // (it can't change until we release it higher up the
1130                        // stack).
1131                        None
1132                    };
1133                    snapshots.push(sp);
1134                }
1135                AccountTrackersSnapshot::Enforcing(snapshots)
1136            }
1137            #[cfg(any(test, feature = "recording_mode"))]
1138            AuthorizationMode::Recording(_) => {
1139                // All trackers should be available to borrow for copy as in
1140                // recording mode we can't have recursive authorization.
1141                // metering: free for recording
1142                AccountTrackersSnapshot::Recording(self.try_borrow_account_trackers(host)?.clone())
1143            }
1144        };
1145        let invoker_contract_tracker_root_snapshots = self
1146            .try_borrow_invoker_contract_trackers(host)?
1147            .iter()
1148            .map(|t| t.invocation_tracker.snapshot(host.as_budget()))
1149            .metered_collect::<Result<Vec<AuthorizedInvocationSnapshot>, HostError>>(host)??;
1150        #[cfg(any(test, feature = "recording_mode"))]
1151        let tracker_by_address_handle = match &self.mode {
1152            AuthorizationMode::Enforcing => None,
1153            AuthorizationMode::Recording(recording_info) => Some(
1154                // metering: free for recording
1155                recording_info
1156                    .try_borrow_tracker_by_address_handle(host)?
1157                    .clone(),
1158            ),
1159        };
1160        Ok(AuthorizationManagerSnapshot {
1161            account_trackers_snapshot,
1162            invoker_contract_tracker_root_snapshots,
1163            #[cfg(any(test, feature = "recording_mode"))]
1164            tracker_by_address_handle,
1165        })
1166    }
1167
1168    // Rolls back this `AuthorizationManager` to the snapshot state.
1169    // metering: covered
1170    fn rollback(
1171        &self,
1172        host: &Host,
1173        snapshot: AuthorizationManagerSnapshot,
1174    ) -> Result<(), HostError> {
1175        let _span = tracy_span!("rollback auth");
1176        match snapshot.account_trackers_snapshot {
1177            AccountTrackersSnapshot::Enforcing(trackers_snapshot) => {
1178                let trackers = self.try_borrow_account_trackers(host)?;
1179                if trackers.len() != trackers_snapshot.len() {
1180                    return Err(host.err(
1181                        ScErrorType::Auth,
1182                        ScErrorCode::InternalError,
1183                        "unexpected bad auth snapshot",
1184                        &[],
1185                    ));
1186                }
1187                for (i, tracker) in trackers.iter().enumerate() {
1188                    let Some(snapopt) = trackers_snapshot.get(i) else {
1189                        return Err(host.err(
1190                            ScErrorType::Auth,
1191                            ScErrorCode::InternalError,
1192                            "unexpected auth snapshot index",
1193                            &[],
1194                        ));
1195                    };
1196                    if let Some(tracker_snapshot) = snapopt {
1197                        tracker
1198                            .try_borrow_mut()
1199                            .map_err(|_| {
1200                                host.err(
1201                                    ScErrorType::Auth,
1202                                    ScErrorCode::InternalError,
1203                                    "unexpected bad auth borrow",
1204                                    &[],
1205                                )
1206                            })?
1207                            .rollback(&tracker_snapshot)?;
1208                    }
1209                }
1210            }
1211            #[cfg(any(test, feature = "recording_mode"))]
1212            AccountTrackersSnapshot::Recording(s) => {
1213                *self.try_borrow_account_trackers_mut(host)? = s;
1214            }
1215        }
1216
1217        let mut invoker_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1218        if invoker_trackers.len() != snapshot.invoker_contract_tracker_root_snapshots.len() {
1219            return Err(host.err(
1220                ScErrorType::Auth,
1221                ScErrorCode::InternalError,
1222                "unexpected bad auth snapshot",
1223                &[],
1224            ));
1225        }
1226        for (tracker, snapshot) in invoker_trackers
1227            .iter_mut()
1228            .zip(snapshot.invoker_contract_tracker_root_snapshots.iter())
1229        {
1230            tracker.invocation_tracker.rollback(snapshot)?;
1231        }
1232
1233        #[cfg(any(test, feature = "recording_mode"))]
1234        if let Some(tracker_by_address_handle) = snapshot.tracker_by_address_handle {
1235            match &self.mode {
1236                AuthorizationMode::Enforcing => (),
1237                AuthorizationMode::Recording(recording_info) => {
1238                    *recording_info.try_borrow_tracker_by_address_handle_mut(host)? =
1239                        tracker_by_address_handle;
1240                }
1241            }
1242        }
1243        Ok(())
1244    }
1245
1246    // metering: covered
1247    fn push_tracker_frame(&self, host: &Host) -> Result<(), HostError> {
1248        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1249            // Skip already borrowed trackers, these must be in the middle of
1250            // authentication and hence don't need stack to be updated.
1251            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1252                tracker.push_frame(host.as_budget())?;
1253            }
1254        }
1255        for tracker in self
1256            .try_borrow_invoker_contract_trackers_mut(host)?
1257            .iter_mut()
1258        {
1259            tracker.push_frame(host.as_budget())?;
1260        }
1261        Ok(())
1262    }
1263
1264    // metering: covered
1265    pub(crate) fn push_create_contract_host_fn_frame(
1266        &self,
1267        host: &Host,
1268        args: CreateContractArgs,
1269    ) -> Result<(), HostError> {
1270        Vec::<CreateContractArgs>::charge_bulk_init_cpy(1, host)?;
1271        self.try_borrow_call_stack_mut(host)?
1272            .push(AuthStackFrame::CreateContractHostFn(args));
1273        self.push_tracker_frame(host)
1274    }
1275
1276    // Records a new call stack frame and returns a snapshot for rolling
1277    // back this stack frame.
1278    // This should be called for every `Host` `push_frame`.
1279    // metering: covered
1280    pub(crate) fn push_frame(
1281        &self,
1282        host: &Host,
1283        frame: &Frame,
1284    ) -> Result<AuthorizationManagerSnapshot, HostError> {
1285        let _span = tracy_span!("push auth frame");
1286        let (contract_id, function_name) = match frame {
1287            Frame::ContractVM { vm, fn_name, .. } => {
1288                (vm.contract_id.metered_clone(host)?, *fn_name)
1289            }
1290            // Skip the top-level host function stack frames as they don't
1291            // contain all the necessary information.
1292            // Use the respective push (like
1293            // `push_create_contract_host_fn_frame`) functions instead to push
1294            // the frame with the required info.
1295            Frame::HostFunction(_) => return self.snapshot(host),
1296            Frame::StellarAssetContract(id, fn_name, ..) => (id.metered_clone(host)?, *fn_name),
1297            #[cfg(any(test, feature = "testutils"))]
1298            Frame::TestContract(tc) => (tc.id.metered_clone(host)?, tc.func),
1299        };
1300        let contract_address = host.add_host_object(ScAddress::Contract(contract_id))?;
1301        Vec::<ContractInvocation>::charge_bulk_init_cpy(1, host)?;
1302        self.try_borrow_call_stack_mut(host)?
1303            .push(AuthStackFrame::Contract(ContractInvocation {
1304                contract_address,
1305                function_name,
1306            }));
1307
1308        self.push_tracker_frame(host)?;
1309        self.snapshot(host)
1310    }
1311
1312    // Pops a call stack frame and maybe rolls back the internal
1313    // state according to the provided snapshot.
1314    // This should be called for every `Host` `pop_frame`.
1315    // metering: covered
1316    pub(crate) fn pop_frame(
1317        &self,
1318        host: &Host,
1319        snapshot: Option<AuthorizationManagerSnapshot>,
1320    ) -> Result<(), HostError> {
1321        let _span = tracy_span!("pop auth frame");
1322        // Important: rollback has to be performed before popping the frame
1323        // from the tracker. This ensures correct work of invoker contract
1324        // trackers for which snapshots are dependent on the current
1325        // call stack.
1326        if let Some(snapshot) = snapshot {
1327            self.rollback(host, snapshot)?;
1328        }
1329        {
1330            let mut call_stack = self.try_borrow_call_stack_mut(host)?;
1331            // Currently we don't push host function call frames, hence this may be
1332            // called with empty stack. We trust the Host to keep things correct,
1333            // i.e. that only host function frames are ignored this way.
1334            if call_stack.is_empty() {
1335                return Ok(());
1336            }
1337            call_stack.pop();
1338        }
1339        for tracker in self.try_borrow_account_trackers(host)?.iter() {
1340            // Skip already borrowed trackers, these must be in the middle of
1341            // authentication and hence don't need stack to be updated.
1342            if let Ok(mut tracker) = tracker.try_borrow_mut() {
1343                tracker.pop_frame();
1344            }
1345        }
1346
1347        let mut invoker_contract_trackers = self.try_borrow_invoker_contract_trackers_mut(host)?;
1348        for tracker in invoker_contract_trackers.iter_mut() {
1349            tracker.pop_frame();
1350        }
1351        // Pop invoker contract trackers that went out of scope. The invariant
1352        // is that tracker only exists for the next sub-contract call (or until
1353        // the tracker's frame itself is popped). Thus trackers form a stack
1354        // where the shorter lifetime trackers are at the top.
1355        while let Some(last) = invoker_contract_trackers.last() {
1356            // *Subtle*: there are two possible scenarios when tracker is considered
1357            // to be out of scope:
1358            // - When the sub-contract call is finished. For example, contract A creates
1359            // a tracker, then calls contract B. When we pop the frame of contract B,
1360            // the tracker's stack will be empty and thus considered to be out of scope.
1361            // - When the contract that created the tracker is going out of scope without
1362            // calling any sub-contracts. For example, contract A creates a tracker and
1363            // returns. When we pop the frame of contract A, the tracker's stack will be
1364            // empty (because it's created empty), and thus considered to be out of scope.
1365            // The invariant above is maintained in both scenarios.
1366            if last.is_out_of_scope() {
1367                invoker_contract_trackers.pop();
1368            } else {
1369                break;
1370            }
1371        }
1372        Ok(())
1373    }
1374
1375    // Returns the recorded per-address authorization payloads that would cover the
1376    // top-level contract function invocation in the enforcing mode.
1377    // Should only be called in the recording mode.
1378    // metering: free, recording mode
1379    #[cfg(any(test, feature = "recording_mode"))]
1380    pub(crate) fn get_recorded_auth_payloads(
1381        &self,
1382        host: &Host,
1383    ) -> Result<Vec<RecordedAuthPayload>, HostError> {
1384        match &self.mode {
1385            AuthorizationMode::Enforcing => Err(HostError::from((
1386                ScErrorType::Auth,
1387                ScErrorCode::InternalError,
1388            ))),
1389            AuthorizationMode::Recording(_) => Ok(self
1390                .try_borrow_account_trackers(host)?
1391                .iter()
1392                .map(|tracker| tracker.try_borrow_or_err()?.get_recorded_auth_payload(host))
1393                .collect::<Result<Vec<RecordedAuthPayload>, HostError>>()?),
1394        }
1395    }
1396
1397    // For recording mode, emulates authentication that would normally happen in
1398    // the enforcing mode.
1399    // This helps to build a more realistic footprint and produce more correct
1400    // meterting data for the recording mode.
1401    // No-op in the enforcing mode.
1402    // metering: covered
1403    #[cfg(any(test, feature = "recording_mode"))]
1404    pub(crate) fn maybe_emulate_authentication(&self, host: &Host) -> Result<(), HostError> {
1405        match &self.mode {
1406            AuthorizationMode::Enforcing => Ok(()),
1407            AuthorizationMode::Recording(_) => {
1408                for tracker in self.try_borrow_account_trackers(host)?.iter() {
1409                    tracker
1410                        .try_borrow_mut_or_err()?
1411                        .emulate_authentication(host)?;
1412                }
1413                Ok(())
1414            }
1415        }
1416    }
1417
1418    // Returns a 'reset' instance of `AuthorizationManager` that has the same
1419    // mode, but no data.
1420    // metering: free, testutils
1421    #[cfg(any(test, feature = "testutils"))]
1422    pub(crate) fn reset(&mut self) {
1423        *self = match &self.mode {
1424            AuthorizationMode::Enforcing => {
1425                AuthorizationManager::new_enforcing_without_authorizations()
1426            }
1427            AuthorizationMode::Recording(rec_info) => {
1428                AuthorizationManager::new_recording(rec_info.disable_non_root_auth)
1429            }
1430        }
1431    }
1432
1433    // Returns all authorizations that have been authenticated for the
1434    // last contract invocation.
1435    // metering: free, testutils
1436    #[cfg(any(test, feature = "testutils"))]
1437    pub(crate) fn get_authenticated_authorizations(
1438        &self,
1439        host: &Host,
1440    ) -> Vec<(ScAddress, xdr::SorobanAuthorizedInvocation)> {
1441        host.as_budget()
1442            .with_observable_shadow_mode(|| {
1443                self.account_trackers
1444                    .borrow()
1445                    .iter()
1446                    .filter(|t| t.borrow().verified)
1447                    .map(|t| {
1448                        (
1449                            host.scaddress_from_address(t.borrow().address).unwrap(),
1450                            t.borrow()
1451                                .invocation_tracker
1452                                .root_authorized_invocation
1453                                .to_xdr(host, true)
1454                                .unwrap(),
1455                        )
1456                    })
1457                    .metered_collect(host)
1458            })
1459            .unwrap()
1460    }
1461}
1462
1463// Some helper extensions to support test-observation.
1464#[allow(dead_code)]
1465impl AuthorizationManager {
1466    pub(crate) fn stack_size(&self) -> usize {
1467        if let Ok(call_stack) = self.call_stack.try_borrow() {
1468            call_stack.len()
1469        } else {
1470            0
1471        }
1472    }
1473
1474    pub(crate) fn stack_hash(&self, budget: &Budget) -> Result<u64, HostError> {
1475        use std::hash::Hasher;
1476        if let Ok(call_stack) = self.call_stack.try_borrow() {
1477            let mut state = CountingHasher::default();
1478            call_stack.metered_hash(&mut state, budget)?;
1479            Ok(state.finish())
1480        } else {
1481            Ok(0)
1482        }
1483    }
1484
1485    pub(crate) fn trackers_hash_and_size(
1486        &self,
1487        budget: &Budget,
1488    ) -> Result<(u64, usize), HostError> {
1489        use std::hash::Hasher;
1490        let mut size: usize = 0;
1491        let mut state = CountingHasher::default();
1492        self.mode.metered_hash(&mut state, budget)?;
1493        if let Ok(account_trackers) = self.account_trackers.try_borrow() {
1494            for tracker in account_trackers.iter() {
1495                if let Ok(tracker) = tracker.try_borrow() {
1496                    size = size.saturating_add(1);
1497                    tracker.metered_hash(&mut state, budget)?;
1498                }
1499            }
1500        }
1501        if let Ok(invoker_contract_trackers) = self.invoker_contract_trackers.try_borrow() {
1502            for tracker in invoker_contract_trackers.iter() {
1503                size = size.saturating_add(1);
1504                tracker.metered_hash(&mut state, budget)?;
1505            }
1506        }
1507        Ok((state.finish(), size))
1508    }
1509}
1510
1511impl InvocationTracker {
1512    // metering: covered by components
1513    fn from_xdr(
1514        host: &Host,
1515        root_invocation: xdr::SorobanAuthorizedInvocation,
1516    ) -> Result<Self, HostError> {
1517        Ok(Self {
1518            root_authorized_invocation: AuthorizedInvocation::from_xdr(host, root_invocation)?,
1519            match_stack: vec![],
1520            root_exhausted_frame: None,
1521            is_fully_processed: false,
1522        })
1523    }
1524
1525    // metering: free
1526    fn new(root_authorized_invocation: AuthorizedInvocation) -> Self {
1527        Self {
1528            root_authorized_invocation,
1529            match_stack: vec![],
1530            root_exhausted_frame: None,
1531            is_fully_processed: false,
1532        }
1533    }
1534
1535    // metering: free for recording
1536    #[cfg(any(test, feature = "recording_mode"))]
1537    fn new_recording(function: AuthorizedFunction, current_stack_len: usize) -> Self {
1538        // Create the stack of `MatchState::Unmatched` leading to the current invocation to
1539        // represent invocations that didn't need authorization on behalf of
1540        // the tracked address.
1541        let mut match_stack = vec![MatchState::Unmatched; current_stack_len - 1];
1542        // Add a MatchState for the current(root) invocation.
1543        match_stack.push(MatchState::RootMatch);
1544        let root_exhausted_frame = Some(match_stack.len() - 1);
1545        Self {
1546            root_authorized_invocation: AuthorizedInvocation::new_recording(function),
1547            match_stack,
1548            root_exhausted_frame,
1549            is_fully_processed: false,
1550        }
1551    }
1552
1553    // Walks a path in the tree defined by `match_stack` and
1554    // returns the last visited authorized node.
1555    // metering: free
1556    fn last_authorized_invocation_mut(
1557        &mut self,
1558    ) -> Result<Option<&mut AuthorizedInvocation>, HostError> {
1559        for (i, m) in self.match_stack.iter().enumerate() {
1560            match m {
1561                MatchState::RootMatch => {
1562                    return Ok(Some(
1563                        self.root_authorized_invocation
1564                            .last_authorized_invocation_mut(&self.match_stack, i + 1)?,
1565                    ));
1566                }
1567                MatchState::SubMatch { .. } => {
1568                    return Err((ScErrorType::Auth, ScErrorCode::InternalError).into())
1569                }
1570                MatchState::Unmatched => (),
1571            }
1572        }
1573        Ok(None)
1574    }
1575
1576    // metering: covered
1577    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1578        Vec::<usize>::charge_bulk_init_cpy(1, budget)?;
1579        self.match_stack.push(MatchState::Unmatched);
1580        Ok(())
1581    }
1582
1583    // metering: free
1584    fn pop_frame(&mut self) {
1585        self.match_stack.pop();
1586        if let Some(root_exhausted_frame) = self.root_exhausted_frame {
1587            if root_exhausted_frame >= self.match_stack.len() {
1588                self.is_fully_processed = true;
1589            }
1590        }
1591    }
1592
1593    // metering: free
1594    fn is_empty(&self) -> bool {
1595        self.match_stack.is_empty()
1596    }
1597
1598    // metering: free
1599    fn is_active(&self) -> bool {
1600        self.root_authorized_invocation.is_exhausted && !self.is_fully_processed
1601    }
1602
1603    // metering: free
1604    fn current_frame_is_already_matched(&self) -> bool {
1605        match self.match_stack.last() {
1606            Some(x) => x.is_matched(),
1607            _ => false,
1608        }
1609    }
1610
1611    // Tries to match the provided invocation as an extension of the last
1612    // currently-matched sub-invocation of the authorized invocation tree (or
1613    // the root, if there is no matched invocation yet). If matching succeeds,
1614    // it writes the match to the corresponding entry in
1615    // [`InvocationTracker::match_stack`].
1616    //
1617    // Returns `true` if the match has been found for the first time per current
1618    // frame.
1619    //
1620    // Metering: covered by components
1621    fn maybe_extend_invocation_match(
1622        &mut self,
1623        host: &Host,
1624        function: &AuthorizedFunction,
1625        allow_matching_root: bool,
1626    ) -> Result<bool, HostError> {
1627        if self.current_frame_is_already_matched() {
1628            return Ok(false);
1629        }
1630        let mut new_match_state = MatchState::Unmatched;
1631        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1632            for (index_in_parent, sub_invocation) in
1633                curr_invocation.sub_invocations.iter_mut().enumerate()
1634            {
1635                if !sub_invocation.is_exhausted
1636                    && host.compare(&sub_invocation.function, function)?.is_eq()
1637                {
1638                    new_match_state = MatchState::SubMatch { index_in_parent };
1639                    sub_invocation.is_exhausted = true;
1640                    break;
1641                }
1642            }
1643        } else if !self.root_authorized_invocation.is_exhausted
1644            && allow_matching_root
1645            && host
1646                .compare(&self.root_authorized_invocation.function, &function)?
1647                .is_eq()
1648        {
1649            new_match_state = MatchState::RootMatch;
1650            self.root_authorized_invocation.is_exhausted = true;
1651            self.root_exhausted_frame = Some(self.match_stack.len() - 1);
1652        }
1653        if new_match_state.is_matched() {
1654            *self.match_stack.last_mut().ok_or_else(|| {
1655                host.err(
1656                    ScErrorType::Auth,
1657                    ScErrorCode::InternalError,
1658                    "invalid match_stack",
1659                    &[],
1660                )
1661            })? = new_match_state;
1662        }
1663        Ok(new_match_state.is_matched())
1664    }
1665
1666    // Records the invocation in this tracker.
1667    // This is needed for the recording mode only.
1668    // This assumes that the address matching is correctly performed before
1669    // calling this.
1670    // metering: free for recording
1671    #[cfg(any(test, feature = "recording_mode"))]
1672    fn record_invocation(
1673        &mut self,
1674        host: &Host,
1675        function: AuthorizedFunction,
1676    ) -> Result<(), HostError> {
1677        if self.current_frame_is_already_matched() {
1678            return Err(host.err(
1679                ScErrorType::Auth,
1680                ScErrorCode::ExistingValue,
1681                "frame is already authorized",
1682                &[],
1683            ));
1684        }
1685        if let Some(curr_invocation) = self.last_authorized_invocation_mut()? {
1686            curr_invocation
1687                .sub_invocations
1688                .push(AuthorizedInvocation::new_recording(function));
1689            let index_in_parent = curr_invocation.sub_invocations.len() - 1;
1690            *self.match_stack.last_mut().unwrap() = MatchState::SubMatch { index_in_parent };
1691        } else {
1692            // This would be a bug
1693            return Err(host.err(
1694                ScErrorType::Auth,
1695                ScErrorCode::InternalError,
1696                "unexpected missing authorized invocation",
1697                &[],
1698            ));
1699        }
1700        Ok(())
1701    }
1702
1703    // metering: free
1704    #[cfg(any(test, feature = "recording_mode"))]
1705    fn has_matched_invocations_in_stack(&self) -> bool {
1706        self.match_stack.iter().any(|i| i.is_matched())
1707    }
1708
1709    // metering: covered
1710    fn snapshot(&self, budget: &Budget) -> Result<AuthorizedInvocationSnapshot, HostError> {
1711        self.root_authorized_invocation.snapshot(budget)
1712    }
1713
1714    // metering: covered
1715    fn rollback(&mut self, snapshot: &AuthorizedInvocationSnapshot) -> Result<(), HostError> {
1716        self.root_authorized_invocation.rollback(snapshot)?;
1717        // Invocation can only be rolled back from 'exhausted' to
1718        // 'non-exhausted' state (as there is no other way to go from
1719        // 'exhausted' state back to 'non-exhausted' state).
1720        if !self.root_authorized_invocation.is_exhausted {
1721            self.root_exhausted_frame = None;
1722            self.is_fully_processed = false;
1723        }
1724        Ok(())
1725    }
1726}
1727
1728impl AccountAuthorizationTracker {
1729    // Metering: covered by the host and components
1730    fn from_authorization_entry(
1731        host: &Host,
1732        auth_entry: SorobanAuthorizationEntry,
1733    ) -> Result<Self, HostError> {
1734        let (address, nonce, signature, is_transaction_source_account) =
1735            match auth_entry.credentials {
1736                SorobanCredentials::SourceAccount => (
1737                    host.source_account_address()?.ok_or_else(|| {
1738                        host.err(
1739                            ScErrorType::Auth,
1740                            ScErrorCode::InternalError,
1741                            "source account is missing when setting auth entries",
1742                            &[],
1743                        )
1744                    })?,
1745                    None,
1746                    Val::VOID.into(),
1747                    true,
1748                ),
1749                SorobanCredentials::Address(address_creds) => (
1750                    host.add_host_object(address_creds.address)?,
1751                    Some((
1752                        address_creds.nonce,
1753                        address_creds.signature_expiration_ledger,
1754                    )),
1755                    host.to_host_val(&address_creds.signature)?,
1756                    false,
1757                ),
1758            };
1759        Ok(Self {
1760            address,
1761            invocation_tracker: InvocationTracker::from_xdr(host, auth_entry.root_invocation)?,
1762            signature,
1763            verified: false,
1764            is_transaction_source_account,
1765            nonce,
1766        })
1767    }
1768
1769    // metering: free, since this is recording mode only
1770    #[cfg(any(test, feature = "recording_mode"))]
1771    fn new_recording(
1772        host: &Host,
1773        address: AddressObject,
1774        function: AuthorizedFunction,
1775        current_stack_len: usize,
1776    ) -> Result<Self, HostError> {
1777        if current_stack_len == 0 {
1778            // This would be a bug.
1779            return Err(host.err(
1780                ScErrorType::Auth,
1781                ScErrorCode::InternalError,
1782                "unexpected empty stack in recording auth",
1783                &[],
1784            ));
1785        }
1786        // Decide if we're tracking the transaction source account, and if so
1787        // don't bother with a nonce.
1788        let is_transaction_source_account =
1789            if let Some(source_acc) = host.source_account_address()? {
1790                host.compare(&source_acc, &address)?.is_eq()
1791            } else {
1792                false
1793            };
1794        let nonce = if !is_transaction_source_account {
1795            let random_nonce: i64 =
1796                host.with_recording_auth_nonce_prng(|p| Ok(p.gen_range(0..=i64::MAX)))?;
1797            // We use the `max_live_until_ledger` as the nonce lifetime here
1798            // in order to account for a maximum possible rent fee (given maximum
1799            // possible signature expiration). However, we don't want to actually
1800            // store that as nonce expiration ledger in the recording tracker,
1801            // as users are able (and encouraged) to customize the signature
1802            // expiration after simulation and before signing the auth payload.
1803            host.consume_nonce(address, random_nonce, host.max_live_until_ledger()?)?;
1804            Some((random_nonce, 0))
1805        } else {
1806            None
1807        };
1808        Ok(Self {
1809            address,
1810            invocation_tracker: InvocationTracker::new_recording(function, current_stack_len),
1811            signature: Val::VOID.into(),
1812            verified: true,
1813            is_transaction_source_account,
1814            nonce,
1815        })
1816    }
1817
1818    // Tries to find and enforce the provided invocation with this tracker and
1819    // lazily performs authentication when needed.
1820    // This is needed for the enforcing mode only.
1821    // This assumes that the address matching is correctly performed before
1822    // calling this.
1823    // Returns true/false based on whether the invocation is found in the
1824    // tracker. Returns error if invocation has been found, but the tracker
1825    // itself is not valid (failed authentication or nonce check).
1826    // metering: covered
1827    fn maybe_authorize_invocation(
1828        &mut self,
1829        host: &Host,
1830        function: &AuthorizedFunction,
1831        allow_matching_root: bool,
1832    ) -> Result<bool, HostError> {
1833        if !self.invocation_tracker.maybe_extend_invocation_match(
1834            host,
1835            function,
1836            allow_matching_root,
1837        )? {
1838            // The call isn't found in the currently tracked tree or is already
1839            // authorized in it.
1840            // That doesn't necessarily mean it's unauthorized (it can be
1841            // authorized in a different tracker).
1842            return Ok(false);
1843        }
1844        if !self.verified {
1845            let authenticate_res = self
1846                .authenticate(host)
1847                .map_err(|err| {
1848                    // Convert any recoverable errors to auth errors so that it's
1849                    // not possible to confuse them for the errors of the
1850                    // contract that has called `require_auth`.
1851                    // While there is no 'recovery' here, non-recoverable errors
1852                    // aren't really useful for decoration.
1853                    if err.is_recoverable() {
1854                        // Also log the original error for diagnostics.
1855                        host.err(
1856                            ScErrorType::Auth,
1857                            ScErrorCode::InvalidAction,
1858                            "failed account authentication with error",
1859                            &[self.address.into(), err.error.to_val()],
1860                        )
1861                    } else {
1862                        err
1863                    }
1864                })
1865                .and_then(|_| self.verify_and_consume_nonce(host));
1866            if let Some(err) = authenticate_res.err() {
1867                return Err(err);
1868            }
1869            self.verified = true;
1870        }
1871        Ok(true)
1872    }
1873
1874    // Records the invocation in this tracker.
1875    // This is needed for the recording mode only.
1876    // This assumes that the address matching is correctly performed before
1877    // calling this.
1878    // metering: free for recording
1879    #[cfg(any(test, feature = "recording_mode"))]
1880    fn record_invocation(
1881        &mut self,
1882        host: &Host,
1883        function: AuthorizedFunction,
1884    ) -> Result<(), HostError> {
1885        self.invocation_tracker.record_invocation(host, function)
1886    }
1887
1888    // Build the authorization payload from the invocations recorded in this
1889    // tracker.
1890    // metering: free for recording
1891    #[cfg(any(test, feature = "recording_mode"))]
1892    fn get_recorded_auth_payload(&self, host: &Host) -> Result<RecordedAuthPayload, HostError> {
1893        host.as_budget().with_observable_shadow_mode(|| {
1894            Ok(RecordedAuthPayload {
1895                address: if !self.is_transaction_source_account {
1896                    Some(host.visit_obj(self.address, |a: &ScAddress| a.metered_clone(host))?)
1897                } else {
1898                    None
1899                },
1900                invocation: self
1901                    .invocation_tracker
1902                    .root_authorized_invocation
1903                    .to_xdr(host, false)?,
1904                nonce: self.nonce.map(|(nonce, _)| nonce),
1905            })
1906        })
1907    }
1908
1909    // Checks if there is at least one authorized invocation in the current call
1910    // stack.
1911    // metering: free
1912    #[cfg(any(test, feature = "recording_mode"))]
1913    fn has_authorized_invocations_in_stack(&self) -> bool {
1914        self.invocation_tracker.has_matched_invocations_in_stack()
1915    }
1916
1917    // metering: covered
1918    fn root_invocation_to_xdr(
1919        &self,
1920        host: &Host,
1921    ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
1922        self.invocation_tracker
1923            .root_authorized_invocation
1924            .to_xdr(host, false)
1925    }
1926
1927    // metering: covered
1928    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
1929        self.invocation_tracker.push_frame(budget)
1930    }
1931
1932    // metering: covered
1933    fn pop_frame(&mut self) {
1934        self.invocation_tracker.pop_frame();
1935    }
1936
1937    // metering: covered
1938    fn verify_and_consume_nonce(&mut self, host: &Host) -> Result<(), HostError> {
1939        if self.is_transaction_source_account {
1940            return Ok(());
1941        }
1942        if let Some((nonce, live_until_ledger)) = &self.nonce {
1943            let ledger_seq = host.with_ledger_info(|li| Ok(li.sequence_number))?;
1944            if ledger_seq > *live_until_ledger {
1945                return Err(host.err(
1946                    ScErrorType::Auth,
1947                    ScErrorCode::InvalidInput,
1948                    "signature has expired",
1949                    &[
1950                        self.address.into(),
1951                        ledger_seq.try_into_val(host)?,
1952                        live_until_ledger.try_into_val(host)?,
1953                    ],
1954                ));
1955            }
1956            let max_live_until_ledger = host.max_live_until_ledger()?;
1957            if *live_until_ledger > max_live_until_ledger {
1958                return Err(host.err(
1959                    ScErrorType::Auth,
1960                    ScErrorCode::InvalidInput,
1961                    "signature expiration is too late",
1962                    &[
1963                        self.address.into(),
1964                        max_live_until_ledger.try_into_val(host)?,
1965                        live_until_ledger.try_into_val(host)?,
1966                    ],
1967                ));
1968            }
1969
1970            return host.consume_nonce(self.address, *nonce, *live_until_ledger);
1971        }
1972        Err(host.err(
1973            ScErrorType::Auth,
1974            ScErrorCode::InternalError,
1975            "unexpected nonce verification state",
1976            &[],
1977        ))
1978    }
1979
1980    // Computes the payload that has to be signed in order to authenticate
1981    // the authorized invocation tree corresponding to this tracker.
1982    // metering: covered by components
1983    fn get_signature_payload(&self, host: &Host) -> Result<[u8; 32], HostError> {
1984        let (nonce, live_until_ledger) = self.nonce.ok_or_else(|| {
1985            host.err(
1986                ScErrorType::Auth,
1987                ScErrorCode::InternalError,
1988                "unexpected missing nonce",
1989                &[],
1990            )
1991        })?;
1992        let payload_preimage =
1993            HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization {
1994                network_id: Hash(host.with_ledger_info(|li| li.network_id.metered_clone(host))?),
1995                nonce,
1996                signature_expiration_ledger: live_until_ledger,
1997                invocation: self.root_invocation_to_xdr(host)?,
1998            });
1999
2000        host.metered_hash_xdr(&payload_preimage)
2001    }
2002
2003    // metering: covered by the hsot
2004    fn authenticate(&self, host: &Host) -> Result<(), HostError> {
2005        if self.is_transaction_source_account {
2006            return Ok(());
2007        }
2008
2009        let sc_addr = host.scaddress_from_address(self.address)?;
2010        // TODO: there should also be a mode where a dummy payload is used
2011        // instead (for enforcing mode preflight).
2012        let payload = self.get_signature_payload(host)?;
2013        match sc_addr {
2014            ScAddress::Account(acc) => {
2015                check_account_authentication(host, acc, &payload, self.signature)?;
2016            }
2017            ScAddress::Contract(acc_contract) => {
2018                check_account_contract_auth(
2019                    host,
2020                    &acc_contract,
2021                    &payload,
2022                    self.signature,
2023                    &self.invocation_tracker.root_authorized_invocation,
2024                )?;
2025            }
2026        }
2027        Ok(())
2028    }
2029
2030    // Emulates authentication for the recording mode.
2031    // metering: covered
2032    #[cfg(any(test, feature = "recording_mode"))]
2033    fn emulate_authentication(&mut self, host: &Host) -> Result<(), HostError> {
2034        if self.is_transaction_source_account {
2035            return Ok(());
2036        }
2037        let sc_addr = host.scaddress_from_address(self.address)?;
2038        match sc_addr {
2039            ScAddress::Account(acc) => {
2040                // Emulate verification of a single signature that belongs to this
2041                // account.
2042                // We could emulate more (up to 20) signature verifications, but
2043                // since signature verification is a pretty expensive operation, while
2044                // multisig in combination with Soroban auth is probably pretty rare,
2045                // multisig users should either use enforcing auth simulation, or
2046                // intentionally increase the instruction count on the recording result.
2047                let key_bytes = match &acc.0 {
2048                    PublicKey::PublicKeyTypeEd25519(k) => k.0,
2049                };
2050                let signature = AccountEd25519Signature {
2051                    public_key: BytesN::from_slice(host, &key_bytes)?,
2052                    signature: BytesN::from_slice(host, &[0_u8; 64])?,
2053                };
2054                let signatures = host_vec![host, signature]?;
2055                self.signature = signatures.into();
2056                // Authentication is expected to fail here after signature verification,
2057                // so we suppress the error and diagnostics.
2058                host.with_suppressed_diagnostic_events(|| {
2059                    let _ = self.authenticate(host);
2060                    Ok(())
2061                })?;
2062
2063                // Emulate a clone of the account, which serves 2 purposes:
2064                // - Account for metered clone in `get_signer_weight_from_account`
2065                // - Return budget error in case if it was suppressed above.
2066                let _ = acc.metered_clone(host.as_budget())?;
2067            }
2068            // Skip custom accounts for now - emulating authentication for
2069            // them requires a dummy signature.
2070            ScAddress::Contract(_) => {}
2071        }
2072        Ok(())
2073    }
2074
2075    // metering: covered
2076    fn snapshot(&self, budget: &Budget) -> Result<AccountAuthorizationTrackerSnapshot, HostError> {
2077        Ok(AccountAuthorizationTrackerSnapshot {
2078            invocation_tracker_root_snapshot: self.invocation_tracker.snapshot(budget)?,
2079            // The verification status can be rolled back in case
2080            // of a contract failure. In case if the root call has
2081            // failed, the nonce will get 'un-consumed' due to storage
2082            // rollback. Thus we need to run verification and consume
2083            // it again in case if the call is retried. Another subtle
2084            // case where this behavior is important is the case when
2085            // custom account's authentication function depends on
2086            // some ledger state which might get modified in-between calls.
2087            verified: self.verified,
2088        })
2089    }
2090
2091    // metering: covered
2092    fn rollback(
2093        &mut self,
2094        snapshot: &AccountAuthorizationTrackerSnapshot,
2095    ) -> Result<(), HostError> {
2096        self.invocation_tracker
2097            .rollback(&snapshot.invocation_tracker_root_snapshot)?;
2098        self.verified = snapshot.verified;
2099        Ok(())
2100    }
2101
2102    // metering: free
2103    fn is_active(&self) -> bool {
2104        self.invocation_tracker.is_active()
2105    }
2106
2107    // metering: free
2108    fn current_frame_is_already_matched(&self) -> bool {
2109        self.invocation_tracker.current_frame_is_already_matched()
2110    }
2111}
2112
2113impl InvokerContractAuthorizationTracker {
2114    // metering: covered by components
2115    fn new_with_curr_contract_as_invoker(
2116        host: &Host,
2117        invoker_auth_entry: Val,
2118    ) -> Result<Self, HostError> {
2119        let invoker_sc_addr = ScAddress::Contract(host.get_current_contract_id_internal()?);
2120        let authorized_invocation = invoker_contract_auth_to_authorized_invocation(
2121            host,
2122            &invoker_sc_addr,
2123            invoker_auth_entry,
2124        )?;
2125        let invocation_tracker = InvocationTracker::new(authorized_invocation);
2126        Ok(Self {
2127            contract_address: host.add_host_object(invoker_sc_addr)?,
2128            invocation_tracker,
2129        })
2130    }
2131
2132    // metering: covered
2133    fn push_frame(&mut self, budget: &Budget) -> Result<(), HostError> {
2134        self.invocation_tracker.push_frame(budget)
2135    }
2136
2137    // metering: covered
2138    fn pop_frame(&mut self) {
2139        self.invocation_tracker.pop_frame();
2140    }
2141
2142    // metering: free
2143    fn is_out_of_scope(&self) -> bool {
2144        self.invocation_tracker.is_empty()
2145    }
2146
2147    // metering: covered
2148    fn maybe_authorize_invocation(
2149        &mut self,
2150        host: &Host,
2151        function: &AuthorizedFunction,
2152    ) -> Result<bool, HostError> {
2153        // Authorization is successful if function is just matched by the
2154        // tracker. No authentication is needed.
2155        self.invocation_tracker
2156            .maybe_extend_invocation_match(host, function, true)
2157    }
2158}
2159
2160impl Host {
2161    // metering: covered by components
2162    fn consume_nonce(
2163        &self,
2164        address: AddressObject,
2165        nonce: i64,
2166        live_until_ledger: u32,
2167    ) -> Result<(), HostError> {
2168        let nonce_key_scval = ScVal::LedgerKeyNonce(ScNonceKey { nonce });
2169        let sc_address = self.scaddress_from_address(address)?;
2170        let nonce_key = self.storage_key_for_address(
2171            sc_address.metered_clone(self)?,
2172            nonce_key_scval.metered_clone(self)?,
2173            xdr::ContractDataDurability::Temporary,
2174        )?;
2175        let live_until_ledger = live_until_ledger
2176            .max(self.get_min_live_until_ledger(xdr::ContractDataDurability::Temporary)?);
2177        self.with_mut_storage(|storage| {
2178            if storage
2179                .has_with_host(&nonce_key, self, None)
2180                .map_err(|err| {
2181                    if err.error.is_type(ScErrorType::Storage)
2182                        && err.error.is_code(ScErrorCode::ExceededLimit)
2183                    {
2184                        return self.err(
2185                            ScErrorType::Storage,
2186                            ScErrorCode::ExceededLimit,
2187                            "trying to access nonce outside of footprint for address",
2188                            &[address.to_val()],
2189                        );
2190                    }
2191                    err
2192                })?
2193            {
2194                return Err(self.err(
2195                    ScErrorType::Auth,
2196                    ScErrorCode::ExistingValue,
2197                    "nonce already exists for address",
2198                    &[address.into()],
2199                ));
2200            }
2201            let data = LedgerEntryData::ContractData(ContractDataEntry {
2202                contract: sc_address,
2203                key: nonce_key_scval,
2204                val: ScVal::Void,
2205                durability: xdr::ContractDataDurability::Temporary,
2206                ext: xdr::ExtensionPoint::V0,
2207            });
2208            let entry = LedgerEntry {
2209                last_modified_ledger_seq: 0,
2210                data,
2211                ext: LedgerEntryExt::V0,
2212            };
2213            storage.put_with_host(
2214                &nonce_key,
2215                &Rc::metered_new(entry, self)?,
2216                Some(live_until_ledger),
2217                self,
2218                None,
2219            )
2220        })
2221    }
2222
2223    // Returns the recorded per-address authorization payloads that would cover the
2224    // top-level contract function invocation in the enforcing mode.
2225    // This should only be called in the recording authorization mode, i.e. only
2226    // if `switch_to_recording_auth` has been called.
2227    #[cfg(any(test, feature = "recording_mode"))]
2228    pub fn get_recorded_auth_payloads(&self) -> Result<Vec<RecordedAuthPayload>, HostError> {
2229        #[cfg(not(any(test, feature = "testutils")))]
2230        {
2231            self.try_borrow_authorization_manager()?
2232                .get_recorded_auth_payloads(self)
2233        }
2234        #[cfg(any(test, feature = "testutils"))]
2235        {
2236            let payloads = self
2237                .try_borrow_previous_authorization_manager()?
2238                .as_ref()
2239                .ok_or_else(|| {
2240                    self.err(
2241                        ScErrorType::Auth,
2242                        ScErrorCode::InvalidAction,
2243                        "previous invocation is missing - no auth data to get",
2244                        &[],
2245                    )
2246                })?
2247                .get_recorded_auth_payloads(self)?;
2248            Ok(payloads)
2249        }
2250    }
2251}
2252
2253#[cfg(any(test, feature = "testutils"))]
2254use crate::{host::frame::ContractReentryMode, xdr::SorobanAuthorizedInvocation};
2255
2256#[cfg(any(test, feature = "testutils"))]
2257impl Host {
2258    /// Invokes the reserved `__check_auth` function on a provided contract.
2259    ///
2260    /// This is useful for testing the custom account contracts. Otherwise, the
2261    /// host prohibits calling `__check_auth` outside of internal implementation
2262    /// of `require_auth[_for_args]` calls.
2263    pub fn call_account_contract_check_auth(
2264        &self,
2265        contract: AddressObject,
2266        args: VecObject,
2267    ) -> Result<Val, HostError> {
2268        use crate::builtin_contracts::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME;
2269        let contract_id = self.contract_id_from_address(contract)?;
2270        let args_vec = self.call_args_from_obj(args)?;
2271        let res = self.call_n_internal(
2272            &contract_id,
2273            ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(self)?,
2274            args_vec.as_slice(),
2275            ContractReentryMode::Prohibited,
2276            true,
2277        );
2278        if let Err(e) = &res {
2279            self.error(
2280                e.error,
2281                "check auth invocation for a custom account contract failed",
2282                &[contract.to_val(), args.to_val()],
2283            );
2284        }
2285        res
2286    }
2287
2288    /// Returns the current state of the authorization manager.
2289    ///
2290    /// Use this in conjunction with `set_auth_manager` to do authorized
2291    /// operations without breaking the current authorization state (useful for
2292    /// preserving the auth state while doing the generic test setup).
2293    pub fn snapshot_auth_manager(&self) -> Result<AuthorizationManager, HostError> {
2294        Ok(self.try_borrow_authorization_manager()?.clone())
2295    }
2296
2297    /// Replaces authorization manager with the provided new instance.
2298    ///
2299    /// Use this in conjunction with `snapshot_auth_manager` to do authorized
2300    /// operations without breaking the current authorization state (useful for
2301    /// preserving the auth state while doing the generic test setup).
2302    pub fn set_auth_manager(&self, auth_manager: AuthorizationManager) -> Result<(), HostError> {
2303        *self.try_borrow_authorization_manager_mut()? = auth_manager;
2304        Ok(())
2305    }
2306
2307    // Returns the authorizations that have been authenticated for the last
2308    // contract invocation.
2309    //
2310    // Authenticated means that either the authorization was authenticated using
2311    // the actual authorization logic for that authorization in enforced mode,
2312    // or that it was recorded in recording mode and authorization was assumed
2313    // successful.
2314    pub fn get_authenticated_authorizations(
2315        &self,
2316    ) -> Result<Vec<(ScAddress, SorobanAuthorizedInvocation)>, HostError> {
2317        Ok(self
2318            .try_borrow_previous_authorization_manager_mut()?
2319            .as_mut()
2320            .map(|am| am.get_authenticated_authorizations(self))
2321            // If no AuthorizationManager is setup, no authorizations could have
2322            // taken place so return an empty vec.
2323            .unwrap_or_default())
2324    }
2325}
2326
2327// metering: free for testutils
2328#[cfg(any(test, feature = "testutils"))]
2329impl PartialEq for RecordedAuthPayload {
2330    fn eq(&self, other: &Self) -> bool {
2331        self.address == other.address
2332            && self.invocation == other.invocation
2333            // Compare nonces only by presence of the value - recording mode
2334            // generates random nonces.
2335            && self.nonce.is_some() == other.nonce.is_some()
2336    }
2337}