Run the app
Let's start the chains and relayer and then run our token bridge!
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
Let's deploy and use our token bridge from Rust (and the command line!).
The src/cli.rs
file defines command line options for the
application: deploy
, view balances
, buy
, and sell
. Open up
the file to follow along, and we'll walk through the deploy
and
buy
commands in more detail.
(Advanced) deployment
First, let's deploy our TokenSender
and ERC20Bridged
contracts. This is a little more complex than previous
examples, since there's a circular dependency between
the two contracts: the TokenSender
stores a reference to
ERC20Bridged
, while ERC20Bridged
stores a reference to
TokenSender
. This circular reference is important because
it lets the token bridge "go both ways"; we can use it to
both buy and sell native tokens and ERC20 tokens.
One way of handling circular dependencies is to one contract, deploy another with the first contract's address, and then update the first contract to include the second contract's address. We don't have to do that here. Instead, we're going to break the circular dependency by explicitly deploying our contracts and their generated shims separately.
This strategy works because the shims don't hold (circular) references to any other contracts; all they do is emit events when they are called. We can take advantage of the shims' simplicity to successfully deploy all of our contracts in sequence---without having to update them later. Here's the strategy we'll use:
- Deploy
ERC20Bridged
's shim (on Ethereum) - Deploy
TokenSender
to Ethereum (and its shim to Polygon), giving the constructor the address ofERC20Bridged
's shim - Deploy the real
ERC20Bridged
contract to Polygon, giving it the address of theTokenSender
shim on Polygon.
The deploy
function follows this strategy:
// Create new Cubist instance
let cubist = cubist().await?;
// Deploy ERC20Bridge shim
let e20b = ERC20Bridged::deploy_shims().await?;
// Get the address of the shim (on Ethereum, TokenSender's target chain)
let e20b_shim_addr = e20b.addr(TokenSender::target());
// Deploy TokenSender with the address of the ERC20Bridged shim (on Ethereum)
// (Also deploys TokenSender shim on Polygon)
let toks = TokenSender::deploy(e20b_shim_addr).await?;
// Get the address of the TokenSender shim (on Polygon, ERC20Bridged's target chain)
let toks_shim_addr = toks.addr(ERC20Bridged::target());
// Deploy ERC20Bridged with the address of the TokenSender shim (on Polygon)
let _e20b =
ERC20Bridged::deploy(("FooBarBaz".to_owned(), "FBB".to_owned(), toks_shim_addr)).await?;
First it deploys the ERC20Bridged
shim to Ethereum,
then TokenSender
(to Ethereum) and its shim
(to Polygon), and finally ERC20Bridged
(to Polygon).
We can deploy the contracts from the command line with:
cargo run --bin cli deploy
The first line of output reflects that we've deployed TokenSender
with
constructor argument 0xc059..., which is the address of the ERC20Bridged
contract's shim. The second line tells us that that we've deployed
ERC20Bridged
to swap FBB, and given it constructor argument
0xa1e8..., which is the address of the TokenSender
contract's shim.
The last two lines result from when_bridged
, which checks that the
Cubist relayer service is up and running.
Buy
The buy
command lets us transfer native (wei) tokens on Ethereum
for FBB tokens on Polygon (with a conversion rate of 99.9%). Let's
look at the buy
command documentation:
/// Mint some FBB tokens. This is done by calling 'TokenSender' and specifying a WEI amount and
/// an address to receive minted FBB. Conversion rate is 0.999, which means that 'TokenSender'
/// will send a request to 'ERC20Bridged' to mint FBB tokens (in the amount of 99.9% of the
/// received WEI amount) and award them to the specified recipient.
Buy(BuyArgs)
The buy
command's arguments are as follows:
struct BuyArgs {
/// Payment in WEI. The amount of minted FBB will be equal to 99.9% of that.
#[clap(index = 1)]
payment_wei: u64,
/// Receiver of newly minted FBB. Either a hex address (starting with '0x')
/// or account index on 'ERC20Bridged' chain. Defaults to the address
/// of the first (index 0) account on 'ERC20Bridged' chain.
#[clap(index = 2, default_value = "0")]
fbb_receiver: String,
}
The buy command is extremely simple:
// Initialize the TokenSender contract from its deployment receipt
let tok = TokenSender::deployed()
.await
.context("Contracts not deployed; call 'deploy' first")?;
// Get one of the pre-created accounts by index
// This is an app specific function defined in cli.rs (and thus not Cubist-generated)
let receiver = to_address(&args.fbb_receiver, erc20_accounts().await?)?;
// Call the TokenSender's bridgeSend function via the Cubist bindings
let mut call = tok.bridge_send(receiver);
// Attach the user-specified amount of wei to the transaction
call.tx.set_value(args.payment_wei);
// Actually make the bridgeSend call
call.send().await?.await?;
All it does is invoke TokenSender
's bridgeSend
via the Cubist bindings,
with the user-specified amount of payment_wei
and receiver account.
Similar to deploy, we can run the buy
command via the command line:
cargo run --bin cli buy 500000000000000000
This will send half an ETH to the default receiver account.
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.