Skip to main content

Writing a Smart Contract

A Mazzaroth Smart Contract is a WebAssembly binary that defines the functions that may be called by transactions submitted to the channel and how data is stored in state for that channel. There are many different ways to write code that can be compiled to WebAssembly. The requirement for it to be considered a Mazzaroth Smart Contract is that it includes an entry function and handles the input object correctly.

We currently support Mazzaroth Smart Contracts written in Rust by leveraging the mazzaroth-rs library with support for more languages on the horizon.

Rust Setup

To get started you should first install the standard rust toolchain. Instructions for setting up Rust can be found here.

After installing the rust toolchain you will need to add the wasm target to compile your contract. With rustup you can use the following command to add the wasm32-unknown-unknown target:

rustup target add wasm32-unknown-unknown

With the toolchain installed you can begin creating your contract.

The quickest way to do this with Cargo is the following command:

cargo init --lib

This will initialize a project structure for a rust library including a Cargo.toml manifest which we will use to add dependencies and set build configurations and the lib.rs file which will contain the majority of the contract setup.

Cargo.toml Manifest Setup

In the Cargo.toml add the following lines under [dependencies] to use the required Mazzaroth dependencies that help build your contract:

mazzaroth-rs = "0.8.1"
mazzaroth-rs-derive = "0.8.1"
mazzaroth-xdr = "0.8.2"

The mazzaroth-rs and mazzaroth-rs-derive dependencies include bindings needed to interact with the Mazzaroth VM when compiled to WebAssembly. The mazzaroth-xdr dependency is required as part of the mazzaroth-rs macro that serializes values passed to and from the runtime.

In addition to the dependencies you should add the following section to the manifest:

[lib]
crate-type = ["cdylib"]

This just specifies that a dynamic system library will be produced and is currently recommended when compiling to WebAssembly.

The last addition to the Cargo.toml is a release profile section that optimizes the size of the produced binary:

[profile.release]
panic = "abort"
lto = true
opt-level = "z"
overflow-checks = true
codegen-units = 1

Contract Setup

The contract code itself will be contained in the /src/lib.rs file.

For a small starting point you can use the following example to start your contract:

extern crate mazzaroth_rs;
extern crate mazzaroth_rs_derive;

use mazzaroth_rs::external::{log, transaction};
use mazzaroth_rs::ContractInterface;
use mazzaroth_rs_derive::mazzaroth_abi;

#[no_mangle]
pub fn entry() {
std::panic::set_hook(Box::new(mazzaroth_rs::external::errors::hook));

// Creates a new instance of the ABI generated around the Contract
let mut contract = HelloWorld::new(Hello {});

let args = transaction::arguments();

// Execute calls one of the functions defined in our contract
// Input for the function to call and it's params comes from the Runtime
// If an error is returned panic can log the value using the panic hook set above
match contract.execute(&args) {
Ok(val) => transaction::ret(val),
Err(val) => panic!("Error returned from execute: `{:?}`", val),
}
}

#[mazzaroth_abi(HelloWorld)]
pub trait HelloWorldContract {
// Simply say hello :)
#[readonly]
fn hello(&mut self) -> String;
}

pub struct Hello {}

impl HelloWorldContract for Hello {
fn hello(&mut self) -> String {
log("Hello World!".to_string());
return "Hello World!".to_string();
}
}

Compiling the contract

You can compile the contract into a binary and the associated ABI file by using this cargo command:

cargo build --release --target wasm32-unknown-unknown

The wasm binary will be produced at target/wasm32-unknown-unknown/release using the name of your package from the Cargo.toml file.

The ABI file will be produced at target/json using your trait name.

Both of the these files will be used when deploying your contract to a channel as explained in the next section.