Soroban Quest - Cross Contract
Tags: soroban, stellar, tutorial
Published: 2022-11-26 16:41:21

We have tackled the Reverse Engineer quest on the [last post](https://dev.to/altug/soroban-quest-reverse-engineer-a95), and are ready for the next quest, Cross Contract.

## Understanding `README.md`

As always, let's check out the `README.md` file on the quest folder.

![README](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ui0pq2qk7fab4ub2a1uc.png)

On this quest, we are tasked with deploying the `CrossContractCall` contract, and use it to interact with the `DataStore` contract we worked on the Auth Store quest.

## Setting Up Our Quest Account

You should be pretty familiar with this concept by now. Use `sq login` if you haven't, and `sq play 4` to start this quest.

## Examining the Contract

Let's check out the contract's source code on `lib.rs` to learn more about this quest.

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

pub trait StorageCallTrait {
    fn inv_get(env: Env, store_id: BytesN<32>, owner: AccountId) -> Bytes;
}

pub struct CrossContractCallContract;

#[contractimpl]
impl StorageCallTrait for CrossContractCallContract {
    fn inv_get(env: Env, store_id: BytesN<32>, owner: AccountId) -> Bytes {
        let storage_client = storage_contract::Client::new(&env, store_id);
        storage_client.get(&owner)
    }
}
```

There are some new concepts on the source code. Let's examine them one by one.

### Contract Import

Previously, we used the `mod` keyword to include the `test.rs` file, or the `error.rs` contract into our code. These files had their source code ready for importing, and we used them for developing our contract.

Using `soroban_sdk::contractimport!` macro is quite different, as we are now importing a precompiled contract. The `soroban_sdk` can use the imported contract's types, interface, and the client in the imported source code this way.

### Contract Traits

We can define traits for contracts using the `trait` keyword. This can be thought as an *abstract* class, meaning that it has the interface but not the source code of its subclasses. On the source code of the `CrossContractCallContract`, we have

```rust
pub trait StorageCallTrait {
    fn inv_get(env: Env, store_id: BytesN<32>, owner: AccountId) -> Bytes;
}
```

which means that every contract that implements the `StorageCallTrait` should implement a function, `inv_get`, which has the parameters `env`, `store_id`, `owner`, and returns a `Bytes` result.

> #### Why Do We Need Traits?
>
> Since interoperability is a basic feature of smart contract blockchains, there are some standards that people use for easier interoperability. Let's say we have a token trait, which implements the `send`, `recv_from`, and `balance_of` traits. If we have such a standard, we can develop a token marketplace for all tokens that use this standard. Actually, most of the tokens you see everywhere implements the [ERC-20 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) created by Ethereum developers!
>
> If we didn't have such standards exist, the exchange developers would have to support every implementation that exist on a smart contract blockchain, which would be harmful for anyone's time, and would raise various security concerns.

After creating a `trait`, we can import it to our contract using

```rust
impl StorageCallTrait for CrossContractCallContract {
    // code implementing inv_get here
}
```

If we fail to implement the `inv_get` function, the Rust compiler would give an error such as

![Trait Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyun74tvswoh38g13kl4.png)

Very handy for developing contracts!

### Overall View

After getting to know about the new concepts on the contract, we can start checking out what the contract does.

There is only one function on the contract implementation, which is `inv_get`

```rust
fn inv_get(env: Env, store_id: BytesN<32>, owner: AccountId) -> Bytes {
        let storage_client = storage_contract::Client::new(&env, store_id);
        storage_client.get(&owner)
    }
```

It simply uses the imported `StorageContract`'s `get` function to return the data that exists on that contract for an address.

## Examining the Test

The `test.rs` file has one test function, `get_cross_call`.

```rust
use crate::{storage_contract, CrossContractCallContract, CrossContractCallContractClient};

#[test]
fn get_cross_call() {
    let env = Env::default();
    let storage_contract_id = env.register_contract_wasm(None, storage_contract::WASM);
    let storage_contract_client = storage_contract::Client::new(&env, storage_contract_id.clone());

    let cross_call_contract_id = env.register_contract(None, CrossContractCallContract);
    let cross_call_contract_client =
        CrossContractCallContractClient::new(&env, cross_call_contract_id);

    let u1 = env.accounts().generate();
    env.set_source_account(&u1);
    storage_contract_client.put(&bytes![&env, 0x48656c6c6f20536f726f62616e21]);

    assert_eq!(
        cross_call_contract_client.inv_get(&storage_contract_id, &u1),
        bytes![&env, 0x48656c6c6f20536f726f62616e21]
    );
}
```

We have an interesting line here, `use crate::{storage_contract, CrossContractCallContract, CrossContractCallContractClient};`, which imports the types from the `lib.rs` file. Since Rust modules are called `crate`s, we import the `crate`s from the contract implementation to use them in tests. Or, we could just use `use super::*` as previous tests did.

> #### Using Specific Classes vs All Classes
>
> Using `use super::*` instead of importing the `crate`s used one by one might look easier, but later could cause some serious headache when the code grows in complexity. If we import **everything** from the parent implementation, we would have to use the names, types, and anything implemented on *that* contract only. For example, we could have problems if we tried to import another implementation of the `storage_contract` with additional functionality for testing. 

Since we have examined lots of tests until now, I think you now understand what this test does. It first deploys the `storage_contract` and the `cross_call_contract` into the testing sandbox we use, then, creates a test user to `put` a value into the `storage_contract`. After `put`ting a value, we can now assert that our `cross_call_contract` returns the same result that was `put` into the `storage_contract`.

## Solving the Quest

Since we are tasked with deploying the `DataStoreContract` again, let's use the commands we used on the Auth Store quest to deploy them using current quest's account.

```bash
cargo build --target wasm32-unknown-unknown --release
soroban deploy --wasm target/wasm32-unknown-unknown/release/soroban_auth_store_contract.wasm
```

After we deploy the `DataStoreContract`, let's invoke the `put` function to put some data into the contract.

```bash
soroban invoke \
--id [the ID of your DataStoreContract] \
--fn put \
--arg [hexadecimal value longer than 10 bytes]
```

![Deploy Data Store and Put Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/peh4mgwbwgk8scwgtwqo.png)

The first part of the quest is done! For the second part, we should first deploy the `CrossContractCallContract` to Soroban Futurenet.

```bash
soroban deploy --wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_call_contract.wasm
```

![Cross Contract Call Contract Deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0z5xoj1y263r5c52tqke.png)

Then, we need to find what the required function arguments are. The first argument we need to supply is `store_id`, which is of type `BytesN<32>`. This corresponds to the `DataStoreContract`'s ID, which consists of 32 bytes, represented in hexadecimal values. The second argument is `owner`, of type `AccountId`. Recall that we used the `AccountId` type when solving the second quest using the `get` function. The idea is the same, we should create the JSON representation of the complex type `AccountId`. We can use the same Python script to find out our raw public key in hexadecimal format.

```bash
soroban invoke \
--id [the ID of your CrossContractCallContract] \
--fn inv_get \
--arg [the ID of your DataStoreContract] \
--arg '{"object": { "accountId": { "publicKeyTypeEd25519": "[your raw public key in hexadecimal format]" } } }'
```

![Successful Inv Get Call](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ub6zne7sd880bdocxm3y.png)

Success! That was quite hard, and you should be proud of yourself. This quest combined almost all of the concepts of previous quests, so your success is well deserved! Use `sq check 4` to claim your reward 🎉!