Run the Example
First go through the Setup process to get your development environment configured, then clone thev23.0.0 tag of soroban-examples repository:
cargo test.
contract_a, contract_a_interface, and contract_b. The first two crates in the workspace contain no tests, but the third crate should give you the following output:
Code
- Contract A Interface
- Contract A
- Contract B
- Contract B Test
workspace/contract_a_interface/src/lib.rs
How It Works
There are three crates that are part of the workspace.contract_a_interfacecontains a trait,ContractAInterface, that only serves as a place to define the interface trait for Contract A.contract_acontains a smart contract,ContractA, that implements logic, and conforms to theContractAInterfacetrait.contract_bis another smart contract, implementing a different interface, and makes a call toContractA, and cross-calls thecontract_afunction. This is also the only crate in the workspace with defined tests.
Contract A Interface: The Trait
Thecontract_a_interface crate defines a trait containing a contract interface. This interface is defined separate from the implementation and only defines what the exported functions of the implementation.
The interface defines add as the only function the contract will contain. The add function requires two inputs, x and y, both u32 integers, and returns a u32 integer as well.
The use of contractclient as an attribute macro on ContractAInterface means a client will be created, conforming to this interface, that can be used by contracts existing outside of the contract_a_interface crate. As you’ll see later, the client, ContractAClient, is used in the contract_b crate to call the add function.
Contract A: The Logic
Thecontract_a crate contains the implementation for Contract A, defining for each function what it should actually do.
The implementation takes the value of x, along with the value of y, and performs a checked_add, returning the sum of both numbers (while avoiding an overflow error).
The
add function doesn’t require an Env argument. That’s totally fine! Env has many useful features that are available to your smart contracts, if you need them. But, you’re not at all required to use it, if you don’t.ContractAInterface is to use the interface, define a ContractA struct, and impl the interface for that struct.
This crate uses the contractimpl attribute macro on the ContractA implementation, making the add function public and invocable by others on the Stellar network.
Contract B: The Invocation
Now that we’ve created a trait incontract_a_interface, and implemented it in contract_a, we can use the contract_b crate to invoke the add function and get the sum of our integers.
We’re creating and implementing an entirely new contract, ContractB. We’re skipping the trait, and getting right to the contract itself.
Defining the contract interface with a
trait separately is optional. It’s a great way to share the interface of a contract, and a contract client, with multiple contracts in a multi-contract workspace.ContractB has one function, add_with, that requires three arguments:
contract: Address- The address of a contract which implements that required client interface,ContractAInterface.x: u32andy: u32- The two numbers we want to (safely) compute the sum of.
ContractB invokes ContractA’s add function, returning its value back to the original invoker. It’s a bit of a long round-trip for this simple example, but it illustrates a really powerful way you can separate out interface/trait definitions from contract logic and share it and its client in a multi-contract workspace.
Practical Use-Case Examples
Beyond this simple example, this technique is versatile and useful. For example, this strategy could be used to:- Create and reference a standardized, consistent token interface.
- Reuse a single interface that you want to incorporate across many different contracts.
Build the Contracts
To build the contracts into a set of.wasm files, use the stellar contract build command. Both workspace/contract_a and workspace/contract_b will be built, and you can use a single command, since our workspace defines its members in the Cargo.toml file:
.wasm files should be found in the workspace/target directory:
stellar-cli knows the contract_a_interface is not intended to be compiled into a .wasm, because the contract_a_interface’s Cargo.toml has its crate-type configured as rlib (rust library). Nice!
Run the Contract
If you havestellar-cli installed, you can invoke contract the functions. Both contracts must be deployed.
- macOS/Linux
- Windows (PowerShell)
ContractB’s add_with function, passing in ContractA’s address for contract, and integer values for x and y (e.g. as 5 and 7).
- macOS/Linux
- Windows (PowerShell)