Skip to main content

Build the app

Now that we've installed Cubist, we can build our app! First, we describe how to build the app; then, we describe what Cubist does when we run build.

note

The following instructions assume that you have installed Cubist.

Build instructions

In the cubist-config.json we specify the target chains on which each contract should run:

{
...
"targets": {
"ethereum" : {
"files": ["./contracts/StorageReceiver.sol"]
},
"polygon": {
"files": ["./contracts/StorageSender.sol"]
}
},
...
}

To change chains, simply alter the configuration file to specify a different chain.

note

Cubist only allows you to assign chains at a per file granularity---not per contract. If you have two contracts in the same file and want to deploy them on different chains, you'll have to put each contract in its own file.

Now that we've specified where our contracts should run, we build the project using Cubist:

cubist build

What's going on behind the scenes

After we run build, Cubist generates files in two places. It:

  1. Creates a build directory that contains the compiled contracts (and more).
  2. Adds files to the Rust src directory. These files allow us to interact with our smart contracts from within our Rust application.

We walk through the build dir and the src dir next.

The build directory

When you run cubist build, Cubist generates new files in the build directory, organized with one directory per target chain:

build
├── ethereum
│   ├── artifacts // Compiled contracts that will run on ethereum
│ │ ├── ...
│   └── contracts // Source files of original and shim contracts on ethereum
| └── ...
└── polygon
├── artifacts // Compiled contracts that will run on polygon
│   ├── StorageReceiver.sol
│   │   └── StorageReceiver.json // ABI (and more) for the shim StorageReceiver contract
│   └── StorageSender.sol
│   └── StorageSender.json // ABI (and more) for the "normal" StorageSender contract
└── contracts
├── StorageReceiver.bridge.json // Configuration for the Cubist relayer
├── StorageReceiver.sol // Shim contract source
└── StorageSender.sol // Original StorageSender source

Within each target directory---here, the polygon and ethereum directories---Cubist saves:

  1. the ABIs produced when compiling the contracts with solc (within the artifacts directory)
  2. the original and shim source files (in the contracts directory).

In this example, the interesting files are in the polygon directory. That's because the StorageSender contract, deployed on Polygon, makes a cross-chain call by executing receiver.store(..) on a StorageReceiver contract deployed on Ethereum. As a result, Cubist must generate a StorageReceiver shim on Polygon for the the StorageSender contract to interact with; that shim will emit events anytime StorageSender calls a StorageReceiver function, and the off-chain Cubist relayer will relay those events to the real StorageReceiver contract on Ethereum. Here's an example of the generated shim code, which lives in build/polygon/contracts/StorageReceiver.sol:

contract StorageReceiver {

event __cubist_event_StorageReceiver_store(uint256 num);

...

function store(uint256 num) public onlyCaller {
emit __cubist_event_StorageReceiver_store(num);
}

}

The build also generates the information that lets the off-chain relayer do its job; this information is in the StorageReceiver.bridge.json file.

danger

You should not modify any of the files in the build directory. These files are automatically generated, so your changes may get overwritten.

The src directory

Cubist also generates the code that allows our Rust dapp to interact with the StorageReceiver and StorageSender contracts. After running build, our source directory looks like this:

src
├── main.rs // Our dapp code
├── cubist_gen.rs // Cubist-generated bindings for interacting with both contracts
└── cubist_gen
├── storage_receiver.rs // Cubist-generated bindings for StorageReceiver
└── storage_sender.rs // Cubist-generated bindings for StorageSender

The code in src/main.rs is pre-existing (because this is an example!), but you'll likely write your app code after running cubist build. Your app code will use the bindings that Cubist generates in cubist_gen and cubist_gen.rs; we'll walk through the functions in that file and directory in step four, when we run the example application.