Repository: stellar/rs-soroban-env
Pull request #840 (2023-06-08 16:27:43)

Provide host function for authorizing deep contract calls from current contract
### What

This introduces a host function that allows to authorize sub-contract calls that are deeper than direct call on behalf of the current invoker.

This also includes supporting refactoring of the auth manager to use host objects more. That allows sharing code between different code paths and provides better metering (e.g. we get metered comparisons 'for free' instead of defining XDR metered cmp functionality). This also should be roughly the same in terms of cost.

Also did a bit of passing-by cleanup of related host functions.

### Why

https://github.com/stellar/rs-soroban-env/issues/744

### Known limitations

This uses a `contracttype` as host function argument. We haven't done so before, although we do have some implicit dependencies on `contracttype` arguments in `__check_auth` function. Given that the authorization payload is pretty complex, I decided to go this route instead of introducing a special serialization format just for that function or introducing a new host object kind with several host functions serving it.

This is implemented via a separate host function as opposed to an additional argument for `call`/`try_call` functions. While this is a bit more error-prone (the function only works for the next sub-contract call), it makes things much easier for majority of contracts that don't need this feature. Otherwise, we would need to define 2 more call functions in order to not expose additional complexity to them.



Patch:
diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json
index bf0ade17..19a60916 100644
--- a/soroban-env-common/env.json
+++ b/soroban-env-common/env.json
@@ -69,41 +69,34 @@
                 },
                 {
                     "export": "3",
-                    "name": "get_current_contract_id",
-                    "args": [],
-                    "return": "BytesObject",
-                    "docs": "Gets the 32-byte identifer of the current contract. Traps if the running contract was not invoked by a contract."
-                },
-                {
-                    "export": "4",
                     "name": "get_ledger_version",
                     "args": [],
                     "return": "U32Val",
                     "docs": "Return the protocol version of the current ledger as a u32."
                 },
                 {
-                    "export": "5",
+                    "export": "4",
                     "name": "get_ledger_sequence",
                     "args": [],
                     "return": "U32Val",
                     "docs": "Return the sequence number of the current ledger as a u32."
                 },
                 {
-                    "export": "6",
+                    "export": "5",
                     "name": "get_ledger_timestamp",
                     "args": [],
                     "return": "U64Val",
                     "docs": "Return the timestamp number of the current ledger as a u64."
                 },
                 {
-                    "export": "7",
+                    "export": "6",
                     "name": "get_current_call_stack",
                     "args": [],
                     "return": "VecObject",
                     "docs": "Returns the full call stack from the first contract call to the current one as a vector of vectors, where the inside vector contains the contract id as Hash, and a function as a Symbol."
                 },
                 {
-                    "export": "8",
+                    "export": "7",
                     "name": "fail_with_error",
                     "args": [
                         {
@@ -115,14 +108,14 @@
                     "docs": "Causes the currently executing contract to fail immediately with a provided error code, which must be of error-type `ScErrorType::Contract`. Does not actually return."
                 },
                 {
-                    "export": "9",
+                    "export": "8",
                     "name": "get_ledger_network_id",
                     "args": [],
                     "return": "BytesObject",
                     "docs": "Return the network id (sha256 hash of network passphrase) of the current ledger as `Bytes`. The value is always 32 bytes in length."
                 },
                 {
-                    "export": "a",
+                    "export": "9",
                     "name": "get_current_contract_address",
                     "args": [],
                     "return": "AddressObject",
@@ -1575,7 +1568,7 @@
                             "type": "VecObject"
                         }
                     ],
-                    "return": "RawVal",
+                    "return": "Void",
                     "docs": "Checks if the address has authorized the invocation of the current contract function with the provided arguments. Traps if the invocation hasn't been authorized."
                 },
                 {
@@ -1587,7 +1580,7 @@
                             "type": "AddressObject"
                         }
                     ],
-                    "return": "RawVal",
+                    "return": "Void",
                     "docs": "Checks if the address has authorized the invocation of the current contract function with all the arguments of the invocation. Traps if the invocation hasn't been authorized."
                 },
                 {
@@ -1637,7 +1630,19 @@
                     ],
                     "return": "RawVal",
                     "docs": "Returns the 32-byte contract identifier corresponding to the provided Address object. If the Address doesn't belong to an account, returns RawVal corresponding to the unit type (`()`)."
-                }                
+                },
+                {
+                    "export": "5",
+                    "name": "authorize_as_curr_contract",
+                    "args": [
+                        {
+                            "name": "auth_entires",
+                            "type": "VecObject"
+                        }
+                    ],
+                    "return": "Void",
+                    "docs": "Authorizes sub-contract calls for the next contract call on behalf of the current contract. Every entry in the argument vector corresponds to `InvokerContractAuthEntry` contract type that authorizes a tree of `require_auth` calls on behalf of the current contract. The entries must not contain any authorizations for the direct contract call, i.e. if current contract needs to call contract function F1 that calls function F2 both of which require auth, only F2 should be present in `auth_entries`."
+                }           
             ]
         },
         {
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index 4c9b9793..c71acf2b 100644
--- a/soroban-env-common/src/meta.rs
+++ b/soroban-env-common/src/meta.rs
@@ -42,7 +42,7 @@ pub const ENV_META_V0_SECTION_NAME: &str = "contractenvmetav0";
 
 soroban_env_macros::generate_env_meta_consts!(
     ledger_protocol_version: 20,
-    pre_release_version: 42,
+    pre_release_version: 43,
 );
 
 pub fn get_ledger_protocol_version(interface_version: u64) -> u32 {
diff --git a/soroban-env-common/src/symbol.rs b/soroban-env-common/src/symbol.rs
index 932c8ecd..0286826d 100644
--- a/soroban-env-common/src/symbol.rs
+++ b/soroban-env-common/src/symbol.rs
@@ -457,10 +457,18 @@ impl TryFrom<&SymbolSmall> for ScVal {
 
 #[cfg(feature = "std")]
 impl<E: Env> TryFromVal<E, Symbol> for ScVal {
+    type Error = ConversionError;
+    fn try_from_val(e: &E, s: &Symbol) -> Result<Self, ConversionError> {
+        Ok(ScVal::Symbol(ScSymbol::try_from_val(e, s)?))
+    }
+}
+
+#[cfg(feature = "std")]
+impl<E: Env> TryFromVal<E, Symbol> for ScSymbol {
     type Error = ConversionError;
     fn try_from_val(e: &E, s: &Symbol) -> Result<Self, ConversionError> {
         let sstr = SymbolStr::try_from_val(e, s)?;
-        Ok(ScVal::Symbol(ScSymbol(sstr.0.as_slice().try_into()?)))
+        Ok(ScSymbol(sstr.0.as_slice()[0..sstr.len()].try_into()?))
     }
 }
 
diff --git a/soroban-env-host/src/auth.rs b/soroban-env-host/src/auth.rs
index ef2eb6ed..d5ebafe4 100644
--- a/soroban-env-host/src/auth.rs
+++ b/soroban-env-host/src/auth.rs
@@ -4,22 +4,25 @@ use std::rc::Rc;
 use soroban_env_common::xdr::{
     ContractDataEntry, ContractDataEntryBody, ContractDataEntryData, CreateContractArgs,
     HashIdPreimage, HashIdPreimageSorobanAuthorization, LedgerEntry, LedgerEntryData,
-    LedgerEntryExt, ScAddress, ScErrorCode, ScErrorType, ScNonceKey, ScSymbol, ScVal,
+    LedgerEntryExt, ScAddress, ScErrorCode, ScErrorType, ScNonceKey, ScVal,
     SorobanAuthorizationEntry, SorobanAuthorizedContractFunction, SorobanAuthorizedFunction,
     SorobanCredentials,
 };
-use soroban_env_common::RawVal;
+use soroban_env_common::{
+    AddressObject, Compare, RawVal, Symbol, TryFromVal, TryIntoVal, VecObject,
+};
 
-use crate::budget::Budget;
-use crate::host::metered_clone::MeteredClone;
+use crate::host::metered_clone::{charge_container_bulk_init_with_elts, MeteredClone};
 use crate::host::Frame;
+use crate::host_object::HostVec;
 use crate::native_contract::account_contract::{
     check_account_authentication, check_account_contract_auth,
 };
+use crate::native_contract::invoker_contract_auth::invoker_contract_auth_to_authorized_invocation;
 use crate::{Host, HostError};
 
 use super::xdr;
-use super::xdr::{Hash, ScVec};
+use super::xdr::Hash;
 
 // Authorization manager encapsulates host-based authentication & authorization
 // framework.
@@ -35,11 +38,15 @@ pub struct AuthorizationManager {
     // Per-address trackers of authorized invocations.
     // Every tracker takes care about a single rooted invocation tree for some
     // address. There can be multiple trackers per address.
-    trackers: Vec<AuthorizationTracker>,
+    account_trackers: Vec<AccountAuthorizationTracker>,
+    // Per-address trackers for authorization performed by the contracts at
+    // execution time (as opposed to signature-based authorization for accounts).
+    // Contract authorizations are always enforced independently of the `mode`,
+    // as they are self-contained and fully defined by the contract logic.
+    invoker_contract_trackers: Vec<InvokerContractAuthorizationTracker>,
     // Current call stack consisting only of the contract invocations (i.e. not
     // the host functions).
     call_stack: Vec<AuthStackFrame>,
-    budget: Budget,
 }
 
 // The authorization payload recorded for an address in the recording
@@ -55,10 +62,12 @@ pub struct RecordedAuthPayload {
 // rollbacks.
 #[derive(Clone)]
 pub struct AuthorizationManagerSnapshot {
-    // AuthorizationTracker has some immutable parts, but it is safer to just
-    // rollback everything. If this is an issue, then the AuthorizationTracker should
-    // probably be separated into 'mutable' and 'immutable' parts.
-    trackers: Vec<AuthorizationTracker>,
+    // AccountAuthorizationTracker has some immutable parts, but it is safer to
+    // just rollback everything. If this is an issue, then the
+    // AccountAuthorizationTracker should probably be separated into 'mutable'
+    // and 'immutable' parts.
+    account_trackers: Vec<AccountAuthorizationTracker>,
+    invoker_contract_trackers: Vec<InvokerContractAuthorizationTracker>,
     tracker_by_address_handle: Option<HashMap<u32, usize>>,
 }
 
@@ -79,16 +88,10 @@ struct RecordingAuthInfo {
     tracker_by_address_handle: HashMap<u32, usize>,
 }
 
-// Stores all the authorizations that are authorized by an address.
-// In the enforcing mode this performs authentication and makes sure that only
-// pre-authorized invocations can happen on behalf of the `address`.
-// In the recording mode this will record the invocations that are authorized
-// on behalf of the address.
+// Helper for matching the 'sparse' tree of authorized invocations to the actual
+// call tree.
 #[derive(Clone)]
-struct AuthorizationTracker {
-    // Tracked address. If `None`, then lazily set to the transaction source
-    // account's (i.e. invoker's) address is used.
-    address: Option<ScAddress>,
+struct InvocationTracker {
     // Root of the authorized invocation tree.
     // The authorized invocation tree only contains the contract invocations
     // that explicitly require authorization on behalf of the address.
@@ -101,6 +104,19 @@ struct AuthorizationTracker {
     // parent's `sub_invocations` vector or 0 for the
     // `root_authorized_invocation`.
     invocation_id_in_call_stack: Vec<Option<usize>>,
+}
+
+// Stores all the authorizations that are authorized by an address.
+// In the enforcing mode this performs authentication and makes sure that only
+// pre-authorized invocations can happen on behalf of the `address`.
+// In the recording mode this will record the invocations that are authorized
+// on behalf of the address.
+#[derive(Clone)]
+struct AccountAuthorizationTracker {
+    // Tracked address. If `None`, then lazily set to the transaction source
+    // account's (i.e. invoker's) address is used.
+    address: Option<AddressObject>,
+    invocation_tracker: InvocationTracker,
     // Arguments representing the signature(s) made by the address to authorize
     // the invocations tracked here.
     signature_args: Vec<RawVal>,
@@ -118,6 +134,13 @@ struct AuthorizationTracker {
     nonce: Option<u64>,
 }
 
+// Stores all the authorizations peformed by contracts at runtime.
+#[derive(Clone)]
+struct InvokerContractAuthorizationTracker {
+    contract_address: AddressObject,
+    invocation_tracker: InvocationTracker,
+}
+
 #[derive(Clone)]
 enum AuthStackFrame {
     Contract(ContractInvocation),
@@ -126,18 +149,18 @@ enum AuthStackFrame {
 
 #[derive(Clone)]
 struct ContractInvocation {
-    pub(crate) contract_address: ScAddress,
-    pub(crate) function_name: ScSymbol,
+    pub(crate) contract_address: AddressObject,
+    pub(crate) function_name: Symbol,
 }
 
-#[derive(Clone, Eq, PartialEq)]
+#[derive(Clone)]
 pub(crate) struct ContractFunction {
-    pub(crate) contract_address: ScAddress,
-    pub(crate) function_name: ScSymbol,
-    pub(crate) args: ScVec,
+    pub(crate) contract_address: AddressObject,
+    pub(crate) function_name: Symbol,
+    pub(crate) args: Vec<RawVal>,
 }
 
-#[derive(Clone, Eq, PartialEq)]
+#[derive(Clone)]
 pub(crate) enum AuthorizedFunction {
     ContractFn(ContractFunction),
     CreateContractHostFn(CreateContractArgs),
@@ -156,101 +179,169 @@ pub(crate) struct AuthorizedInvocation {
     is_exhausted: bool,
 }
 
+impl Compare<ContractFunction> for Host {
+    type Error = HostError;
+
+    fn compare(
+        &self,
+        a: &ContractFunction,
+        b: &ContractFunction,
+    ) -> Result<std::cmp::Ordering, Self::Error> {
+        let ord = self.compare(&a.contract_address, &b.contract_address)?;
+        if !ord.is_eq() {
+            return Ok(ord);
+        }
+        let ord = self.compare(&a.function_name, &b.function_name)?;
+        if !ord.is_eq() {
+            return Ok(ord);
+        }
+        self.compare(&a.args, &b.args)
+    }
+}
+
+impl Compare<AuthorizedFunction> for Host {
+    type Error = HostError;
+
+    fn compare(
+        &self,
+        a: &AuthorizedFunction,
+        b: &AuthorizedFunction,
+    ) -> Result<std::cmp::Ordering, Self::Error> {
+        match (a, b) {
+            (AuthorizedFunction::ContractFn(f1), AuthorizedFunction::ContractFn(f2)) => {
+                self.compare(f1, f2)
+            }
+            (
+                AuthorizedFunction::CreateContractHostFn(c1),
+                AuthorizedFunction::CreateContractHostFn(c2),
+            ) => self.compare(c1, c2),
+            (AuthorizedFunction::ContractFn(_), AuthorizedFunction::CreateContractHostFn(_)) => {
+                Ok(std::cmp::Ordering::Less)
+            }
+            (AuthorizedFunction::CreateContractHostFn(_), AuthorizedFunction::ContractFn(_)) => {
+                Ok(std::cmp::Ordering::Greater)
+            }
+        }
+    }
+}
+
 impl AuthStackFrame {
     fn to_authorized_function(
         &self,
-        args: ScVec,
-        budget: &Budget,
+        host: &Host,
+        args: Vec<RawVal>,
     ) -> Result<AuthorizedFunction, HostError> {
         match self {
             AuthStackFrame::Contract(contract_frame) => {
                 Ok(AuthorizedFunction::ContractFn(ContractFunction {
-                    contract_address: contract_frame.contract_address.metered_clone(budget)?,
-                    function_name: contract_frame.function_name.metered_clone(budget)?,
+                    contract_address: contract_frame
+                        .contract_address
+                        .metered_clone(host.budget_ref())?,
+                    function_name: contract_frame
+                        .function_name
+                        .metered_clone(host.budget_ref())?,
                     args,
                 }))
             }
             AuthStackFrame::CreateContractHostFn(args) => Ok(
-                AuthorizedFunction::CreateContractHostFn(args.metered_clone(budget)?),
+                AuthorizedFunction::CreateContractHostFn(args.metered_clone(host.budget_ref())?),
             ),
         }
     }
 }
 
 impl AuthorizedFunction {
-    fn from_xdr(xdr_fn: SorobanAuthorizedFunction) -> Self {
-        match xdr_fn {
+    fn from_xdr(host: &Host, xdr_fn: SorobanAuthorizedFunction) -> Result<Self, HostError> {
+        Ok(match xdr_fn {
             SorobanAuthorizedFunction::ContractFn(xdr_contract_fn) => {
                 AuthorizedFunction::ContractFn(ContractFunction {
-                    contract_address: xdr_contract_fn.contract_address,
-                    function_name: xdr_contract_fn.function_name,
-                    args: xdr_contract_fn.args,
+                    contract_address: host.add_host_object(xdr_contract_fn.contract_address)?,
+                    function_name: Symbol::try_from_val(host, &xdr_contract_fn.function_name)?,
+                    args: host.scvals_to_rawvals(xdr_contract_fn.args.as_slice())?,
                 })
             }
             SorobanAuthorizedFunction::CreateContractHostFn(xdr_args) => {
                 AuthorizedFunction::CreateContractHostFn(xdr_args)
             }
-        }
+        })
     }
 
-    fn to_xdr(&self, budget: &Budget) -> Result<SorobanAuthorizedFunction, HostError> {
+    fn to_xdr(&self, host: &Host) -> Result<SorobanAuthorizedFunction, HostError> {
         match self {
-            AuthorizedFunction::ContractFn(contract_fn) => Ok(
-                SorobanAuthorizedFunction::ContractFn(SorobanAuthorizedContractFunction {
-                    contract_address: contract_fn.contract_address.metered_clone(budget)?,
-                    function_name: contract_fn.function_name.metered_clone(budget)?,
-                    args: contract_fn.args.metered_clone(budget)?,
-                }),
-            ),
+            AuthorizedFunction::ContractFn(contract_fn) => {
+                let function_name_sc_val = host.from_host_val(contract_fn.function_name.into())?;
+                let function_name = if let ScVal::Symbol(s) = function_name_sc_val {
+                    s
+                } else {
+                    return Err(host.err(
+                        ScErrorType::Object,
+                        ScErrorCode::InternalError,
+                        "unexpected non-symbol function name",
+                        &[],
+                    ));
+                };
+                Ok(SorobanAuthorizedFunction::ContractFn(
+                    SorobanAuthorizedContractFunction {
+                        contract_address: host
+                            .scaddress_from_address(contract_fn.contract_address)?,
+                        function_name,
+                        args: host.rawvals_to_scvec(contract_fn.args.as_slice())?,
+                    },
+                ))
+            }
             AuthorizedFunction::CreateContractHostFn(create_contract_args) => {
                 Ok(SorobanAuthorizedFunction::CreateContractHostFn(
-                    create_contract_args.metered_clone(budget)?,
+                    create_contract_args.metered_clone(host.budget_ref())?,
                 ))
             }
         }
     }
 
-    fn to_xdr_non_metered(&self) -> xdr::SorobanAuthorizedFunction {
+    fn to_xdr_non_metered(&self, host: &Host) -> Result<xdr::SorobanAuthorizedFunction, HostError> {
         match self {
-            AuthorizedFunction::ContractFn(contract_fn) => {
+            AuthorizedFunction::ContractFn(contract_fn) => Ok(
                 SorobanAuthorizedFunction::ContractFn(SorobanAuthorizedContractFunction {
-                    contract_address: contract_fn.contract_address.clone(),
-                    function_name: contract_fn.function_name.clone(),
-                    args: contract_fn.args.clone(),
-                })
-            }
-            AuthorizedFunction::CreateContractHostFn(create_contract_args) => {
-                SorobanAuthorizedFunction::CreateContractHostFn(create_contract_args.clone())
-            }
+                    contract_address: host
+                        .visit_obj(contract_fn.contract_address, |addr: &ScAddress| {
+                            Ok(addr.clone())
+                        })?,
+                    function_name: contract_fn.function_name.try_into_val(host)?,
+                    args: host.rawvals_to_scvec_non_metered(contract_fn.args.as_slice())?,
+                }),
+            ),
+            AuthorizedFunction::CreateContractHostFn(create_contract_args) => Ok(
+                SorobanAuthorizedFunction::CreateContractHostFn(create_contract_args.clone()),
+            ),
         }
     }
 }
 
 impl AuthorizedInvocation {
-    fn from_xdr(xdr_invocation: xdr::SorobanAuthorizedInvocation) -> Result<Self, HostError> {
+    fn from_xdr(
+        host: &Host,
+        xdr_invocation: xdr::SorobanAuthorizedInvocation,
+    ) -> Result<Self, HostError> {
         let sub_invocations_xdr = xdr_invocation.sub_invocations.into_vec();
         let sub_invocations = sub_invocations_xdr
             .into_iter()
-            .map(AuthorizedInvocation::from_xdr)
+            .map(|a| AuthorizedInvocation::from_xdr(host, a))
             .collect::<Result<Vec<_>, _>>()?;
         Ok(Self {
-            function: AuthorizedFunction::from_xdr(xdr_invocation.function),
+            function: AuthorizedFunction::from_xdr(host, xdr_invocation.function)?,
             sub_invocations,
             is_exhausted: false,
         })
     }
 
-    fn to_xdr(&self, budget: &Budget) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
-        Ok(xdr::SorobanAuthorizedInvocation {
-            function: self.function.to_xdr(budget)?,
-            sub_invocations: self
-                .sub_invocations
-                .iter()
-                .map(|i| i.to_xdr(budget))
-                .collect::<Result<Vec<xdr::SorobanAuthorizedInvocation>, HostError>>()?
-                .try_into()
-                .map_err(|_| HostError::from((ScErrorType::Auth, ScErrorCode::InternalError)))?,
-        })
+    pub(crate) fn new(
+        function: AuthorizedFunction,
+        sub_invocations: Vec<AuthorizedInvocation>,
+    ) -> Self {
+        Self {
+            function,
+            sub_invocations,
+            is_exhausted: false,
+        }
     }
 
     fn new_recording(function: AuthorizedFunction) -> Self {
@@ -261,19 +352,37 @@ impl AuthorizedInvocation {
         }
     }
 
+    fn to_xdr(&self, host: &Host) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
+        charge_container_bulk_init_with_elts::<
+            Vec<xdr::SorobanAuthorizedInvocation>,
+            xdr::SorobanAuthorizedInvocation,
+        >(self.sub_invocations.len() as u64, host.budget_ref())?;
+        Ok(xdr::SorobanAuthorizedInvocation {
+            function: self.function.to_xdr(host)?,
+            sub_invocations: self
+                .sub_invocations
+                .iter()
+                .map(|i| i.to_xdr(host))
+                .collect::<Result<Vec<xdr::SorobanAuthorizedInvocation>, HostError>>()?
+                .try_into()
+                .map_err(|_| HostError::from((ScErrorType::Auth, ScErrorCode::InternalError)))?,
+        })
+    }
+
     // Non-metered conversion should only be used for the recording preflight
     // runs or testing.
     fn to_xdr_non_metered(
         &self,
+        host: &Host,
         exhausted_sub_invocations_only: bool,
     ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
         Ok(xdr::SorobanAuthorizedInvocation {
-            function: self.function.to_xdr_non_metered(),
+            function: self.function.to_xdr_non_metered(host)?,
             sub_invocations: self
                 .sub_invocations
                 .iter()
                 .filter(|i| i.is_exhausted || !exhausted_sub_invocations_only)
-                .map(|i| i.to_xdr_non_metered(exhausted_sub_invocations_only))
+                .map(|i| i.to_xdr_non_metered(host, exhausted_sub_invocations_only))
                 .collect::<Result<Vec<xdr::SorobanAuthorizedInvocation>, HostError>>()?
                 .try_into()
                 .map_err(|_| HostError::from((ScErrorType::Auth, ScErrorCode::InternalError)))?,
@@ -306,7 +415,7 @@ impl AuthorizedInvocation {
 
 impl Default for AuthorizationManager {
     fn default() -> Self {
-        Self::new_enforcing_without_authorizations(Budget::default())
+        Self::new_enforcing_without_authorizations()
     }
 }
 
@@ -320,41 +429,41 @@ impl AuthorizationManager {
     ) -> Result<Self, HostError> {
         let mut trackers = vec![];
         for auth_entry in auth_entries {
-            trackers.push(AuthorizationTracker::from_authorization_entry(
+            trackers.push(AccountAuthorizationTracker::from_authorization_entry(
                 host, auth_entry,
             )?);
         }
         Ok(Self {
             mode: AuthorizationMode::Enforcing,
             call_stack: vec![],
-            budget: host.budget_cloned(),
-            trackers,
+            account_trackers: trackers,
+            invoker_contract_trackers: vec![],
         })
     }
 
     // Creates a new enforcing `AuthorizationManager` that doesn't allow any
     // authorizations.
     // This is useful as a safe default mode.
-    pub(crate) fn new_enforcing_without_authorizations(budget: Budget) -> Self {
+    pub(crate) fn new_enforcing_without_authorizations() -> Self {
         Self {
             mode: AuthorizationMode::Enforcing,
             call_stack: vec![],
-            budget,
-            trackers: vec![],
+            account_trackers: vec![],
+            invoker_contract_trackers: vec![],
         }
     }
 
     // Creates a new recording `AuthorizationManager`.
     // All the authorization requirements will be recorded and can then be
     // retrieved using `get_recorded_auth_payloads`.
-    pub(crate) fn new_recording(budget: Budget) -> Self {
+    pub(crate) fn new_recording() -> Self {
         Self {
             mode: AuthorizationMode::Recording(RecordingAuthInfo {
                 tracker_by_address_handle: Default::default(),
             }),
             call_stack: vec![],
-            budget,
-            trackers: vec![],
+            account_trackers: vec![],
+            invoker_contract_trackers: vec![],
         }
     }
 
@@ -366,62 +475,101 @@ impl AuthorizationManager {
     pub(crate) fn require_auth(
         &mut self,
         host: &Host,
-        address_obj_handle: u32,
-        address: ScAddress,
-        args: ScVec,
+        address: AddressObject,
+        args: Vec<RawVal>,
     ) -> Result<(), HostError> {
         let curr_invocation = self.call_stack.last().ok_or_else(|| {
             host.err(
                 ScErrorType::Auth,
                 ScErrorCode::InternalError,
-                "unexpected require_auth outside of contract",
+                "unexpected require_auth outside of valid frame",
                 &[],
             )
         })?;
         self.require_auth_internal(
             host,
-            address_obj_handle,
             address,
-            curr_invocation.to_authorized_function(args, &self.budget)?,
+            curr_invocation.to_authorized_function(host, args)?,
         )
     }
 
-    fn verify_contract_invoker_auth(&self, contract_id: &ScAddress) -> bool {
+    pub(crate) fn add_invoker_contract_auth(
+        &mut self,
+        host: &Host,
+        auth_entries: VecObject,
+    ) -> Result<(), HostError> {
+        let auth_entries =
+            host.visit_obj(auth_entries, |e: &HostVec| e.to_vec(host.budget_ref()))?;
+        for e in auth_entries {
+            self.invoker_contract_trackers
+                .push(InvokerContractAuthorizationTracker::new(host, e)?)
+        }
+        Ok(())
+    }
+
+    fn verify_contract_invoker_auth(
+        &mut self,
+        host: &Host,
+        address: AddressObject,
+        function: &AuthorizedFunction,
+    ) -> Result<bool, HostError> {
+        // If stack has just one call there can't be invoker.
         if self.call_stack.len() < 2 {
-            return false;
+            return Ok(false);
         }
+
+        // Try matching the direct invoker contract first. It is considered to
+        // have authorized any direct calls.
         let invoker_frame = &self.call_stack[self.call_stack.len() - 2];
         if let AuthStackFrame::Contract(invoker_contract) = invoker_frame {
-            return contract_id == &invoker_contract.contract_address;
+            if host
+                .compare(&invoker_contract.contract_address, &address)?
+                .is_eq()
+            {
+                return Ok(true);
+            }
+        }
+        // If there is no direct invoker, there still might be a valid
+        // sub-contract call authorization from another invoker higher up the
+        // stack. Note, that invoker contract trackers consider the direct frame
+        // to never require auth (any `require_auth` calls would be matched by
+        // logic above).
+        for tracker in &mut self.invoker_contract_trackers {
+            if host.compare(&tracker.contract_address, &address)?.is_eq()
+                && tracker.maybe_authorize_invocation(host, function)?
+            {
+                return Ok(true);
+            }
         }
-        return false;
+
+        return Ok(false);
     }
 
     fn require_auth_enforcing(
         &mut self,
         host: &Host,
-        address: &ScAddress,
+        address: AddressObject,
         function: &AuthorizedFunction,
     ) -> Result<(), HostError> {
         // Iterate all the trackers and try to find one that
         // fullfills the authorization requirement.
-        for tracker in &mut self.trackers {
+        for tracker in &mut self.account_trackers {
             let address_matches = if let Some(addr) = &tracker.address {
-                addr == address
+                host.compare(addr, &address)?.is_eq()
             } else {
                 // Lazily fill the address for the invoker trackers,
                 // so that it's possible to create the auth manager
                 // without knowing the invoker beforehand and also
                 // to not keep calling into `source_account` function.
-                let source_addr = ScAddress::Account(host.source_account().ok_or_else(|| {
+                let source_addr = host.source_account_address()?.ok_or_else(|| {
                     host.err(
                         ScErrorType::Auth,
                         ScErrorCode::InternalError,
                         "unexpected missing invoker in auth manager",
                         &[],
                     )
-                })?);
-                let source_matches = &source_addr == address;
+                })?;
+                let source_matches = host.compare(&source_addr, &address)?.is_eq();
                 tracker.address = Some(source_addr);
                 source_matches
             };
@@ -453,26 +601,26 @@ impl AuthorizationManager {
     fn require_auth_internal(
         &mut self,
         host: &Host,
-        address_obj_handle: u32,
-        address: ScAddress,
+        address: AddressObject,
         function: AuthorizedFunction,
     ) -> Result<(), HostError> {
         // For now we give a blanket approval of the invoker contract to any
         // calls it made, but never to the deeper calls. It's possible
         // to eventually add a capability to pre-authorize arbitrary call
         // stacks on behalf of the contract.
-        if self.verify_contract_invoker_auth(&address) {
+        if self.verify_contract_invoker_auth(host, address, &function)? {
             return Ok(());
         }
 
         match &mut self.mode {
-            AuthorizationMode::Enforcing => self.require_auth_enforcing(host, &address, &function),
+            AuthorizationMode::Enforcing => self.require_auth_enforcing(host, address, &function),
             AuthorizationMode::Recording(recording_info) => {
+                let address_obj_handle = address.get_handle();
                 if let Some(tracker_id) = recording_info
                     .tracker_by_address_handle
                     .get(&address_obj_handle)
                 {
-                    let tracker = &mut self.trackers[*tracker_id];
+                    let tracker = &mut self.account_trackers[*tracker_id];
                     // The recording invariant is that trackers are created
                     // with the first authorized invocation, which means
                     // that when their stack no longer has authorized
@@ -483,20 +631,22 @@ impl AuthorizationManager {
                             .tracker_by_address_handle
                             .remove(&address_obj_handle);
                     } else {
-                        return self.trackers[*tracker_id].record_invocation(host, function);
+                        return self.account_trackers[*tracker_id]
+                            .record_invocation(host, function);
                     }
                 }
                 // If a tracker for the new tree doesn't exist yet, create
                 // it and initialize with the current invocation.
-                self.trackers.push(AuthorizationTracker::new_recording(
-                    host,
-                    address,
-                    function,
-                    self.call_stack.len(),
-                )?);
+                self.account_trackers
+                    .push(AccountAuthorizationTracker::new_recording(
+                        host,
+                        address,
+                        function,
+                        self.call_stack.len(),
+                    )?);
                 recording_info
                     .tracker_by_address_handle
-                    .insert(address_obj_handle, self.trackers.len() - 1);
+                    .insert(address_obj_handle, self.account_trackers.len() - 1);
                 Ok(())
             }
         }
@@ -511,14 +661,16 @@ impl AuthorizationManager {
             }
         };
         AuthorizationManagerSnapshot {
-            trackers: self.trackers.clone(),
+            account_trackers: self.account_trackers.clone(),
+            invoker_contract_trackers: self.invoker_contract_trackers.clone(),
             tracker_by_address_handle,
         }
     }
 
     // Rolls back this `AuthorizationManager` to the snapshot state.
     pub(crate) fn rollback(&mut self, snapshot: AuthorizationManagerSnapshot) {
-        self.trackers = snapshot.trackers;
+        self.account_trackers = snapshot.account_trackers;
+        self.invoker_contract_trackers = snapshot.invoker_contract_trackers;
         if let Some(tracker_by_address_handle) = snapshot.tracker_by_address_handle {
             match &mut self.mode {
                 AuthorizationMode::Recording(recording_info) => {
@@ -532,7 +684,7 @@ impl AuthorizationManager {
     pub(crate) fn push_create_contract_host_fn_frame(&mut self, args: CreateContractArgs) {
         self.call_stack
             .push(AuthStackFrame::CreateContractHostFn(args));
-        for tracker in &mut self.trackers {
+        for tracker in &mut self.account_trackers {
             tracker.push_frame();
         }
     }
@@ -542,7 +694,7 @@ impl AuthorizationManager {
     pub(crate) fn push_frame(&mut self, host: &Host, frame: &Frame) -> Result<(), HostError> {
         let (contract_id, function_name) = match frame {
             Frame::ContractVM(vm, fn_name, _) => {
-                (vm.contract_id.metered_clone(&self.budget)?, *fn_name)
+                (vm.contract_id.metered_clone(host.budget_ref())?, *fn_name)
             }
             // Skip the top-level host function stack frames as they don't
             // contain all the necessary information.
@@ -550,21 +702,21 @@ impl AuthorizationManager {
             // `push_create_contract_host_fn_frame`) functions instead to push
             // the frame with the required info.
             Frame::HostFunction(_) => return Ok(()),
-            Frame::Token(id, fn_name, _) => (id.metered_clone(&self.budget)?, *fn_name),
+            Frame::Token(id, fn_name, _) => (id.metered_clone(host.budget_ref())?, *fn_name),
             #[cfg(any(test, feature = "testutils"))]
             Frame::TestContract(tc) => (tc.id.clone(), tc.func),
         };
         // Currently only contracts might appear in the call stack.
-        let contract_address = ScAddress::Contract(contract_id);
-        let Ok(ScVal::Symbol(function_name)) = host.from_host_val(function_name.to_raw()) else {
-            return Err(host.err(ScErrorType::Auth, ScErrorCode::InternalError, "frame function name conversion failed", &[]))
-        };
+        let contract_address = host.add_host_object(ScAddress::Contract(contract_id))?;
         self.call_stack
             .push(AuthStackFrame::Contract(ContractInvocation {
                 contract_address,
                 function_name,
             }));
-        for tracker in &mut self.trackers {
+        for tracker in &mut self.account_trackers {
+            tracker.push_frame();
+        }
+        for tracker in &mut self.invoker_contract_trackers {
             tracker.push_frame();
         }
         Ok(())
@@ -582,24 +734,37 @@ impl AuthorizationManager {
             return;
         }
         self.call_stack.pop();
-        for tracker in &mut self.trackers {
+        for tracker in &mut self.account_trackers {
             tracker.pop_frame();
         }
+
+        let mut i = 0;
+        while i < self.invoker_contract_trackers.len() {
+            self.invoker_contract_trackers[i].pop_frame();
+            if self.invoker_contract_trackers[i].is_out_of_scope() {
+                self.invoker_contract_trackers.swap_remove(i);
+            } else {
+                i += 1;
+            }
+        }
     }
 
     // Returns the recorded per-address authorization payloads that would cover the
     // top-level contract function invocation in the enforcing mode.
     // Should only be called in the recording mode.
-    pub(crate) fn get_recorded_auth_payloads(&self) -> Result<Vec<RecordedAuthPayload>, HostError> {
+    pub(crate) fn get_recorded_auth_payloads(
+        &self,
+        host: &Host,
+    ) -> Result<Vec<RecordedAuthPayload>, HostError> {
         match &self.mode {
             AuthorizationMode::Enforcing => Err(HostError::from((
                 ScErrorType::Auth,
                 ScErrorCode::InternalError,
             ))),
             AuthorizationMode::Recording(_) => Ok(self
-                .trackers
+                .account_trackers
                 .iter()
-                .map(|tracker| tracker.get_recorded_auth_payload())
+                .map(|tracker| tracker.get_recorded_auth_payload(host))
                 .collect::<Result<Vec<RecordedAuthPayload>, HostError>>()?),
         }
     }
@@ -613,7 +778,7 @@ impl AuthorizationManager {
         match &self.mode {
             AuthorizationMode::Enforcing => Ok(()),
             AuthorizationMode::Recording(_) => {
-                for tracker in &self.trackers {
+                for tracker in &self.account_trackers {
                     tracker.emulate_authentication(host)?;
                 }
                 Ok(())
@@ -627,11 +792,9 @@ impl AuthorizationManager {
     pub(crate) fn reset(&mut self) {
         *self = match self.mode {
             AuthorizationMode::Enforcing => {
-                AuthorizationManager::new_enforcing_without_authorizations(self.budget.clone())
-            }
-            AuthorizationMode::Recording(_) => {
-                AuthorizationManager::new_recording(self.budget.clone())
+                AuthorizationManager::new_enforcing_without_authorizations()
             }
+            AuthorizationMode::Recording(_) => AuthorizationManager::new_recording(),
         }
     }
 
@@ -640,10 +803,11 @@ impl AuthorizationManager {
     #[cfg(any(test, feature = "testutils"))]
     pub(crate) fn get_authenticated_authorizations(
         &self,
+        host: &Host,
     ) -> Vec<(ScAddress, xdr::SorobanAuthorizedInvocation)> {
-        self.trackers
+        self.account_trackers
             .iter()
-            .filter(|t| t.authenticated && t.root_authorized_invocation.is_exhausted)
+            .filter(|t| t.authenticated && t.is_exhausted())
             .filter_map(|t| {
                 // Ignore authorizations without an address as they are implied,
                 // less useful as a test utility, and not succinctly capturable
@@ -653,9 +817,10 @@ impl AuthorizationManager {
                 // to use.
                 t.address.as_ref().map(|a| {
                     (
-                        a.clone(),
-                        t.root_authorized_invocation
-                            .to_xdr_non_metered(true)
+                        host.scaddress_from_address(*a).unwrap(),
+                        t.invocation_tracker
+                            .root_authorized_invocation
+                            .to_xdr_non_metered(host, true)
                             .unwrap(),
                     )
                 })
@@ -664,7 +829,150 @@ impl AuthorizationManager {
     }
 }
 
-impl AuthorizationTracker {
+impl InvocationTracker {
+    fn from_xdr(
+        host: &Host,
+        root_invocation: xdr::SorobanAuthorizedInvocation,
+    ) -> Result<Self, HostError> {
+        Ok(Self {
+            root_authorized_invocation: AuthorizedInvocation::from_xdr(host, root_invocation)?,
+            invocation_id_in_call_stack: vec![],
+        })
+    }
+
+    fn new(root_authorized_invocation: AuthorizedInvocation) -> Self {
+        Self {
+            root_authorized_invocation,
+            invocation_id_in_call_stack: vec![],
+        }
+    }
+
+    fn new_recording(function: AuthorizedFunction, current_stack_len: usize) -> Self {
+        // Create the stack of `None` leading to the current invocation to
+        // represent invocations that didn't need authorization on behalf of
+        // the tracked address.
+        let mut invocation_id_in_call_stack = vec![None; current_stack_len - 1];
+        // Add the id for the current(root) invocation.
+        invocation_id_in_call_stack.push(Some(0));
+        Self {
+            root_authorized_invocation: AuthorizedInvocation::new_recording(function),
+            invocation_id_in_call_stack,
+        }
+    }
+
+    // Walks a path in the tree defined by `invocation_id_in_call_stack` and
+    // returns the last visited authorized node.
+    fn last_authorized_invocation_mut(&mut self) -> Option<&mut AuthorizedInvocation> {
+        for i in 0..self.invocation_id_in_call_stack.len() {
+            if self.invocation_id_in_call_stack[i].is_some() {
+                return Some(
+                    self.root_authorized_invocation
+                        .last_authorized_invocation_mut(&self.invocation_id_in_call_stack, i + 1),
+                );
+            }
+        }
+        None
+    }
+
+    fn push_frame(&mut self) {
+        self.invocation_id_in_call_stack.push(None);
+    }
+
+    fn pop_frame(&mut self) {
+        self.invocation_id_in_call_stack.pop();
+    }
+
+    fn is_empty(&self) -> bool {
+        self.invocation_id_in_call_stack.is_empty()
+    }
+
+    // Tries to match the provided invocation to the authorized sub-invocation
+    // of the current tree and push it to the call stack.
+    // Returns `true` if the match has been found for the first time per current
+    // frame.
+    fn maybe_push_matching_invocation_frame(
+        &mut self,
+        host: &Host,
+        function: &AuthorizedFunction,
+    ) -> Result<bool, HostError> {
+        let frame_is_already_matched = match self.invocation_id_in_call_stack.last() {
+            Some(Some(_)) => true,
+            _ => false,
+        };
+        if frame_is_already_matched {
+            return Ok(false);
+        }
+        let mut frame_index = None;
+        if let Some(curr_invocation) = self.last_authorized_invocation_mut() {
+            for i in 0..curr_invocation.sub_invocations.len() {
+                let sub_invocation = &mut curr_invocation.sub_invocations[i];
+                if !sub_invocation.is_exhausted
+                    && host.compare(&sub_invocation.function, function)?.is_eq()
+                {
+                    frame_index = Some(i);
+                    sub_invocation.is_exhausted = true;
+                    break;
+                }
+            }
+        } else if !self.root_authorized_invocation.is_exhausted
+            && host
+                .compare(&self.root_authorized_invocation.function, &function)?
+                .is_eq()
+        {
+            frame_index = Some(0);
+            self.root_authorized_invocation.is_exhausted = true;
+        }
+        if frame_index.is_some() {
+            *self.invocation_id_in_call_stack.last_mut().unwrap() = frame_index;
+        }
+        Ok(frame_index.is_some())
+    }
+
+    // Records the invocation in this tracker.
+    // This is needed for the recording mode only.
+    // This assumes that the address matching is correctly performed before
+    // calling this.
+    fn record_invocation(
+        &mut self,
+        host: &Host,
+        function: AuthorizedFunction,
+    ) -> Result<(), HostError> {
+        let frame_is_already_authorized = match self.invocation_id_in_call_stack.last() {
+            Some(Some(_)) => true,
+            _ => false,
+        };
+        if frame_is_already_authorized {
+            return Err(host.err(
+                ScErrorType::Auth,
+                ScErrorCode::ExistingValue,
+                "frame is already authorized",
+                &[],
+            ));
+        }
+        if let Some(curr_invocation) = self.last_authorized_invocation_mut() {
+            curr_invocation
+                .sub_invocations
+                .push(AuthorizedInvocation::new_recording(function));
+            *self.invocation_id_in_call_stack.last_mut().unwrap() =
+                Some(curr_invocation.sub_invocations.len() - 1);
+        } else {
+            // This would be a bug
+            return Err(host.err(
+                ScErrorType::Auth,
+                ScErrorCode::InternalError,
+                "unexpected missing authorized invocation",
+                &[],
+            ));
+        }
+        Ok(())
+    }
+
+    fn has_matched_invocations_in_stack(&self) -> bool {
+        self.invocation_id_in_call_stack.iter().any(|i| i.is_some())
+    }
+}
+
+impl AccountAuthorizationTracker {
     fn from_authorization_entry(
         host: &Host,
         auth_entry: SorobanAuthorizationEntry,
@@ -672,7 +980,7 @@ impl AuthorizationTracker {
         let (address, nonce, signature_args) = match auth_entry.credentials {
             SorobanCredentials::SourceAccount => (None, None, vec![]),
             SorobanCredentials::Address(address_creds) => (
-                Some(address_creds.address),
+                Some(host.add_host_object(address_creds.address)?),
                 Some(address_creds.nonce),
                 host.scvals_to_rawvals(address_creds.signature_args.0.as_slice())?,
             ),
@@ -680,20 +988,19 @@ impl AuthorizationTracker {
         let is_invoker = address.is_none();
         Ok(Self {
             address,
-            root_authorized_invocation: AuthorizedInvocation::from_xdr(auth_entry.root_invocation)?,
+            invocation_tracker: InvocationTracker::from_xdr(host, auth_entry.root_invocation)?,
             signature_args,
             authenticated: false,
             need_nonce: !is_invoker,
             is_invoker,
             nonce,
-            invocation_id_in_call_stack: vec![],
             is_valid: true,
         })
     }
 
     fn new_recording(
         host: &Host,
-        address: ScAddress,
+        address: AddressObject,
         function: AuthorizedFunction,
         current_stack_len: usize,
     ) -> Result<Self, HostError> {
@@ -709,8 +1016,8 @@ impl AuthorizationTracker {
         // If the invoker account is known, set it to `None`, so that the final
         // recorded payload wouldn't contain the address. This makes it easier
         // to use more optimal payload when only invoker auth is used.
-        let is_invoker = if let Some(source_acc) = host.source_account() {
-            ScAddress::Account(source_acc) == address
+        let is_invoker = if let Some(source_acc) = host.source_account_address()? {
+            host.compare(&source_acc, &address)?.is_eq()
         } else {
             false
         };
@@ -727,8 +1034,7 @@ impl AuthorizationTracker {
         invocation_id_in_call_stack.push(Some(0));
         Ok(Self {
             address: Some(address),
-            root_authorized_invocation: AuthorizedInvocation::new_recording(function),
-            invocation_id_in_call_stack,
+            invocation_tracker: InvocationTracker::new_recording(function, current_stack_len),
             signature_args: Default::default(),
             is_valid: true,
             authenticated: true,
@@ -754,11 +1060,10 @@ impl AuthorizationTracker {
         if !self.is_valid {
             return Ok(false);
         }
-        let frame_is_already_authorized = match self.invocation_id_in_call_stack.last() {
-            Some(Some(_)) => true,
-            _ => false,
-        };
-        if frame_is_already_authorized || !self.maybe_push_matching_invocation_frame(function) {
+        if !self
+            .invocation_tracker
+            .maybe_push_matching_invocation_frame(host, function)?
+        {
             // The call isn't found in the currently tracked tree or is already
             // authorized in it.
             // That doesn't necessarily mean it's unauthorized (it can be
@@ -802,46 +1107,30 @@ impl AuthorizationTracker {
         host: &Host,
         function: AuthorizedFunction,
     ) -> Result<(), HostError> {
-        let frame_is_already_authorized = match self.invocation_id_in_call_stack.last() {
-            Some(Some(_)) => true,
-            _ => false,
-        };
-        if frame_is_already_authorized {
-            return Err(host.err(
-                ScErrorType::Auth,
-                ScErrorCode::ExistingValue,
-                "frame is already authorized",
-                &[],
-            ));
-        }
-        if let Some(curr_invocation) = self.last_authorized_invocation_mut() {
-            curr_invocation
-                .sub_invocations
-                .push(AuthorizedInvocation::new_recording(function));
-            *self.invocation_id_in_call_stack.last_mut().unwrap() =
-                Some(curr_invocation.sub_invocations.len() - 1);
-        } else {
-            // This would be a bug
-            return Err(host.err(
-                ScErrorType::Auth,
-                ScErrorCode::InternalError,
-                "unexpected missing authorized invocation",
-                &[],
-            ));
-        }
-        Ok(())
+        self.invocation_tracker.record_invocation(host, function)
     }
 
     // Build the authorization payload from the invocations recorded in this
     // tracker.
-    fn get_recorded_auth_payload(&self) -> Result<RecordedAuthPayload, HostError> {
+    fn get_recorded_auth_payload(&self, host: &Host) -> Result<RecordedAuthPayload, HostError> {
         Ok(RecordedAuthPayload {
-            address: if self.is_invoker {
-                None
+            address: if let Some(addr) = self.address {
+                Some(host.visit_obj(addr, |a: &ScAddress| Ok(a.clone()))?)
             } else {
-                self.address.clone()
+                if !self.is_invoker {
+                    return Err(host.err(
+                        ScErrorType::Auth,
+                        ScErrorCode::InternalError,
+                        "unexpected missing address for non-invoker",
+                        &[],
+                    ));
+                }
+                None
             },
-            invocation: self.root_authorized_invocation.to_xdr_non_metered(false)?,
+            invocation: self
+                .invocation_tracker
+                .root_authorized_invocation
+                .to_xdr_non_metered(host, false)?,
             nonce: self.nonce,
         })
     }
@@ -849,22 +1138,24 @@ impl AuthorizationTracker {
     // Checks if there is at least one authorized invocation in the current call
     // stack.
     fn has_authorized_invocations_in_stack(&self) -> bool {
-        self.invocation_id_in_call_stack.iter().any(|i| i.is_some())
+        self.invocation_tracker.has_matched_invocations_in_stack()
     }
 
-    fn invocation_to_xdr(
+    fn root_invocation_to_xdr(
         &self,
-        budget: &Budget,
+        host: &Host,
     ) -> Result<xdr::SorobanAuthorizedInvocation, HostError> {
-        self.root_authorized_invocation.to_xdr(budget)
+        self.invocation_tracker
+            .root_authorized_invocation
+            .to_xdr(host)
     }
 
     fn push_frame(&mut self) {
-        self.invocation_id_in_call_stack.push(None);
+        self.invocation_tracker.push_frame();
     }
 
     fn pop_frame(&mut self) {
-        self.invocation_id_in_call_stack.pop();
+        self.invocation_tracker.pop_frame();
     }
 
     // Consumes nonce if the nonce check is still needed.
@@ -878,55 +1169,13 @@ impl AuthorizationTracker {
         if let Some(addr) = &self.address {
             Ok(Some(host.read_and_consume_nonce(
                 addr.metered_clone(host.budget_ref())?,
-                &self.root_authorized_invocation.function,
+                &self.invocation_tracker.root_authorized_invocation.function,
             )?))
         } else {
             Ok(None)
         }
     }
 
-    // Walks a path in the tree defined by `invocation_id_in_call_stack` and
-    // returns the last visited authorized node.
-    fn last_authorized_invocation_mut(&mut self) -> Option<&mut AuthorizedInvocation> {
-        for i in 0..self.invocation_id_in_call_stack.len() {
-            if self.invocation_id_in_call_stack[i].is_some() {
-                return Some(
-                    self.root_authorized_invocation
-                        .last_authorized_invocation_mut(&self.invocation_id_in_call_stack, i + 1),
-                );
-            }
-        }
-        None
-    }
-
-    // Tries to match the provided invocation to the authorized sub-invocation
-    // of the current tree and push it to the call stack.
-    // Returns `true` if the match has been found.
-    fn maybe_push_matching_invocation_frame(&mut self, function: &AuthorizedFunction) -> bool {
-        let mut frame_index = None;
-        if let Some(curr_invocation) = self.last_authorized_invocation_mut() {
-            for i in 0..curr_invocation.sub_invocations.len() {
-                let sub_invocation = &mut curr_invocation.sub_invocations[i];
-                // TODO: the equality comparison should be metered.
-                if !sub_invocation.is_exhausted && &sub_invocation.function == function {
-                    frame_index = Some(i);
-                    sub_invocation.is_exhausted = true;
-                    break;
-                }
-            }
-        } else if !self.root_authorized_invocation.is_exhausted
-            // TODO: the equality comparison should be metered.
-            && &self.root_authorized_invocation.function == function
-        {
-            frame_index = Some(0);
-            self.root_authorized_invocation.is_exhausted = true;
-        }
-        if frame_index.is_some() {
-            *self.invocation_id_in_call_stack.last_mut().unwrap() = frame_index;
-        }
-        frame_index.is_some()
-    }
-
     fn verify_nonce(&mut self, host: &Host) -> Result<(), HostError> {
         let nonce_is_correct = if let Some(nonce) = self.maybe_consume_nonce(host)? {
             if let Some(tracker_nonce) = self.nonce {
@@ -960,7 +1209,7 @@ impl AuthorizationTracker {
                 network_id: Hash(
                     host.with_ledger_info(|li| li.network_id.metered_clone(host.budget_ref()))?,
                 ),
-                invocation: self.invocation_to_xdr(host.budget_ref())?,
+                invocation: self.root_invocation_to_xdr(host)?,
                 nonce: self.nonce.ok_or_else(|| {
                     host.err(
                         ScErrorType::Auth,
@@ -978,21 +1227,23 @@ impl AuthorizationTracker {
         if self.is_invoker {
             return Ok(());
         }
+
         if let Some(address) = &self.address {
+            let sc_addr = host.scaddress_from_address(*address)?;
             // TODO: there should also be a mode where a dummy payload is used
             // instead (for enforcing mode preflight).
             let payload = self.get_signature_payload(host)?;
-            match address {
+            match sc_addr {
                 ScAddress::Account(acc) => {
                     check_account_authentication(host, acc, &payload, &self.signature_args)?;
                 }
                 ScAddress::Contract(acc_contract) => {
                     check_account_contract_auth(
                         host,
-                        acc_contract,
+                        &acc_contract,
                         &payload,
                         &self.signature_args,
-                        &self.root_authorized_invocation,
+                        &self.invocation_tracker.root_authorized_invocation,
                     )?;
                 }
             }
@@ -1013,11 +1264,12 @@ impl AuthorizationTracker {
             return Ok(());
         }
         if let Some(address) = &self.address {
+            let sc_addr = host.scaddress_from_address(*address)?;
             // Compute the real payload for the sake of metering, but don't use it.
             let _payload = self.get_signature_payload(host)?;
-            match address {
+            match sc_addr {
                 ScAddress::Account(acc) => {
-                    let _account = host.load_account(acc.clone())?;
+                    let _account = host.load_account(acc)?;
                 }
                 // Skip custom accounts for now - emulating authentication for
                 // them requires a dummy signature.
@@ -1033,6 +1285,52 @@ impl AuthorizationTracker {
             ))
         }
     }
+
+    #[cfg(any(test, feature = "testutils"))]
+    fn is_exhausted(&self) -> bool {
+        self.invocation_tracker
+            .root_authorized_invocation
+            .is_exhausted
+    }
+}
+
+impl InvokerContractAuthorizationTracker {
+    fn new(host: &Host, invoker_auth_entry: RawVal) -> Result<Self, HostError> {
+        let invoker_sc_addr = ScAddress::Contract(host.get_current_contract_id_internal()?);
+        let authorized_invocation = invoker_contract_auth_to_authorized_invocation(
+            host,
+            &invoker_sc_addr,
+            invoker_auth_entry,
+        )?;
+        let invocation_tracker = InvocationTracker::new(authorized_invocation);
+        Ok(Self {
+            contract_address: host.add_host_object(invoker_sc_addr)?,
+            invocation_tracker,
+        })
+    }
+
+    fn push_frame(&mut self) {
+        self.invocation_tracker.push_frame();
+    }
+
+    fn pop_frame(&mut self) {
+        self.invocation_tracker.pop_frame();
+    }
+
+    fn is_out_of_scope(&self) -> bool {
+        self.invocation_tracker.is_empty()
+    }
+
+    fn maybe_authorize_invocation(
+        &mut self,
+        host: &Host,
+        function: &AuthorizedFunction,
+    ) -> Result<bool, HostError> {
+        // Authorization is successful if function is just matched by the
+        // tracker. No authentication is needed.
+        self.invocation_tracker
+            .maybe_push_matching_invocation_frame(host, function)
+    }
 }
 
 impl Host {
@@ -1047,9 +1345,7 @@ impl Host {
         });
         let contract_id = match function {
             AuthorizedFunction::ContractFn(contract_fn) => self.contract_id_from_scaddress(
-                contract_fn
-                    .contract_address
-                    .metered_clone(self.budget_ref())?,
+                self.scaddress_from_address(contract_fn.contract_address)?,
             )?,
             AuthorizedFunction::CreateContractHostFn(_) => {
                 // Use the special contract id for all the host fn nonces for
@@ -1106,17 +1402,15 @@ impl Host {
 
     fn read_and_consume_nonce(
         &self,
-        address: ScAddress,
+        address: AddressObject,
         function: &AuthorizedFunction,
     ) -> Result<u64, HostError> {
         let nonce_key_scval = ScVal::LedgerKeyNonce(ScNonceKey {
-            nonce_address: address,
+            nonce_address: self.scaddress_from_address(address)?,
         });
         let contract_id = match function {
             AuthorizedFunction::ContractFn(contract_fn) => self.contract_id_from_scaddress(
-                contract_fn
-                    .contract_address
-                    .metered_clone(self.budget_ref())?,
+                self.scaddress_from_address(contract_fn.contract_address)?,
             )?,
             AuthorizedFunction::CreateContractHostFn(_) => {
                 // Use the special contract id for all the host fn nonces for
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 8848b8c2..dda5cc1e 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -143,10 +143,10 @@ impl Host {
             objects: Default::default(),
             storage: RefCell::new(storage),
             context: Default::default(),
-            budget: budget.clone(),
+            budget,
             events: Default::default(),
             authorization_manager: RefCell::new(
-                AuthorizationManager::new_enforcing_without_authorizations(budget),
+                AuthorizationManager::new_enforcing_without_authorizations(),
             ),
             diagnostic_level: Default::default(),
             base_prng: RefCell::new(None),
@@ -167,13 +167,18 @@ impl Host {
         *self.0.source_account.borrow_mut() = None;
     }
 
-    pub fn source_account(&self) -> Option<AccountId> {
-        self.0.source_account.borrow().clone()
+    pub fn source_account_address(&self) -> Result<Option<AddressObject>, HostError> {
+        if let Some(acc) = self.0.source_account.borrow().as_ref() {
+            Ok(Some(self.add_host_object(ScAddress::Account(
+                acc.metered_clone(self.budget_ref())?,
+            ))?))
+        } else {
+            Ok(None)
+        }
     }
 
     pub fn switch_to_recording_auth(&self) {
-        *self.0.authorization_manager.borrow_mut() =
-            AuthorizationManager::new_recording(self.budget_cloned());
+        *self.0.authorization_manager.borrow_mut() = AuthorizationManager::new_recording();
     }
 
     pub fn set_authorization_entries(
@@ -441,13 +446,9 @@ impl Host {
         args: CreateContractArgs,
     ) -> Result<AddressObject, HostError> {
         if let Some(deployer_address) = deployer {
-            let sc_addr = self.visit_obj(deployer_address, |addr: &ScAddress| {
-                addr.metered_clone(self.budget_ref())
-            })?;
             self.0.authorization_manager.borrow_mut().require_auth(
                 self,
-                deployer_address.get_handle(),
-                sc_addr,
+                deployer_address,
                 Default::default(),
             )?;
         }
@@ -515,7 +516,7 @@ impl Host {
                     &[],
                 )
             })?
-            .get_authenticated_authorizations())
+            .get_authenticated_authorizations(self))
     }
 
     fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
@@ -599,7 +600,7 @@ impl Host {
             self.0
                 .authorization_manager
                 .borrow()
-                .get_recorded_auth_payloads()
+                .get_recorded_auth_payloads(self)
         }
         #[cfg(any(test, feature = "testutils"))]
         {
@@ -615,7 +616,7 @@ impl Host {
                         &[],
                     )
                 })?
-                .get_recorded_auth_payloads()
+                .get_recorded_auth_payloads(self)
         }
     }
 
@@ -2490,21 +2491,13 @@ impl VmCallerEnv for Host {
         vmcaller: &mut VmCaller<Self::VmUserState>,
         address: AddressObject,
         args: VecObject,
-    ) -> Result<RawVal, Self::Error> {
-        let sc_addr = self.visit_obj(address, |addr: &ScAddress| {
-            addr.metered_clone(self.budget_ref())
-        })?;
-
+    ) -> Result<Void, Self::Error> {
+        let args = self.visit_obj(args, |a: &HostVec| a.to_vec(self.budget_ref()))?;
         Ok(self
             .0
             .authorization_manager
             .borrow_mut()
-            .require_auth(
-                self,
-                address.get_handle(),
-                sc_addr,
-                self.call_args_to_scvec(args)?,
-            )?
+            .require_auth(self, address, args)?
             .into())
     }
 
@@ -2512,8 +2505,7 @@ impl VmCallerEnv for Host {
         &self,
         vmcaller: &mut VmCaller<Self::VmUserState>,
         address: AddressObject,
-    ) -> Result<RawVal, Self::Error> {
-        let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?;
+    ) -> Result<Void, Self::Error> {
         let args = self.with_current_frame(|f| {
             let args = match f {
                 Frame::ContractVM(_, _, args) => args,
@@ -2529,23 +2521,28 @@ impl VmCallerEnv for Host {
                 #[cfg(any(test, feature = "testutils"))]
                 Frame::TestContract(c) => &c.args,
             };
-            self.rawvals_to_scvec(&args)
+            args.metered_clone(self.budget_ref())
         })?;
 
         Ok(self
             .0
             .authorization_manager
             .borrow_mut()
-            .require_auth(self, address.get_handle(), addr, args)?
+            .require_auth(self, address, args)?
             .into())
     }
 
-    fn get_current_contract_id(
+    fn authorize_as_curr_contract(
         &self,
         vmcaller: &mut VmCaller<Self::VmUserState>,
-    ) -> Result<BytesObject, Self::Error> {
-        let id = self.get_current_contract_id_internal()?;
-        self.add_host_object(ScBytes(id.0.to_vec().try_into()?))
+        auth_entries: VecObject,
+    ) -> Result<Void, HostError> {
+        Ok(self
+            .0
+            .authorization_manager
+            .borrow_mut()
+            .add_invoker_contract_auth(self, auth_entries)?
+            .into())
     }
 
     fn account_public_key_to_address(
diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs
index 7ffe3a9d..f54c9a72 100644
--- a/soroban-env-host/src/host/conversion.rs
+++ b/soroban-env-host/src/host/conversion.rs
@@ -13,11 +13,12 @@ use soroban_env_common::num::{
 };
 use soroban_env_common::xdr::{
     self, int128_helpers, AccountId, ContractDataType, ContractLedgerEntryType, Int128Parts,
-    Int256Parts, ScBytes, ScErrorCode, ScErrorType, ScMap, ScMapEntry, UInt128Parts, UInt256Parts,
+    Int256Parts, ScAddress, ScBytes, ScErrorCode, ScErrorType, ScMap, ScMapEntry, UInt128Parts,
+    UInt256Parts,
 };
 use soroban_env_common::{
-    BytesObject, Convert, Object, ScValObjRef, ScValObject, TryFromVal, TryIntoVal, U32Val,
-    VecObject,
+    AddressObject, BytesObject, Convert, Object, ScValObjRef, ScValObject, TryFromVal, TryIntoVal,
+    U32Val, VecObject,
 };
 
 impl Host {
@@ -317,7 +318,28 @@ impl Host {
         Ok(ScVec(
             raw_vals
                 .iter()
-                .map(|v| ScVal::try_from_val(self, v).map_err(|e| e.into()))
+                .map(|v| self.from_host_val(*v))
+                .collect::<Result<Vec<ScVal>, HostError>>()?
+                .try_into()
+                .map_err(|_| {
+                    err!(
+                        self,
+                        (ScErrorType::Object, ScErrorCode::ExceededLimit),
+                        "vector size limit exceeded",
+                        raw_vals.len()
+                    )
+                })?,
+        ))
+    }
+
+    pub(crate) fn rawvals_to_scvec_non_metered(
+        &self,
+        raw_vals: &[RawVal],
+    ) -> Result<ScVec, HostError> {
+        Ok(ScVec(
+            raw_vals
+                .iter()
+                .map(|v| v.try_into_val(self)?)
                 .collect::<Result<Vec<ScVal>, HostError>>()?
                 .try_into()
                 .map_err(|_| {
@@ -332,6 +354,10 @@ impl Host {
     }
 
     pub(crate) fn scvals_to_rawvals(&self, sc_vals: &[ScVal]) -> Result<Vec<RawVal>, HostError> {
+        charge_container_bulk_init_with_elts::<Vec<RawVal>, RawVal>(
+            sc_vals.len() as u64,
+            self.as_budget(),
+        )?;
         sc_vals
             .iter()
             .map(|scv| self.to_host_val(scv))
@@ -360,6 +386,15 @@ impl Host {
     pub(crate) fn scbytes_from_hash(&self, hash: &Hash) -> Result<ScBytes, HostError> {
         self.scbytes_from_slice(hash.as_slice())
     }
+
+    pub(crate) fn scaddress_from_address(
+        &self,
+        address: AddressObject,
+    ) -> Result<ScAddress, HostError> {
+        self.visit_obj(address, |addr: &ScAddress| {
+            addr.metered_clone(self.budget_ref())
+        })
+    }
 }
 
 impl Convert<&Object, ScValObject> for Host {
diff --git a/soroban-env-host/src/host/declared_size.rs b/soroban-env-host/src/host/declared_size.rs
index 07694c85..bc06135f 100644
--- a/soroban-env-host/src/host/declared_size.rs
+++ b/soroban-env-host/src/host/declared_size.rs
@@ -1,6 +1,8 @@
 use std::rc::Rc;
 
-use soroban_env_common::xdr::{ContractIdPreimage, CreateContractArgs};
+use soroban_env_common::xdr::{
+    ContractIdPreimage, CreateContractArgs, SorobanAuthorizedInvocation,
+};
 
 use crate::{
     events::{HostEvent, InternalContractEvent, InternalEvent},
@@ -142,6 +144,7 @@ impl_declared_size_type!(ScString, 24);
 impl_declared_size_type!(ScSymbol, 24);
 impl_declared_size_type!(CreateContractArgs, 99);
 impl_declared_size_type!(ContractIdPreimage, 66);
+impl_declared_size_type!(SorobanAuthorizedInvocation, 128);
 
 // composite types
 
@@ -314,6 +317,11 @@ mod test {
         expect!["24"].assert_eq(size_of::<ScSymbol>().to_string().as_str());
         expect!["99"].assert_eq(size_of::<CreateContractArgs>().to_string().as_str());
         expect!["66"].assert_eq(size_of::<ContractIdPreimage>().to_string().as_str());
+        expect!["128"].assert_eq(
+            size_of::<SorobanAuthorizedInvocation>()
+                .to_string()
+                .as_str(),
+        );
         // composite types
         expect!["16"].assert_eq(size_of::<&[ScVal]>().to_string().as_str());
         expect!["48"].assert_eq(size_of::<(RawVal, ScVal)>().to_string().as_str());
@@ -442,6 +450,7 @@ mod test {
         assert_mem_size_le_declared_size!(ScString);
         assert_mem_size_le_declared_size!(ScSymbol);
         assert_mem_size_le_declared_size!(CreateContractArgs);
+        assert_mem_size_le_declared_size!(SorobanAuthorizedInvocation);
         // composite types
         assert_mem_size_le_declared_size!(&[ScVal]);
         assert_mem_size_le_declared_size!((RawVal, ScVal));
diff --git a/soroban-env-host/src/host/metered_clone.rs b/soroban-env-host/src/host/metered_clone.rs
index 9ee7e995..b63a3535 100644
--- a/soroban-env-host/src/host/metered_clone.rs
+++ b/soroban-env-host/src/host/metered_clone.rs
@@ -1,6 +1,8 @@
 use std::{mem, rc::Rc};
 
-use soroban_env_common::xdr::{ContractEventType, ContractIdPreimage, ScErrorCode, ScErrorType};
+use soroban_env_common::xdr::{
+    ContractEventType, ContractIdPreimage, ScErrorCode, ScErrorType, SorobanAuthorizedInvocation,
+};
 
 use crate::{
     budget::Budget,
@@ -221,6 +223,7 @@ impl MeteredClone for AccessType {}
 impl MeteredClone for InternalContractEvent {}
 impl MeteredClone for CreateContractArgs {}
 impl MeteredClone for ContractIdPreimage {}
+impl MeteredClone for SorobanAuthorizedInvocation {}
 // composite types
 impl<T> MeteredClone for Rc<T> {}
 impl<T> MeteredClone for &[T] {}
diff --git a/soroban-env-host/src/host/metered_vector.rs b/soroban-env-host/src/host/metered_vector.rs
index 75931c4e..8808c153 100644
--- a/soroban-env-host/src/host/metered_vector.rs
+++ b/soroban-env-host/src/host/metered_vector.rs
@@ -290,6 +290,10 @@ where
     pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, A> {
         self.vec.iter_mut()
     }
+
+    pub fn to_vec(&self, budget: &Budget) -> Result<Vec<A>, HostError> {
+        self.vec.metered_clone(budget)
+    }
 }
 
 impl<A> DeclaredSizeForMetering for MeteredVector<A>
diff --git a/soroban-env-host/src/native_contract.rs b/soroban-env-host/src/native_contract.rs
index e4964cd5..8ff6b242 100644
--- a/soroban-env-host/src/native_contract.rs
+++ b/soroban-env-host/src/native_contract.rs
@@ -1,5 +1,6 @@
 pub(crate) mod base_types;
 pub(crate) mod contract_error;
+pub(crate) mod invoker_contract_auth;
 pub(crate) mod token;
 
 use crate::host::{Host, HostError};
diff --git a/soroban-env-host/src/native_contract/account_contract.rs b/soroban-env-host/src/native_contract/account_contract.rs
index 767176ce..3690b8d1 100644
--- a/soroban-env-host/src/native_contract/account_contract.rs
+++ b/soroban-env-host/src/native_contract/account_contract.rs
@@ -3,7 +3,6 @@ use crate::auth::{AuthorizedFunction, AuthorizedInvocation};
 // it doesn't need to be directly invoked. But semantically this is analagous
 // to a generic smart wallet contract that supports authentication and blanket
 // context authorization.
-use crate::host::metered_clone::MeteredClone;
 use crate::host::{frame::ContractReentryMode, Host};
 use crate::native_contract::{base_types::BytesN, contract_error::ContractError};
 use crate::{err, HostError};
@@ -18,7 +17,7 @@ use crate::native_contract::base_types::Vec as HostVec;
 
 const MAX_ACCOUNT_SIGNATURES: u32 = 20;
 
-use soroban_env_common::xdr::{AccountId, ScVal};
+use soroban_env_common::xdr::AccountId;
 use soroban_native_sdk_macros::contracttype;
 
 use super::base_types::Address;
@@ -64,24 +63,11 @@ impl AuthorizationContext {
     fn from_authorized_fn(host: &Host, function: &AuthorizedFunction) -> Result<Self, HostError> {
         match function {
             AuthorizedFunction::ContractFn(contract_fn) => {
-                let fn_name = Symbol::try_from(host.to_host_val(&ScVal::Symbol(
-                    contract_fn.function_name.metered_clone(host.budget_ref())?,
-                ))?)?;
-                let args = HostVec::try_from_val(
-                    host,
-                    &host.scvals_to_rawvals(contract_fn.args.0.as_slice())?,
-                )?;
+                let args = HostVec::try_from_val(host, &contract_fn.args)?;
                 Ok(AuthorizationContext::Contract(
                     ContractAuthorizationContext {
-                        contract: Address::try_from_val(
-                            host,
-                            &host.add_host_object(
-                                contract_fn
-                                    .contract_address
-                                    .metered_clone(host.budget_ref())?,
-                            )?,
-                        )?,
-                        fn_name,
+                        contract: contract_fn.contract_address.try_into_val(host)?,
+                        fn_name: contract_fn.function_name,
                         args,
                     },
                 ))
@@ -169,30 +155,14 @@ pub(crate) fn check_account_contract_auth(
 
 pub(crate) fn check_account_authentication(
     host: &Host,
-    account_id: &AccountId,
+    account_id: AccountId,
     payload: &[u8],
-    signature_args: &Vec<RawVal>,
+    signatures: &Vec<RawVal>,
 ) -> Result<(), HostError> {
-    if signature_args.len() != 1 {
-        return Err(err!(
-            host,
-            ContractError::AuthenticationError,
-            "incorrect number of signature args",
-            signature_args.len()
-        ));
-    }
-    let sigs: HostVec = signature_args[0].try_into_val(host).map_err(|_| {
-        host.error(
-            ContractError::AuthenticationError.into(),
-            "incompatible signature format",
-            &[],
-        )
-    })?;
-
     // Check if there is too many signatures: there shouldn't be more
     // signatures then the amount of account signers.
-    let len = sigs.len()?;
-    if len > MAX_ACCOUNT_SIGNATURES {
+    let len = signatures.len();
+    if len > MAX_ACCOUNT_SIGNATURES as usize {
         return Err(err!(
             host,
             ContractError::AuthenticationError,
@@ -201,11 +171,11 @@ pub(crate) fn check_account_authentication(
         ));
     }
     let payload_obj = host.bytes_new_from_slice(payload)?;
-    let account = host.load_account(account_id.metered_clone(host.budget_ref())?)?;
+    let account = host.load_account(account_id)?;
     let mut prev_pk: Option<BytesN<32>> = None;
     let mut weight = 0u32;
-    for i in 0..sigs.len()? {
-        let sig: AccountEd25519Signature = sigs.get(i)?;
+    for sig in signatures {
+        let sig = AccountEd25519Signature::try_from_val(host, sig)?;
         // Cannot take multiple signatures from the same key
         if let Some(prev) = prev_pk {
             if prev.compare(&sig.public_key)? != Ordering::Less {
diff --git a/soroban-env-host/src/native_contract/base_types.rs b/soroban-env-host/src/native_contract/base_types.rs
index b8bfd816..658c991e 100644
--- a/soroban-env-host/src/native_contract/base_types.rs
+++ b/soroban-env-host/src/native_contract/base_types.rs
@@ -96,6 +96,10 @@ impl Bytes {
             .unwrap();
         res
     }
+
+    pub fn as_object(&self) -> BytesObject {
+        self.object
+    }
 }
 
 #[derive(Clone)]
@@ -182,6 +186,10 @@ impl<const N: usize> BytesN<N> {
             object: env.bytes_new_from_slice(items)?,
         })
     }
+
+    pub fn as_object(&self) -> BytesObject {
+        self.object
+    }
 }
 
 #[derive(Clone)]
@@ -387,6 +395,10 @@ impl Vec {
         self.object = self.host.vec_push_back(self.object, x)?;
         Ok(())
     }
+
+    pub fn as_object(&self) -> VecObject {
+        self.object
+    }
 }
 
 #[derive(Clone)]
@@ -449,12 +461,15 @@ impl Address {
     }
 
     pub(crate) fn to_sc_address(&self) -> Result<ScAddress, HostError> {
-        self.host.visit_obj(self.object, |addr: &ScAddress| {
-            addr.metered_clone(self.host.budget_ref())
-        })
+        self.host.scaddress_from_address(self.object)
     }
 
     pub(crate) fn require_auth(&self) -> Result<(), HostError> {
-        Ok(self.host.require_auth(self.object)?.try_into()?)
+        self.host.require_auth(self.object)?;
+        Ok(())
+    }
+
+    pub(crate) fn as_object(&self) -> AddressObject {
+        self.object
     }
 }
diff --git a/soroban-env-host/src/native_contract/invoker_contract_auth.rs b/soroban-env-host/src/native_contract/invoker_contract_auth.rs
new file mode 100644
index 00000000..67a9ce42
--- /dev/null
+++ b/soroban-env-host/src/native_contract/invoker_contract_auth.rs
@@ -0,0 +1,97 @@
+use super::account_contract::{
+    ContractAuthorizationContext, ContractExecutable, CreateContractHostFnContext,
+};
+use crate::host::metered_clone::MeteredClone;
+use crate::host_object::HostVec;
+use crate::{
+    auth::{AuthorizedFunction, AuthorizedInvocation, ContractFunction},
+    native_contract::base_types::Vec as ContractTypeVec,
+    Host, HostError,
+};
+use soroban_env_common::xdr::{
+    ContractIdPreimage, ContractIdPreimageFromAddress, CreateContractArgs, ScAddress,
+    ScContractExecutable,
+};
+use soroban_env_common::{RawVal, TryFromVal, TryIntoVal};
+use soroban_native_sdk_macros::contracttype;
+
+#[derive(Clone)]
+#[contracttype]
+pub struct SubContractInvocation {
+    pub context: ContractAuthorizationContext,
+    pub sub_invocations: ContractTypeVec, // Vec of InvokerContractAuthEntry
+}
+
+/// Contract type used as the element of `authorize_as_curr_contract` host
+/// function argument vector.
+/// Defines sub-contract call authorizations performed on behalf of the current
+/// contract.
+#[derive(Clone)]
+#[contracttype]
+pub enum InvokerContractAuthEntry {
+    Contract(SubContractInvocation),
+    CreateContractHostFn(CreateContractHostFnContext),
+}
+
+impl InvokerContractAuthEntry {
+    fn to_authorized_invocation(
+        &self,
+        host: &Host,
+        invoker_contract_addr: &ScAddress,
+    ) -> Result<AuthorizedInvocation, HostError> {
+        match &self {
+            InvokerContractAuthEntry::Contract(contract_invocation) => {
+                let function = AuthorizedFunction::ContractFn(ContractFunction {
+                    contract_address: contract_invocation.context.contract.as_object(),
+                    function_name: contract_invocation.context.fn_name,
+                    args: host.visit_obj(
+                        contract_invocation.context.args.as_object(),
+                        |v: &HostVec| v.to_vec(host.budget_ref()),
+                    )?,
+                });
+                let sub_invocations: Vec<InvokerContractAuthEntry> = host.visit_obj(
+                    contract_invocation.sub_invocations.as_object(),
+                    |v: &HostVec| {
+                        v.iter()
+                            .map(|val| InvokerContractAuthEntry::try_from_val(host, val))
+                            .collect::<Result<Vec<InvokerContractAuthEntry>, HostError>>()
+                    },
+                )?;
+                let sub_invocations = sub_invocations
+                    .into_iter()
+                    .map(|i| i.to_authorized_invocation(host, invoker_contract_addr))
+                    .collect::<Result<Vec<AuthorizedInvocation>, HostError>>()?;
+                Ok(AuthorizedInvocation::new(function, sub_invocations))
+            }
+            InvokerContractAuthEntry::CreateContractHostFn(create_contract_fn) => {
+                let wasm_hash = match &create_contract_fn.executable {
+                    ContractExecutable::Wasm(b) => {
+                        host.hash_from_bytesobj_input("wasm_ref", b.as_object())?
+                    }
+                };
+                let function = AuthorizedFunction::CreateContractHostFn(CreateContractArgs {
+                    contract_id_preimage: ContractIdPreimage::Address(
+                        ContractIdPreimageFromAddress {
+                            address: invoker_contract_addr.metered_clone(host.budget_ref())?,
+                            salt: host.uint256_from_bytesobj_input(
+                                "salt",
+                                create_contract_fn.salt.as_object(),
+                            )?,
+                        },
+                    ),
+                    executable: ScContractExecutable::WasmRef(wasm_hash),
+                });
+                Ok(AuthorizedInvocation::new(function, vec![]))
+            }
+        }
+    }
+}
+
+pub(crate) fn invoker_contract_auth_to_authorized_invocation(
+    host: &Host,
+    invoker_contract_addr: &ScAddress,
+    invoker_auth_entry: RawVal,
+) -> Result<AuthorizedInvocation, HostError> {
+    let entry = InvokerContractAuthEntry::try_from_val(host, &invoker_auth_entry)?;
+    entry.to_authorized_invocation(host, invoker_contract_addr)
+}


Test patch:
diff --git a/soroban-env-host/src/native_contract/testutils.rs b/soroban-env-host/src/native_contract/testutils.rs
index ffb4e30a..05103ff6 100644
--- a/soroban-env-host/src/native_contract/testutils.rs
+++ b/soroban-env-host/src/native_contract/testutils.rs
@@ -10,7 +10,7 @@ use soroban_env_common::xdr::{
     SorobanAuthorizationEntry, SorobanAuthorizedContractFunction, SorobanAuthorizedFunction,
     SorobanAuthorizedInvocation, SorobanCredentials, Thresholds, Uint256,
 };
-use soroban_env_common::{EnvBase, TryFromVal, TryIntoVal};
+use soroban_env_common::{EnvBase, Symbol, TryFromVal};
 
 use crate::native_contract::base_types::BytesN;
 
@@ -119,7 +119,7 @@ impl<'a> TestSigner<'a> {
                         .push(&sign_payload_for_account(host, key, payload))
                         .unwrap();
                 }
-                host_vec![host, signatures]
+                signatures
             }
             TestSigner::AccountContract(signer) => (signer.sign)(payload),
             TestSigner::ContractInvoker(_) => host_vec![host],
@@ -206,8 +206,8 @@ pub(crate) fn authorize_single_invocation(
             host.read_nonce(
                 signer.address(host).to_sc_address().unwrap(),
                 &AuthorizedFunction::ContractFn(ContractFunction {
-                    contract_address: contract_address.to_sc_address().unwrap(),
-                    function_name: Default::default(),
+                    contract_address: contract_address.clone().into(),
+                    function_name: Symbol::try_from_small_str("").unwrap(),
                     args: Default::default(),
                 }),
             )
diff --git a/soroban-env-host/src/test/auth.rs b/soroban-env-host/src/test/auth.rs
index 5e1de3e8..f87efdcd 100644
--- a/soroban-env-host/src/test/auth.rs
+++ b/soroban-env-host/src/test/auth.rs
@@ -16,14 +16,14 @@ use crate::native_contract::testutils::{
     create_account, generate_keypair, sign_payload_for_account,
 };
 use crate::{host_vec, Host, LedgerInfo};
-use soroban_env_common::{AddressObject, Env, Symbol, SymbolStr, TryIntoVal};
+use soroban_env_common::{AddressObject, Env, Symbol, SymbolStr, TryFromVal, TryIntoVal};
 
 use crate::native_contract::base_types::Vec as HostVec;
 
 #[derive(Clone)]
 #[contracttype]
 pub struct ContractTreeNode {
-    pub addr: Address,
+    pub contract: Address,
     pub need_auth: HostVec,
     pub children: HostVec,
 }
@@ -161,8 +161,8 @@ impl AuthTest {
             let sc_address = self.key_to_sc_address(&self.keys[address_id]);
             let mut next_nonce = HashMap::<ScAddress, u64>::new();
             for sign_root in &sign_payloads[address_id] {
-                let contract_address = sign_root.contract_address.to_sc_address().unwrap();
-                let nonce = if let Some(nonce) = next_nonce.get(&contract_address) {
+                let sign_root_contract = sign_root.contract_address.to_sc_address().unwrap();
+                let nonce = if let Some(nonce) = next_nonce.get(&sign_root_contract) {
                     *nonce
                 } else {
                     let nonce = self
@@ -170,22 +170,22 @@ impl AuthTest {
                         .read_nonce(
                             sc_address.clone(),
                             &AuthorizedFunction::ContractFn(ContractFunction {
-                                contract_address: contract_address.clone(),
-                                function_name: Default::default(),
+                                contract_address: sign_root.contract_address.clone().into(),
+                                function_name: Symbol::try_from_small_str("").unwrap(),
                                 args: Default::default(),
                             }),
                         )
                         .unwrap();
                     self.last_nonces.insert(
                         (
-                            contract_address.clone(),
+                            sign_root_contract.clone(),
                             self.keys[address_id].public.as_bytes().to_vec(),
                         ),
                         nonce,
                     );
                     nonce
                 };
-                next_nonce.insert(contract_address, nonce + 1);
+                next_nonce.insert(sign_root_contract, nonce + 1);
                 let root_invocation = self.convert_sign_node(sign_root);
                 let payload_preimage =
                     HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization {
@@ -201,10 +201,7 @@ impl AuthTest {
                 let payload = self.host.metered_hash_xdr(&payload_preimage).unwrap();
                 let signature_args = host_vec![
                     &self.host,
-                    host_vec![
-                        &self.host,
-                        sign_payload_for_account(&self.host, &self.keys[address_id], &payload)
-                    ]
+                    sign_payload_for_account(&self.host, &self.keys[address_id], &payload)
                 ];
                 contract_auth.push(SorobanAuthorizationEntry {
                     credentials: SorobanCredentials::Address(SorobanAddressCredentials {
@@ -242,8 +239,8 @@ impl AuthTest {
                     .read_nonce(
                         self.key_to_sc_address(key),
                         &AuthorizedFunction::ContractFn(ContractFunction {
-                            contract_address: contract_address.to_sc_address().unwrap(),
-                            function_name: Default::default(),
+                            contract_address: contract_address.clone().into(),
+                            function_name: Symbol::try_from_small_str("").unwrap(),
                             args: Default::default(),
                         }),
                     )
@@ -333,7 +330,7 @@ impl AuthTest {
             need_auth.push(n).unwrap();
         }
         ContractTreeNode {
-            addr: root.contract_address.clone(),
+            contract: root.contract_address.clone(),
             need_auth,
             children,
         }
@@ -1313,3 +1310,97 @@ fn test_out_of_order_auth() {
         false,
     );
 }
+
+#[test]
+fn test_invoker_subcontract_auth() {
+    let mut test = AuthTest::setup(0, 6);
+
+    let setup = SetupNode::new(
+        &test.contracts[0],
+        vec![true],
+        vec![
+            SetupNode::new(
+                &test.contracts[1],
+                vec![true],
+                vec![
+                    SetupNode::new(&test.contracts[3], vec![true], vec![]),
+                    SetupNode::new(&test.contracts[2], vec![true], vec![]),
+                ],
+            ),
+            SetupNode::new(
+                &test.contracts[2],
+                vec![true],
+                vec![
+                    SetupNode::new(
+                        &test.contracts[4],
+                        vec![true],
+                        vec![SetupNode::new(&test.contracts[3], vec![true], vec![])],
+                    ),
+                    // Contract will authorize this, but `require_auth` isn't
+                    // called - that's fine as we don't require exhausting
+                    // every authorization.
+                    SetupNode::new(&test.contracts[3], vec![false], vec![]),
+                ],
+            ),
+        ],
+    );
+
+    let tree = test.convert_setup_tree(&setup);
+    let args = host_vec![&test.host, tree];
+    let fn_name = Symbol::try_from_val(&test.host, &"invoker_auth_fn").unwrap();
+    assert_eq!(
+        test.run_recording(&test.contracts[5], fn_name.clone(), args.clone(),),
+        vec![]
+    );
+
+    test.test_enforcing(
+        test.contracts[5].clone(),
+        fn_name.clone(),
+        args.clone(),
+        vec![],
+        true,
+    )
+}
+
+#[test]
+fn test_invoker_subcontract_with_gaps() {
+    let mut test = AuthTest::setup(0, 4);
+
+    // The direct contract call doesn't need to require auth - the contract
+    // still authorizes the following sub-contract calls only.
+    let top_gap_setup = SetupNode::new(
+        &test.contracts[0],
+        vec![false],
+        vec![SetupNode::new(&test.contracts[1], vec![true], vec![])],
+    );
+
+    let fn_name = Symbol::try_from_val(&test.host, &"invoker_auth_fn").unwrap();
+
+    test.test_enforcing(
+        test.contracts[3].clone(),
+        fn_name.clone(),
+        host_vec![&test.host, test.convert_setup_tree(&top_gap_setup)],
+        vec![],
+        true,
+    );
+
+    // Gap in the middle is not allowed (contract authorizes 1->2, while we
+    // require auth only for 2).
+    let mid_gap_setup = SetupNode::new(
+        &test.contracts[0],
+        vec![true],
+        vec![SetupNode::new(
+            &test.contracts[1],
+            vec![false],
+            vec![SetupNode::new(&test.contracts[2], vec![true], vec![])],
+        )],
+    );
+
+    test.test_enforcing(
+        test.contracts[3].clone(),
+        fn_name.clone(),
+        host_vec![&test.host, test.convert_setup_tree(&mid_gap_setup)],
+        vec![],
+        false,
+    )
+}
diff --git a/soroban-env-host/src/test/token.rs b/soroban-env-host/src/test/token.rs
index e703f0f6..7a083a61 100644
--- a/soroban-env-host/src/test/token.rs
+++ b/soroban-env-host/src/test/token.rs
@@ -2685,11 +2685,7 @@ fn test_recording_auth_for_token() {
     assert_eq!(
         test.host.get_authenticated_authorizations().unwrap(),
         vec![(
-            test.host
-                .visit_obj(admin.address(&test.host).into(), |addr: &ScAddress| Ok(
-                    addr.clone()
-                ))
-                .unwrap(),
+            admin.address(&test.host).to_sc_address().unwrap(),
             SorobanAuthorizedInvocation {
                 function: SorobanAuthorizedFunction::ContractFn(
                     SorobanAuthorizedContractFunction {
diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs
index 092d404e..a12cb172 100644
--- a/soroban-env-host/src/test/util.rs
+++ b/soroban-env-host/src/test/util.rs
@@ -163,14 +163,6 @@ impl Host {
         Ok(rawval.try_into()?)
     }
 
-    pub(crate) fn raw_val_vec_to_sc_vec(&self, v: Vec<RawVal>) -> ScVec {
-        let mut res = Vec::<ScVal>::new();
-        for val in v {
-            res.push(val.try_into_val(self).unwrap());
-        }
-        res.try_into().unwrap()
-    }
-
     // Registers a contract with provided WASM source and returns the registered
     // contract's address.
     // This relies on the host to have no footprint enforcement.
diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock
index 969fe43d..7860e37c 100644
--- a/soroban-test-wasms/wasm-workspace/Cargo.lock
+++ b/soroban-test-wasms/wasm-workspace/Cargo.lock
@@ -867,7 +867,7 @@ dependencies = [
  "sha2",
  "soroban-env-common",
  "soroban-spec",
- "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9)",
+ "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=ba1ff9231ae81793d470db93815ead664eb21fb2)",
  "syn 2.0.16",
 ]
 
@@ -885,7 +885,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "sha2",
- "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9)",
+ "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=ba1ff9231ae81793d470db93815ead664eb21fb2)",
  "syn 2.0.16",
  "thiserror",
  "wasmparser",
@@ -952,7 +952,7 @@ dependencies = [
 [[package]]
 name = "stellar-xdr"
 version = "0.0.16"
-source = "git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9#a2f370c930bb94f7bc0c9ea0426ecef3700b90a9"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=ba1ff9231ae81793d470db93815ead664eb21fb2#ba1ff9231ae81793d470db93815ead664eb21fb2"
 dependencies = [
  "crate-git-revision",
  "hex",
diff --git a/soroban-test-wasms/wasm-workspace/auth/src/lib.rs b/soroban-test-wasms/wasm-workspace/auth/src/lib.rs
index e4cfe5ca..99bf54b2 100644
--- a/soroban-test-wasms/wasm-workspace/auth/src/lib.rs
+++ b/soroban-test-wasms/wasm-workspace/auth/src/lib.rs
@@ -1,12 +1,15 @@
 #![no_std]
-use soroban_sdk::{contractimpl, contracttype, vec, Address, Env, IntoVal, Symbol, Vec};
+use soroban_sdk::{
+    auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation},
+    contractimpl, contracttype, vec, Address, Env, IntoVal, Symbol, Vec,
+};
 
 struct AuthContract;
 
 #[derive(Clone)]
 #[contracttype]
 pub struct TreeNode {
-    pub addr: Address,
+    pub contract: Address,
     pub need_auth: Vec<bool>,
     pub children: Vec<TreeNode>,
 }
@@ -30,7 +33,7 @@ impl AuthContract {
         for child in tree.children.iter() {
             let child = child.unwrap();
             env.invoke_contract::<()>(
-                &child.addr.clone(),
+                &child.contract.clone(),
                 &Symbol::short("tree_fn"),
                 (addresses.clone(), child).into_val(&env),
             );
@@ -49,4 +52,37 @@ impl AuthContract {
     pub fn do_auth(_env: Env, addr: Address, _val: u32) {
         addr.require_auth();
     }
+
+    pub fn invoker_auth_fn(env: Env, tree: TreeNode) {
+        // Build auth entries from children - tree root auth works
+        // automatically.
+        let mut auth_entries = vec![&env];
+        for child in tree.children.iter() {
+            auth_entries.push_back(tree_to_invoker_contract_auth(&env, &child.unwrap()));
+        }
+        env.authorize_as_curr_contract(auth_entries);
+        let curr_address = env.current_contract_address();
+        env.invoke_contract::<()>(
+            &tree.contract,
+            &Symbol::short("tree_fn"),
+            (vec![&env, curr_address], tree.clone()).into_val(&env),
+        );
+    }
+}
+
+// Converts the whole provided tree to invoker auth entries, ignoring
+// `need_auth` (for covering non-matching tree scenarios).
+fn tree_to_invoker_contract_auth(env: &Env, tree: &TreeNode) -> InvokerContractAuthEntry {
+    let mut sub_invocations = vec![env];
+    for c in tree.children.iter() {
+        sub_invocations.push_back(tree_to_invoker_contract_auth(env, &c.unwrap()));
+    }
+    InvokerContractAuthEntry::Contract(SubContractInvocation {
+        context: ContractContext {
+            contract: tree.contract.clone(),
+            fn_name: Symbol::short("tree_fn"),
+            args: vec![&env],
+        },
+        sub_invocations,
+    })
 }
diff --git a/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs b/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
index ab31daea..4f8fbed4 100644
--- a/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
+++ b/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
@@ -8,6 +8,8 @@ impl Contract {
     // Note that anyone can create a contract here with any salt, so a users call to
     // this could be frontrun and the same salt taken.
     pub fn create(e: Env, wasm_hash: BytesN<32>, salt: BytesN<32>) {
-        e.deployer().with_current_contract(&salt).deploy(&wasm_hash);
+        e.deployer()
+            .with_current_contract()
+            .deploy(&salt, &wasm_hash);
     }
 }