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.
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.
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.