description |
---|
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:
WASM
bytecode is generated by compiling the code.- The bytecode is deployed on to the chain.
- 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
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 <[email protected]>"]
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.
The code can be compiled and the optimized bytecode can be fetched by running the build.sh
shell script.
./build.sh
{% hint style="warning" %}
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.
{% endhint %}
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.
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.
{% hint style="warning" %} Note: The gas limit is precise to 9 decimal places. Thus, 109 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. {% endhint %}
./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.
{% hint style="warning" %}
If the system returns the error that the gaslimit
is not enough, please change the gaslimit
and enter a bigger value.
{% endhint %}
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.
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.