Skip to main content

Run the app

We're going to use Cubist to set up the local chains and the Cubist trusted relayer. Then, we'll interact with our multi-producer, multi-consumer app from within Rust.

Start Cubist chains and relayer

Now that we've built our contracts, we want to deploy them and then interact with them. We're going to do this locally; the config (cubist-config.json) already specifies the URLs on which the chains will run. The app config will look something like this:

{
...
"network_profiles": {
"default": {
"ethereum": { "url": "http://127.0.0.1:8545" },
"polygon": { "url": "http://127.0.0.1:9545" }
}
}
}

You can alter the config to specify other URLs if you choose.

To start both the local chains and the Cubist relayer, use:

cubist start

Note that this command may take a while (i.e., a minute) the first time you run it. This is because Cubist needs to download and build the local chain running services before it can start the chains.

Once you've run cubist start, you'll see output that looks something like this:

Launching chains
ethereum ✔ [ 0s] [....................] http://localhost:8545/
polygon ✔ [ 1s] [....................] http://localhost:9545/ All servers available
Watching <path>/cubist-deploy dir

This output shows where the localnets are running (e.g., localhost:8545), and how long they took to become initialized and available. The output also lets us know that the local relayer is successfully watching for the events that tell it to relay information from one chain to another.

tip

Alternatively, you can start the chains and the relayer seperately with cubist chains start and cubist relayer start. For more information on these and other Cubist CLI commands, try cubist help, or check out the CLI reference.

caution

Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!

Run the app

Once again, run using:

cargo run

This will invoke the main function in src/main.rs; for examples of interactive, command-line Rust applications, see the storage app and the token bridge app. Now let's take a look at the main function and see how it deploys contracts and interacts with them.

Deploy

First, we create a new cubist instance and deploy the receiver contracts:

let cubist = cubist().await?;    
...
let r1 = R1::deploy(()).await?;
let r2 = R2::deploy(()).await?;

Next, we deploy the channel:

let ch = Channel::deploy((r1.addr(Channel::target()), r2.addr(Channel::target()))).await?;

Recall that the channel constructor takes two arguments, the addresses of both receiver contracts. In this example, though, the Channel contract is deployed on an Avalanche subnet, while the receivers are deployed on Avalanche and Polygon! Never fear. Because the channel communicates cross-chain with both receivers, Cubist automatically generates shim contracts for the receivers on the Avalanche subnet. We're able to get the addresses of the receivers by calling addr(Channel::target()): this call gets the address of the (shim) contracts on Channel's target chain (an Avalanche subnet). Now that we've deployed the receiver contracts, we can use the same pattern to deploy the senders:

println!("Deploy producers");
let s1 = S1::deploy(ch.addr(S1::target())).await?;
let s2 = S2::deploy(ch.addr(S2::target())).await?;

Great! Now everything's deployed. Finally, before trying to invoke cross-chain calls, we can make sure that the Cubist relayer is running:

assert_eq!(true, cubist.when_bridged(None).await);

Interact

Let's try invoking some smart contract functions from Rust:

let num1 = U256::from(1);
s1.send(num1).send().await?.await?;

We use the ethers library to convert from Rust types to Solidity types using U256::from. Then, we send from the first sender contract using a Cubist-generated binding for the send smart contract function. Next, we try retrieving from both receivers and the channel up to fifteen times:

for i in 0..15 {
let msg = format!("({i}) ch: {:?}, r1: {:?}, r2: {:?}",
ch.retrieve().call().await?,
r1.retrieve().call().await?,
r2.retrieve().call().await?);
println!("{}", msg);
if msg.ends_with("1, r1: 1, r2: 1") {
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}

Once the number one has been stored to both receivers, we break out of the retrieving loop.

The rest of the main function sends and receives another number in a similar way.

Shut down

Running cubist stop will shut down both the chains and the relayer. Once the chains and the relayer are shut down, trying to call contract functions from within your dapp will result in errors.