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.