Skip to main content

Create a Basic Applet

Generally the life-cycle of a Weilliptic Applet, a WeilChain smart-contract, consists of the following steps:

  1. Specifying the Applet's interface using WIDL.
  2. Generate server bindings for the language of your choice using the WIDL compiler.
  3. Fill in the bindings with business logic.
  4. Compile the applet into a WASM module and deploy on the WeilChain platform.
  5. Use the exposed methods of the Applet using either the Weilliptic CLI, or Wallet, or DApp.

In this tutorial we'll implement a basic Counter Applet.

If you prefer a video explanation, Here is a complete end-to-end walkthrough of writing, deploying and interacting smart contracts on WeilChain.

warning

The steps are slightly different depending on the language you are using, so make sure to chose the right language now.

You have chosen Rust!

Preparations

wadk and wadk-utils will be officially available on crates.io as part of the testnet launch.

This tutorial assumes that

To start, create a new project:

cargo new counter_rust --lib
cd counter_rust

Contract Specification

Create file counter.widl, with the following contents.

counter.widl
interface Counter {
query func get_count() -> uint;
mutate func increment()
}

This file defines a service called Counter with two methods, get_count and increment().

Observe that the definition of get_count() starts with query but the definition of increment() starts with mutate. These indicate if the operation will merely consult the state or can potentially update it.

query

query methods will not update the state of the Applet. Even if your method does update the state, the change will not be persisted.

mutate

mutate methods may update the Applet's state. Any changes to the state will be made durable at the end of the execution.

Observe as well that the definition of the last method is not terminated by a ;.

Server-Side Bindings

Server-side bindings act as a proxy between the host and the Applet. Server-side bindings generation may be performed manually, but this approach is time-consuming and error prone. Hence we use the WIDL compiler to generate the bindings.

widl generate counter.widl server rust

A file named bindings.rs should have been created. It contains a skeleton for the Applet being developed, annotated with macros that will be expanded to the actual bindings during the compilation.

bindings.rs
use serde::{Deserialize, Serialize};
use weil_macros::{constructor, mutate, query, smart_contract, WeilType};

pub trait Counter {
fn new() -> Result<Self, String>
where
Self: Sized;
async fn get_count(&self) -> usize;
async fn increment(&mut self);
}

#[derive(Serialize, Deserialize, WeilType)]
pub struct CounterContractState {
// define your contract state here!
}

#[smart_contract]
impl Counter for CounterContractState {
#[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{
unimplemented!();
}


#[query]
async fn get_count(&self) -> usize {
unimplemented!();
}


#[mutate]
async fn increment(&mut self) {
unimplemented!();
}
}

Observe that a trait with the name of the service, Counter, has been defined and that it defines three methods, two of which match the methods defined in the WIDL file, get_count and increment. The third method is a constructor for the service.

We'll fill in the logic for these methods later. For now, let's compile the contract.

Compiling the contract

In order to compile the skeleton, first, copy bindings.rs its contents to file src/lib.rs.

cat bindings.rs > src/lib.rc
rm bindings.rs

Next, update the Cargo.toml file to be as follows and include needed dependencies.

Cargo.toml
[package]
name = "counter"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0.219"
serde_json = "1.0.140"
anyhow = "1.0.97"

weil_macros = "0.1" # as a subcrate of wadk on crates.io
weil_rs = "0.1" # as a subcrate of wadk on crates.io
weil_contracts = "0.1" # as a subcrate of wadk on crates.io
wadk-utils = "0.1" # Separate public crate on crates.io

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

Finally compile the contract into a WASM module.

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

You should now have file target/wasm32-unknown-unknown/release/counter.wasm, which is the body of the Applet. However, it is not useful in its current state and until we fill in the logic of the methods.

Filling in the logic

Open contract file and go to the definition of struct/class with the state. This is struct/class, which will contain the Applet's state, is still empty. Modify it to include a single field with name count and integer type.

src/lib.rs
...
#[derive(Serialize, Deserialize, WeilType)]
pub struct CounterContractState {
count: usize;
}
...

Now locate the constructor and change its contents as follows, so it sets the initial state of the Applet, when it is deployed.

...
#[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{
Ok(CounterContractState { counter: 0 })
}
...

Finally, locate methods to get the counter's value and to increment it, and add the corresponding logic :

...
#[query]
async fn get_count(&self) -> usize {
self.count
}


#[mutate]
async fn increment(&mut self) {
self.count += 1
}
...

Observe that the query and mutate keywords used in the WIDL file became macros in the Rust definition and that the query method receives a reference to the Applet state, while the mutate method receives a mutable reference.

Now compile the Applet again to ensure that your file is correct.

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

Next steps

Congratulations! You should have your Applet ready to be deployed, which you can do by following the tutorial Deploy and use a WeilChain Applet.