Technical Guide | Teach you to discuss Wasm contract development

Ontology Wasm has received great attention from community developers since its launch. Because this technology reduces the cost of dApp contracting on business logic, it greatly enriches the dApp ecosystem.

Wasm currently supports development in both Rust and C++ languages. The Rust language has better support for Wasm, and the generated bytecode is more streamlined, which can further reduce the cost of contract calls. So how do you use Rust for Wasm contract development?

First, use Rust for Wasm contract development

1.1 New contract
Cargo is a rare project build and package management tool for developing Rust programs that helps developers better organize code and third-party library dependencies. To create a new Ontology Wasm empty contract, just execute the following command:
  Cargo new --lib hello-world 
The project structure it generates is:
  |-Cargo.toml
 |-src
    |-lib.rs 

Among them, the Cargo.toml file is used to configure project basic information and dependent library information, etc. The [lib] section in the file must be set to crate-type = ["cdylib"]; and the lib.rs file is used to write contract logic code. In addition, you need to add the dependency settings in the [dependencies] section of the configuration file Cargo.toml: ontio-std={https://github.com/ontio/ontology-wasm-cdt-rust}

  Ontio-std={https://github.com/ontio/ontology-wasm-cdt-rust} 

With this dependency, developers can call interfaces that interact with the ontology blockchain and tools such as parameter serialization.

1.2 Contract entry function
Each program has an entry function, such as our usual main function, but the contract does not have a main function. When developing a Wasm contract with Rust, the invoke function is used by default as the entry function for contract execution. The function names in Rust are confused when compiling the Rust source code into bytecode that the virtual machine can execute. To prevent the compiler from generating extra bytecode and reducing the contract size, the invoke function adds the #[no_mangle] annotation.
How does the Invoke function get the parameters of the trade execution? The ontio_std library provides the runtime::input() function to receive parameters for transaction execution. Developers can use ZeroCopySource to deserialize the received byte array. Among them, the first byte array read out is the method name of the call, and the method parameters are read later.
How is the contract execution result returned? The runtime::ret function provided by the ontio_std library returns the result of the method execution.

A complete invoke function is as follows:

  #[no_mangle]
 Pub fn invoke() {
     Let input = runtime::input();
     Let mut source = ZeroCopySource::new(&input);
     Let action: &[u8] = source.read().unwrap();
     Let mut sink = Sink::new(12);
     Match action {
         b"hello" => sink.write(say_hello()),
         _ => panic!("unsupported action!"),
     }
     Runtime::ret(sink.bytes()) 
  • 1.3 Contract data serialization and deserialization
In the contract development process, developers always encounter serialization and deserialization problems, that is, how to save a struct type of data to the database and how the byte array read from the database is deserialized to obtain Struct type data.
The Ontio_std library provides the Decoder and Encoder interfaces for serializing and deserializing data. The fields of the Struct structure also implement the Decoder and Encoder interfaces so that the struct can be serialized and deserialized. Sink instances are required when serializing various data types. The Sink instance has a collection type field buf, which stores byte type data, and all serialized data is stored in buf.
For fixed-length data (for example: byte, u16, u32, u64, etc.), the data is directly converted into a byte array and then stored in buf; for data of non-fixed length, serialization needs to be serialized first, then sequence Data (such as unsigned integers of unknown size, including u16, u32 or u64, etc.).
Deserialization and serialization are just the opposite. For all serialization methods, there is a corresponding deserialization method. Deserialization requires the use of a Source instance. This instance has two fields buf and pos. Buf is used to store the data to be deserialized, and pos is used to store the current read position. When reading the specified type of data, if you know its length, you can read it directly; for data of unknown length, read the length first, and then read the content.
1.4 Accessing and updating data on the chain
Ontology-wasm-cdt-rust has encapsulated the operation method of the data on the chain, which is convenient for developers to implement operations such as adding, deleting, and changing data on the chain. among them:
Ø database::get(key) is used to query data from the chain, and key requires the implementation of the AsRef interface.

Ø database::put(key, value) is used to store data on the chain, the key requires the implementation of the AsRef interface, and the value requires the implementation of the Encoder interface.

Ø database::delete(key) is used to delete data from the chain, and the key requires the AsRef interface.

1.5 contract test
The contract method needs to access the data on the chain and requires the corresponding virtual machine to execute the contract bytecode, so it is generally necessary to deploy the contract to the chain for testing. But such a test method is more troublesome. To make it easier for developers to test contracts, the ontio_std library provides a mock test module. This module provides simulation of the data on the chain, making it easy for developers to unit test the methods in the contract.
Specific cases can refer to:
Https://github.com/ontio/ontology-wasm-cdt-rust/blob/master/examples/oep5token/src/test.rs
1.6 Commissioning contract
Developers can use console::debug(msg) to output debugging information during contract debugging. The msg information will be printed in the node log log. There is a precondition that the log level needs to be set to debug mode when the Ontology local test node is started.
In addition, developers can also use runtime::notify(msg) to output relevant debugging information during contract debugging. This method saves the printed information to the chain and can be queried from the chain via the getSmartCodeEvent method.