A must-see for newbies! How Rust Beginners Write Gear Smart Contracts (1)

Time:2022-8-8

For the prerequisite knowledge of Gear contract, you can first understand this article:Gear contract revealed.

This article will mainly explain how to use Rust to create a simple decentralized application on the Gear blockchain network.

Let's take a voting application as an example to study the Gear smart contract infrastructure and learn how to use the program's Actor model architecture, process messages, and interact with state.

This article aims to demonstrate how easy and convenient it is to create applications on the Gear platform.

Let's start with Rust

Rust is a multi-programming paradigm language focused on safety and performance. It is built with speed and efficiency in mind, providing abstractions and functional features at zero cost. For many developers, it solves common problems with other low-level languages ​​such as C and C++.

For why Gear uses Rust, see this article: Why does Gear use Rust?

In addition, Rust has a significant advantage: Rust code can be compiled to wasm.

So, let's start installing Rust on your computer.

First, open your favorite terminal and run the installer:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Let's install the toolchain to compile the code:

rustup toolchain add nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
cargo install --git https://github.com/gear-tech/gear wasm-proc

With everything in place, it's time to start our first program!

Create a Rust program

Let's create the voting application project with the help of the cargo command:

cargo new voting-app --lib

Take a look at the project structure:

voting-app/
├── Cargo.toml
└── src
└── lib.rs

Cargo.toml is the project manifest in Rust, it contains the metadata needed to compile the project and some necessary dependencies:

[package]
name = "voting-app"
version = "0.1.0"
authors = ["Gear Technologies"]
edition = "2018"
license = "GPL-3.0"

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

[dependencies]
gstd = { git = "https://github.com/gear-tech/gear", features = ["debug"] }
scale-info = { version = "1.0.0", default-features = false, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
primitive-types = { version = "0.10.1", default-features = false, features = ["scale-info"]}

[profile.release]
lto = true
opt-level = 's'

Open the src/lib.rs file, and at the beginning of the file, import the Gear library files we need. Look again at the basic structure of this program:

#![feature(const_btree_new)]
#![no_std]

// External packages (crates) and internal modules import
use codec::{Decode, Encode};
use gstd::{debug, msg, prelude::*};
use scale_info::TypeInfo;

// Init function that is executed once upon contract initialization
// Here is empty
#[no_mangle]
pub unsafe extern "C" fn init() {}

// Handle function that processes the incoming message
#[no_mangle]
pub unsafe extern "C" fn handle() {}

It is the minimal structure necessary for our application to work. The init() function is executed once during context initialization, and the Handle function is responsible for handling all incoming messages from the program.

Next, we'll add a Voting structure, which will contain the main code for the handler state.

#[derive(Clone)]
pub struct State {
    votes_received: BTreeMap<String, i32>,
}

impl State {
    // Create a state
    pub const fn new() -> Self {
        Self {
            votes_received: BTreeMap::new(),
        }
    }

    // Add new candidate
    pub fn add_candidate(&mut self, candidate: String) {
        self.votes_received.insert(candidate, 0);
    }

    // Vote for the candidate by name. If candidate no exist add it
    pub fn vote_for_candidate(&mut self, name: String) {
        let counter = self.votes_received.entry(name).or_insert(0);
        *counter += 1;
    }
}

// The state itself (i.e. the variable state will be accessed through)
static mut STATE: State = State::new();

We also need to define the metadata structures used to implement the input and output communication interfaces. The method described in this paper is a binary mapping of interactions between different programming languages. For example, since the program is compiled into WASM format, it can only understand the language of bytes. To simplify operations, we define data structures ahead of time for further encoding and decoding. For this we use a special macro gstd::metadata!

gstd::metadata! {
   title: "Voting App",
   handle:
       input: Action,
   state:
       input: StateAction,
       output: StateReply,
}

Now let's start processing incoming messages. Whenever our contract receives an incoming message, we will handle it accordingly. Let's describe the handle() function:

#[derive(Debug, TypeInfo, Encode)]
pub enum StateReply {
   All(BTreeMap<String, i32>),
   VotesFor(i32),
}

#[derive(Debug, TypeInfo, Decode)]
pub enum StateAction {
   All,
   VotesFor(String),
}

// Handle function that processes the incoming message
#[no_mangle]
pub unsafe extern "C" fn handle() {
    let action: Action = msg::load().unwrap();

    debug!("Received action: {:?}", action);

    match action {
        Action::AddCandidate(name) => {
            STATE.add_candidate(name.clone());

            msg::reply((), 0, 0);

            debug!("Added new candidate: {:?}", name);
        }

        Action::VoteForCandidate(name) => {
            STATE.vote_for_candidate(name.clone());

            msg::reply((), 0, 0);

            debug!("Voted for: {:?}", name);
        }
    }
}

Now we can communicate with our program. Join candidates and vote for them. All that's left is for our program to display the names of all candidates or a certain person. For this, we will use the meta_state() function, which will return the state immediately without any overhead.

// The function that returns a part of memory with a state
#[no_mangle]
pub unsafe extern "C" fn meta_state() -> *mut [i32; 2] {
   let query: StateAction = msg::load().expect("failed to decode input argument");

   let encoded = match query {
       StateAction::All => StateReply::All(STATE.votes_received.clone()).encode(),

       StateAction::VotesFor(name) => {
           let votes_for_candidate = STATE
               .votes_received
               .get(&name)
               .expect("Can't find any candidate");

           StateReply::VotesFor(votes_for_candidate.clone()).encode()
       }
   };

   let result = gstd::macros::util::to_wasm_ptr(&encoded[..]);
   core::mem::forget(encoded);

   result
}

Source file: https://github.com/gear-tech/…

Build the Gear program

Our smart contract is ready! Now it needs to be compiled and uploaded to the Gear blockchain. let's start!

In the voting application folder, we compiled our smart contract:

RUSTFLAGS="-C link-args=--import-memory" cargo +nightly build --release --target=wasm32-unknown-unknown

wasm-proc --path ./target/wasm32-unknown-unknown/release/voting_app.wasm

After our application is compiled successfully, the final object file is intarget/wasm32-unknown-unknown/release/voting-app.opt.wasmandtarget/wasm32-unknown-unknown/release/voting-app.meta.wasm(meta.wasm is a binary interface for interacting with javascript programs)

Other tools needed

Install the Polkadot.js extension

Download and install the Polkadot.js browser extension:https://polkadot.js.org/exten…

create Account

Create a new account using the Polkadot.js extension. Don't forget to keep your mnemonic and password in a safe place.

✉️ Uploader

  • redirect tohttps://idea.gear-tech.io/
  • Connect to your account using the Connect button to allow websites to access your wallet in your Polkadot.js plugin.
  • Use the &quot;Get Test Account&quot; button to fund your test account. This button can be pressed several times.
  • Upload the program (.opt.wasm) and metadata (.meta.wasm), give the program a meaningful name, and set the gas limit to 100,000,000. Transactions are signed using the Polkadot.js plugin.
  • Find the program on the Recently Uploaded Programs page and copy its address.

Add new candidates/vote for candidates

  • Find your program in the &quot;All Programs&quot; section and open the messaging form.
  • Add a new candidate or vote for an existing candidate
  • Set the gas limit to 300,000,000 and click Send request.

read status

  • Jump to read status in program page
  • Enter a candidate's name to get its vote count, or leave the input blank to see all current candidates.

In the next article, we will learn how to use the gtest library to write test cases for Gear smart contracts.

About GearFans

Gear is the computing component of the Polkadot ecosystem, and GearFans is a community of Gear enthusiasts.

  • Official website: https://gear-tech.io/
  • Twitter:https://twitter.com/gear_techs
  • GitHub:https://github.com/gear-tech
  • Discord:https://discord.com/invite/7B…
  • Telegram group: https://t.me/gear_tech
  • Telegram Chinese group: https://t.me/Gear_CN
  • Telegram Chinese development group: https://t.me/gear_dev_cn
  • QQ group: 677703337