Router-wasm-bindings

The wasm bindings for building CosmWasm smart contracts that can run on the Router chain.

Prerequisites

Before starting, make sure you have rustup along with a recent rustc and cargo version installed. Currently, we are testing on 1.62.1+.

And you need to have the wasm32-unknown-unknown target installed as well.

You can check that via:

```sh rustc --version cargo --version rustup target list --installed

if wasm32 is not listed above, run this

rustup target add wasm32-unknown-unknown ```

Context

On the Router chain, We can build two types of contracts. - Contracts that are not interacting with cross-chain contracts. - Contracts that are back and forth sending the data request to cross-chain contracts.

To build second type of contracts that are interacting with other chains, the user/ applications needs to implement router-wasm-binding crate.

```sh

add the following line in the cargo.toml [dependencies] section

router-wasm-bindings = "0.1.14" ```

How to use the Router-Wasm-Bindings

To implement cross-chain interoperability, the contract needs to implement the following functionality - SudoMsg for handling incoming requests from the other chains - RouterMsg to send a request to the other chains.

The Contract can write the intermediate business logic in-between the incoming request and outbound request. While writing the intermediate business logic, the developer can convert single or multiple incoming requests into single or multiple outbound requests.

Also, while creating requests to other chains, the contract can be developed in such a way that multiple requests can be generated to different chains.

You can find examples of different scenarios in the cw-bridge-contracts repository.

[SudoMsg]

The SudoMsg is an enum and it has two different message types.

1) HandleInboundReq 2) HandleOutboundAck

In the following code snippet, we added the details at the field level of the SudoMsg. This will helps us in building an understanding of the data that will be coming either in the inbound request or in the outbound acknowledgment request.

```rust

[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]

[serde(renameall = "snakecase")]

pub enum SudoMsg { // Sudo msg to handle incoming requests from other chains HandleInboundReq { // the inbound initiator application contract address sender: String, // inbound request src chain type chaintype: u32, // inbound request src chain id sourcechainid: String, // the inbound request instructions in base64 format payload: Binary, }, // Sudo msg to handle outbound message acknowledgment HandleOutboundAck { // the outbound request initiator router address outboundtxrequestedby: String, // outbound request destination chain type destinationchaintype: u32, // outbound request destination chain id destinationchainid: String, // outbound batch request nonce outboundbatchnonce: u64, // outbound request execution code info executioncode: u64, // outbound request execution status info executionstatus: bool, // outbound request contract calls individual execution status execflags: Vec, // outbound request contract calls individual execution response execdata: Vec, }, } ```

The sudo function is one of the entry-point in a cosmwasm contract. It can be called internally by the chain only. In Router Chain, the developer needs to implement this sudo function to receive an incoming request. Here, in the following code snippet, we have shown the sample sudo function implementation.

Developers can have any sort of business logic inside the handle_in_bound_request and handle_out_bound_ack_request functions.

```sh

import router binding message

use routerwasmbindings::{RouterMsg, SudoMsg};

[cfgattr(not(feature = "library"), entrypoint)]

pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult> { match msg { # Sudo msg to handle incoming requests from other chains SudoMsg::HandleInboundReq { sender, chaintype, sourcechainid, payload, } => handleinboundrequest(deps, sender, chaintype, sourcechainid, payload), # Sudo msg to handle outbound message acknowledgment SudoMsg::HandleOutboundAck { outboundtxrequestedby, destinationchaintype, destinationchainid, outboundbatchnonce, executioncode, executionstatus, execflags, execdata, } => handleoutboundackrequest( deps, outboundtxrequestedby, destinationchaintype, destinationchainid, outboundbatchnonce, executioncode, executionstatus, execflags, execdata, ), } } ```

[RouterMsg]

The RouterMsg is an enum type inside the router-wasm-bindings. It contains one custom message type.

1) OutboundBatchRequests

In the following code snippet, we have added one implementation of OutboundBatchRequests. This message is used to create an outbound request. In the outbound request, we can specify the destination chain id & type, the contract addresses & instructions, the request expiry timestamp, the atomicity flag, etc.

```rust // import router binding message use routerwasmbindings::{RouterMsg, SudoMsg}; use routerwasmbindings::types::{ ChainType, ContractCall, OutboundBatchRequest, OutboundBatchResponse, OutboundBatchResponses, };

let address: String = String::from("destinationcontractaddress"); let payload: Vec = let payload: Vec = b"sample payload data".tovec(); // Single Outbound request with single contract call let contractcall: ContractCall = ContractCall { destinationcontractaddress: address.clone().intobytes(), payload, }; let outboundbatchreq: OutboundBatchRequest = OutboundBatchRequest { destinationchaintype: ChainType::ChainTypeEvm.getchaincode(), destinationchainid: String::from("137"), contractcalls: vec![contractcall], relayerfee: Coin { denom: String::from("route"), amount: Uint128::new(100000u128), }, outgoingtxfee: Coin { denom: String::from("route"), amount: Uint128::new(100000u128), }, isatomic: false, exptimestamp: None, }; let outboundbatchreqs: RouterMsg = RouterMsg::OutboundBatchRequests { outboundbatchrequests: vec![outboundbatchreq] };

let res = Response::new() .addmessage(outboundbatch_reqs); Ok(res)

```

Compiling and running tests

Now that you created your custom contract, make sure you can compile and run it before making any changes. Go into the repository and do:

```sh

this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOURNAMEHERE.wasm

cargo wasm

this runs unit tests with helpful backtraces

RUST_BACKTRACE=1 cargo unit-test

auto-generate json schema

cargo schema ```

Understanding the tests

The main code is in src/contract.rs and the unit tests there run in pure rust, which makes them very quick to execute and give nice output on failures, especially if you do RUST_BACKTRACE=1 cargo unit-test.

We consider testing critical for anything on a blockchain, and recommend to always keep the tests up to date.

Generating JSON Schema

While the Wasm calls (instantiate, execute, query) accept JSON, this is not enough information to use it. We need to expose the schema for the expected messages to the clients. You can generate this schema by calling cargo schema, which will output 3 files in ./schema, corresponding to the 3 message types the contract accepts.

These files are in standard json-schema format, which should be usable by various client side tools, either to auto-generate codecs, or just to validate incoming json wrt. the defined schema.

Preparing the Wasm bytecode for production

Before we upload it to a chain, we need to ensure the smallest output size possible, as this will be included in the body of a transaction. We also want to have a reproducible build process, so third parties can verify that the uploaded Wasm code did indeed come from the claimed rust code.

To solve both these issues, we have produced rust-optimizer, a docker image to produce an extremely small build output consistently. The suggested way to run it is this:

sh docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer:0.12.6

Or, If you're on an arm64 machine, you should use a docker image built with arm64. sh docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer-arm64:0.12.6

We must mount the contract code to /code. You can use an absolute path instead of $(pwd) if you don't want to cd to the directory first. The other two volumes are nice for speedup. Mounting /code/target in particular is useful to avoid docker overwriting your local dev files with root permissions. Note the /code/target cache is unique for each contract being compiled to limit interference, while the registry cache is global.

This is rather slow compared to local compilations, especially the first compilation of a given contract. The use of the two volume caches is very useful to speed up following compiles of the same contract.

This produces an artifacts directory with a PROJECT_NAME.wasm, as well as checksums.txt, containing the Sha256 hash of the wasm file. The wasm file is compiled deterministically (anyone else running the same docker on the same git commit should get the identical file with the same Sha256 hash). It is also stripped and minimized for upload to a blockchain (we will also gzip it in the uploading process to make it even smaller).