Skip to content

Nested Contracts

Soroban Contract in Contract Authorization
#![no_std]
 
pub mod contract_a {
    use soroban_sdk::{
        auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation},
        contract, contractimpl, vec, Env, Address, Symbol, IntoVal, log
    };
 
    use crate::contract_b::ContractBClient;
 
    #[contract]
    pub struct ContractA;
 
    #[contractimpl]
    impl ContractA {
        pub fn call_b(env: Env, contract_b_address: Address, contract_c_address: Address) {
            /// @dev should remove logs before deploying smart contracts
            log!(&env, "Contract B address: {}, Contract C address: {}", contract_b_address, contract_c_address);
 
            env.authorize_as_current_contract(vec![
               &env,
                InvokerContractAuthEntry::Contract(SubContractInvocation {
                    context: ContractContext {
                       contract: contract_c_address.clone(),
                       fn_name: Symbol::new(&env, "authorized_fn_c"),
                       args: (env.current_contract_address(),).into_val(&env),
                    },
 
                    sub_invocations: vec![&env],
                }),
            ]);
        }
    }
}
 
pub mod contract_b {
    use soroban_sdk::{contract, contractimpl, Env, Address, log};
 
    use crate::contract_c::ContractCClient;
 
    #[contract]
    pub struct ContractB;
 
    #[contractimpl]
    impl ContractB {
        pub fn authorized_fn_b(env: Env, authorizer: Address, contract_c_address: Address) {
            log!(&env, "Authorizer: {}, Contract C address: {}", authorizer, contract_c_address);
 
            authorizer.require_auth();
            let client = ContractCClient::new(&env, &contract_c_address);
            client.authorized_fn_c(&authorizer);
        }
    }
}
 
pub mod contract_c {
    use soroban_sdk::{contract, contractimpl, Env, Address, log};
 
    #[contract]
    pub struct ContractC;
 
    #[contractimpl]
    impl ContractC {
        pub fn authorized_fn_c(_env: Env, authorizer: Address) {
            /// @dev should remove logs before deploying smart contracts
            log!(&env, "Authorizer: {}", authorizer);
 
            authorizer.require_auth();
        }
    }
}

Explanation

#![no_std] This attribute prevents linking to the standard library, making the code lighter and more efficient for Soroban contracts. It's big so we save on size.

use soroban_sdk::{contract, contractimpl, Env, log} Imports stuffs from the Soroban SDK. Env is basic Soroban type, we need it because we can't use the Rust standard library.

#[contract] Marks the struct as a Soroban smart contract. Soroban smart contracts are defined as Rust structs.

#[contractimpl] Marks the implementation block as containing contract methods and transforms it to code that Soroban can evaluate directly

ContractA

  • Calls the authorized_fn_b function in ContractB.
  • Authorizes itself as the current contract using authorize_as_current_contract.
  • The authorization includes a SubContractInvocation for ContractC. The SubContractInvocation structure is used to define the details of a subcontract call. The SubContractInvocation specifies the contract address, function name (authorized_fn_c), and arguments (the current contract's address).
  • This allows ContractA to call authorized_fn_c in ContractC on behalf of itself.

ContractB

  • The authorized_fn_b function requires the caller to have authorization.
  • It creates a client for ContractC and calls authorized_fn_c in ContractC, passing the authorizer's address.

ContractC

  • The authorized_fn_c function requires the caller to have authorization.
  • It simply checks the caller's authorization and doesn't perform any additional actions.

Run in Playground

Loading playground...