Interactors Overview
Overview
Interactors are Rust programs designed to facilitate interactions with smart contracts on the blockchain. They are essential for system testing and managing the setup and execution of smart contracts in a live blockchain environment. Making use of unified syntax, proxies, and the capability to be autogenerated using sc-meta all snippets
, interactors streamline the interaction process, providing a quick and efficient solution for system testing, seamlessly integrating with the development workflow.
Key Features
- System testing: Perform comprehensive scenario-based testing on the actual blockchain to validate contract behavior under real-world conditions.
- Contract setup and calling: Interactors simplify the process of setting up (deploy/upgrade) and interacting with smart contracts (calling smart contract endpoints, query), making it easier for Rust developers to deploy and manage their contracts on the blockchain.
Before using interactors, make sure you have the following setup:
Rust
programming language installed on your system:- get
rustup
:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- choose default toolchain
rustup update
rustup default stable
rustup target add wasm32-unknown-unknown- check installed toolchains
rustc --version
rustup show- get
- MultiversX Rust framework version
0.50.0
or higher. multiversx-sc-meta
tool installed for generating an interactor template.
Workflow
When interacting with a contract from a Rust testing environment, the following steps are typically required:
- Build the contract: Compile and build the smart contract.
- Create the proxy file: This can be autogenerated using
sc-meta all proxy
, which requires a properly configuredsc-config.toml
for custom proxy paths. Adding a proxy path into thesc-config.toml
file ensures that a proxy will be created at that path. - Create a new project and import the proxy: Set up a new project and import the generated proxy.
- Write code using unified syntax: Utilize the proxy and unified syntax to write the interaction code.
- Run the code and send transactions: Execute the code to send transactions, either against the Rust VM or on the actual blockchain.
However, thanks to the snippets generator, the workflow for the Rust interactors is very straightforward. The sc-meta
tool automates all the previously mentioned steps, generating the necessary boilerplate code for building an interactor for the current contract.
To generate an interactor, simply run sc-meta all snippets
in the root folder of the contract.
adder % sc-meta all snippets
This command performs the following actions:
- Compiles the contract (still needs to be built).
- Creates a
sc-config.toml
configuration file. If the file already exists, another proxy path is inserted (the path to the interactor) without changing the previous setup. - Generates a proxy based on the configuration path from the file (
proxy.rs
). - Creates an async Rust program with a CLI based on the contract endpoints found in the proxy. Returns typed results for further processing. The new project will be under the newly created
interactor
folder, inside the contract root folder. - Creates
config.toml
andconfig.rs
files used to setup and parse the chain simulator config.
Make sure to include the newly generated interactor into the existing file hierarchy to be able to compile and run the code.
Scenarios & CLI
Given the customizable nature of real-life scenarios, the generated interactor provides only a minimal starting point for your Rust program. The generated interactions include:
- Separate functions for calling each endpoint of the smart contract (based on the proxy).
- A CLI with distinct commands for each specific endpoint call.
In order to run the code and make use of the CLI, simply call cargo run <endpoint_name>
in the root of the interactor
folder.
interactor % cargo run deploy
If, however, the CLI needs to cover more complex scenarios, the generated async functions can be used directly and composed with each other. If a testing approach is preferred instead of the CLI, then we can make use of the tokio::test
feature, and we could write a system test as such:
#[tokio::test]
async fn test_full_adder_scenario() {
let mut contract_interactor = ContractInteract::new().await;
contract_interactor.deploy().await; // deploy adder
contract_interactor.add(2u64).await; // add 2
let sum = contract_interactor.sum().await; // fetch sum
println!("sum is {:#?}", sum);
}
If ran, this test will run the scenario and will produce transactions on the actual blockchain, depending on the environment setup (devnet, testnet and mainnet).
Running the deploy
command for every contract will create a new state.toml
file, where information about the contract state will be written for consistency.
Traces
To enhance testing support, any instance of the Interactor
struct implements the .with_tracer(...)
method. This method enables the interactor to use a tracer
and output the result to the specified path. A tracer records every action executed through the interactor, mapping each action to a mandos step
.
let mut interactor = Interactor::new(config.gateway_uri())
.await
.use_chain_simulator(config.use_chain_simulator())
.with_tracer("test_trace.scen.json")
.await;
This ensures the tracer is active, and a file containing all the mandos steps for the specific scenario will be created at the specified path. Having a quickly generated mandos test that encompasses the entire scenario is highly beneficial. After generating the mandos test, you can swiftly test the same scenario against both the Rust and GO VMs.