Project Initiation - Hello World

An illustration to write the contract logic using rust

To execute Rust code on the Ontology blockchain, there's a process that needs to be followed. The steps are carried out in the following way:

  1. WASM bytecode is generated by compiling the code.

  2. The bytecode is deployed on to the chain.

  3. Functions from the contract are invoked.

We will approach the development process from two different angles. In this section, we will first look at a template designed with the specific goal of getting you acquainted with the fundamentals of writing a WASM smart contract and working with a template. And then in the later sections, we will proceed to demonstrating how to start writing code from scratch.

To facilitate developers looking to work on Ontology WASM smart contracts we have made available a Rust template that developers can clone and start editing to speed things up. The code can be cloned from Github using the following command-

git clone https://github.com/ontio/rust-wasm-contract-template.git

Project file hierarchy and specifics

The file hierarchy of the project is mapped below.

.
├── .cargo
│   └── config
├── Cargo.toml
├── build.sh
└── src
    └── lib.rs

The config file in .cargo directory contains the configuration settings which will be used when compiling the contract. The contents of the file-

[target.wasm32-unknown-unknown]
rustflags = [
    "-C", "link-args=-z stack-size=32768"
]

[target.wasm32-unknown-unknown] is the compile target. The target will directly be compiled to WASM using the low-level virtual machine (LLVM) back end. The resultant bytecode can be executed on Linux, Mac, and Windows system platforms. rustflags is used to configure the link arguments and the default stack size to 32768 bytes, 32KB that is. This indicates the highest stack value that the contract is allowed to use.

cargo.toml file contains a few configuration settings and other details regarding the contract. The content is as follows-

[package]
name = "rust-wasm-contract-template"
version = "0.1.0"
authors = ["laizy <aochyi@126.com>"]
edition = "2018"

#See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"] #Compile as a dynamic link library

[dependencies]
ontio-std = {git = "https://github.com/ontio/ontology-wasm-cdt-rust"}

[features]
mock = ["ontio-std/mock"]

In the [lib] configuration module, crate-type = ["cdylib"] specifies the compilation DLL that can be invoked using other languages.

path = "src/lib.rs" sets the library file path.

[dependencies] section is used to specify the project dependency details. Here, we import the ontio-std library.

[features] is used to toggle newly introduced features that are unstable. These features can be used with the nightly version compiler only.

The build.sh file encapsulates functions that will be used to compile and optimize our contract. Executing this shell script will move the optimized bytecode to the output directory.

The src/lib.rs rust file is used to write the contract logic. The template contains the following code:

#![no_std]
use ontio_std::runtime;

#[no_mangle]
fn invoke() {
    runtime::ret(b"hello");
}

#![no_std] annotation is used to indicate that the core library is to be used instead of the standard library, referred to as crate in rust. This will allow us to use Ontology's APIs.

#![no_mangle] annotation indicates that when the code is compiled to WASM bytecode, the compiler will not obscure the invoke method. The runtime module encapsulates the API that allows the contract to communicate with the blockchain. The runtime::ret() method is used to return the result of contract invocation. Here, we are trying to implement a simple contract that returns "hello" when invoked.

Compiling the contract

The code can be compiled and the optimized bytecode can be fetched by running the build.sh shell script.

./build.sh

If the console returns a "Permission denied" message, use sudo on linux systems or run the command line as administrator on windows platforms and run the script again.

After the script successfully executes, it will create the output directory in the following way-

├── output
   ├── rust_wasm_contract_template.wasm
   └── rust_wasm_contract_template.wasm.str

Two files are generated here. The WASM file is the bytecode generated by compiling the smart contract that we compiled, and the str file contains the hex encoding for the bytecode.

Deploying the contract

Once the code is compiled, it needs to be deployed on to the chain to be executed. The bytecode that we generated can be deployed on both the test net and the private net for testing. For now, let us look at how to deploy the contract on the private net.

First, we need to create a wallet account and run our private node. We use the following shell command to create an account-

./ontology account add

After executing the above command, follow the instructions and set up and account with the default configuration. Then, use the following command in a new command line window to start a private node.

./ontology --testmode --loglevel 1

The --loglevel 1 parameter is used to set the log level to debug. In case there is any debug information returned while testing the node, it will be displayed in the log files generated in the Log directory.

In a new window, access the Ontology master directory and execute the following command to deploy the contract. Enter the password for the account when prompted.

The parameters consist of the target path and some other information regarding the contract which is optional to fill in. The gaslimit is set at the end.

Note: The gas limit is precise to 9 decimal places. Thus, 10^9 units of gas would be equivalent to 1 ONG token with the minimum valid value being 0.000000001. The gas cost, which basically means the cost to carry out a transaction on the chain, is calculated by taking the product of the gas price and the gas limit.

./ontology contract deploy --vmtype 3 --code ./rust_wasm_contract_template.wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000

The result will be as follows-

Password:
Deploy contract:
  Contract Address:0be3df2e320f86f55709806425dc1f0b91966634
  TxHash:bd83f796bfd79bbb2546978ebd02d5ff3a54c2a4a6550d484689f627513f5770

Tip:
  Using './ontology info status bd83f796bfd79bbb2546978ebd02d5ff3a54c2a4a6550d484689f627513f5770' to query transaction status.

If the system returns the error that the gaslimit is not enough, please change the gaslimit and enter a bigger value.

Test invocation

Next, we use the following command to invoke our smart contract. Here we use the contract address that was returned by the system earlier when we deployed the contract.

There are no parameters to be passed for this function, and the execution mode is --prepare which indicates that the contract will be pre-executed, thereby allowing us to see the value that will be returned by the invoke function.

./ontology contract invoke --address 0be3df2e320f86f55709806425dc1f0b91966634 --vmtype 3 --params '' --version 0 --prepare

The result is as follows-

Invoke:346696910b1fdc2564800957f5860f322edfe30b Params:null
Contract invoke successfully
Gas limit:20000
Return:68656c6c6f (raw value)

We expected a "Hello" to show up, but the value returned by the system is 68656c6c6f. Why?

The reason is simple. All the data values returned by the system will be hex encoded. A simple hexadecimal to string conversion will show that 68656c6c6f is in fact "Hello".

You have successfully executed your first Ontology WASM contract.

Templates for reference

Please follow the following link to find the various templates made available by Ontology. The templates serve as examples that illustrate how token exchange and transaction protocols can be realized using rust.

Last updated