Soroban Contracts 101 : Cross Contract Call
Tags: soroban, sorobanathon, stellar, smartcontract
Published: 2023-03-18 16:24:42

Hi there! Welcome to my eigth post of my series called "Soroban Contracts 101", where I'll be explaining the basics of Soroban contracts, such as data storage, authentication, custom types, and more. All the code that we're gonna explain throughout this series will mostly come from [soroban-contracts-101](https://github.com/yuzurush/soroban-contracts-101) github repository.

In this eigth post of the series, I'll be covering soroban cross contract call. Cross-contract calls (where one contract calls methods on another contract) are useful for a few reasons in blockchain smart contracts:

Modularity - you can break your system into separate contracts/modules that call on each other, keeping components focused and reusable
Abstraction - contracts can expose a high-level interface that hides internal complexity
Sharing code/logic - common logic/data storage could be in a shared contract
Interoperability - contracts could call into standard contracts to access common blockchain functionality


We're gonna using 2 contract, **Contract A** act as the "called contract", and **Contract B** act as the "calling contract". Contract B gonna calls function on Contract A and handles the return values/errors.

##The Contracts Code
###Contract A Code

```rust
pub struct ContractA;

#[contractimpl]
impl ContractA {
    pub fn add(x: u32, y: u32) -> u32 {
        x.checked_add(y).expect("no overflow")
    }
}
```
Contract A is very simple contract with one function:
- It defines a `ContractA` struct 
- It implements a `contractimpl` on ContractA
- The `add` function:

> - Takes two u32 parameters (`x` and `y`)
> - Uses `checked_add` to add them, which returns an `Option<u32>` (None if there is an overflow)
> - Uses `expect` to panic if there is an overflow (with a message), Otherwise returns the sum

###Contract B Code

```rust
mod contract_a {
    soroban_sdk::contractimport!(
        file = "../../target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm"
    );
}

pub struct ContractB;

#[contractimpl]
impl ContractB {
    pub fn add_with(env: Env, contract_id: BytesN<32>, x: u32, y: u32) -> u32 {
        let client = contract_a::ContractClient::new(&env, contract_id);
        client.add(&x, &y)
    }
}
```
Our ContractB calls into the first contract (ContractA). It:

- Imports the `ContractA` contract as a module (`contract_a`) using a `contractimport`
- Defines a `ContractB` struct
- Implements a `contractimpl` on `ContractB`
- Defines an `add_with` function that:

> - Takes the `Env`, `ContractA` contract ID, and two u32 values (`x` and `y`)
> - Creates a client for `ContractA` using the passed in ID
> - Calls `add` on the `ContractA` client, passing in `x` and `y`
> - Returns the result from the `ContractA` call

##The Test Code

```
#[test]
fn test() {
    let env = Env::default();

    // Register contract A using the imported WASM.
    let contract_a_id = env.register_contract_wasm(None, contract_a::WASM);

    // Register contract B defined in this crate.
    let contract_b_id = env.register_contract(None, ContractB);

    // Create a client for calling contract B.
    let client = ContractBClient::new(&env, &contract_b_id);

    // Invoke contract B via its client. Contract B will invoke contract A.
    let sum = client.add_with(&contract_a_id, &5, &7);
    assert_eq!(sum, 12);
}
```
Our test code used to test `ContractB` contract. It:

- Creates an `Env`
- Registers `ContractA` from its WASM file 
- Registers `ContractB`
- Creates a `ContractB` client
- Calls `add_with` on the `ContractB` client, passing in the `ContractA` ID, `x` and `y` values to `add`
- Asserts that the correct sum is returned

So this test exercises the full cross-contract call flow, and verifies that the correct result is returned.

##Running Contract Tests
Test conducted in contract_b directory

```
cd cross_contract/contract_b
cargo test
```
If the tests are successful, you should see an output similar to:

```
running 1 test
test test::test ... ok
```

##Building The Contract
We need to build both Contract A and Contract B, to build Contract A use the following command:
```
cd .../cross_contract/contract_a
cargo build --target wasm32-unknown-unknown --release
```
To build Contract B use the following command:
```
cd .../cross_contract/contract_b
cargo build --target wasm32-unknown-unknown --release
```
Both .wasm files should be found in the ../target directory:
```
target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm
target/wasm32-unknown-unknown/release/soroban_cross_contract_b_contract.wasm
```
##Deploying The Contract
Unlike previous post, we need to deploy the contract first, instead of invoking the contract WASM files directly.To deploy the contracts use this command :
**Deploy ContractA**
```
soroban contract deploy \
    --wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm \
    --id a
```
**Deploy ContractB**
```
soroban contract deploy \
    --wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_b_contract.wasm \
    --id b
```

##Invoking The Contract
To do cross contract call, we only need to invoke `ContractB` contract, use the following command with Soroban-CLI  :

```
soroban contract invoke \
    --id b \
    -- \
    add_with \
    --contract_id a \
    --x 10 \
    --y 5
```
You should see the following output:

```
15
```

##Conclusion
We explored how to implement and use cross-contract calls in Soroban. Cross-contract calls allow contracts to execute methods on other contracts. Overall, cross-contract calls are a useful pattern for contract modularity and abstraction, if designed and tested carefully to avoid risks. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.