Creating your own project
Writing your smart contract from scratch
Using templates is convenient but it might limit certain dimensions of the development process.
So, let us write a contract from scratch and test it. This will be more helpful for developers who are familiar with smart contract development but are just getting started with Ontology WASM smart contracts.
Create a New Smart Contract
We create a new library, so to speak, to start implementing a new smart contract. Navigate to the appropriate directory and execute the following code-
If successfully executed, the file hierarchy of the new library would look like so-
As you may have noticed, a rust based WASM contract consists of two components, one being the Cargo.toml
file and the other being the src/lib.rs
rust file that is used to write and implement contract logic.
Generate the ontio-std API File
Before generating the API file, we need to edit the Cargo.toml
file to add certain dependencies and libraries that will be used later.
First, under the [dependencies]
configuration, include the Ontology WASM contract toolkit. Also, since we will compiling the contract in a form that is different from the standard, we need to add [lib]
configuration settings.
The [features]
configuration is used to toggle certain unstable features. Please note that these features can only be compiled using the nightly compiler.
A complete Cargo.toml
would look something like-
We still do not know the APIs that we can use from the toolkit that we just included in the project dependencies.
The following command can be used to generate the API documentation for this library.
After successful execution of the above command the project structure would be as follows-
The API documentation can be found in the doc directory. The settings.html file can be opened using a web browser. The reference for ontio-std library looks like-
Libraries are referred to as crates in the context of Rust.
Writing Contract Logic
The src/lib.rs
file that was generated when we created a new library has the following contents by default-
There is some test code included. You can test this code by executing the cargo test
command under the root
directory.
We can now proceed with writing our logic by editing this file.
First, we import the ontio-std
library added in the Cargo.toml
dependencies. We use the #![no_std]
annotation so as to prevent rust from using the standard library.
Then we add an invoke function that acts as the main() for all intents and purposes. We also import the APIs that will allow us to carry out parameter I/O.
Here, Sink
and Source
objects have been imported. Source
allows us to fetch the parameters and data that is passed when the contract is invoked externally. Sink
is used to serialize data of different formats to the bytearray format.
The prelude
module of ontio-std provides a few commonly used data types Address
, U128
, String
, etc., apart from other useful functions.
The runtime API contains functions that are used to interact with the blockchain and the application end. For example, the runtime::ret()
method is used to return the result of contract execution.
The #[no_mangle]
annotation instructs the compiler not to obscure the main function while compiling it to ensure that Ontology's build tools can work with the bytecode that is generated post-compilation.
With this, the basic methods that allow us to interact with the contract are in place. The contract can now be compiled.
Compiling the Contract
The code needs to be compiled and converted to WASM
bytecode
to be deployed on the chain. The following command can be used to compile a contract.
The RUSTFLAGS="-C link-arg=-zstack-size=32768"
directive is used to set the maximum allowed stack size to 32KB. The default size of the rustc
compiler stack is 1MB, which is very wasteful as far as smart contracts are concerned. wasm32-unknown-unknown
indicates that the target bytecode be generated using the LLVM back end compiler. It generates better and more sophisticated code than the emscripten compiler.
Once the above code is successfully executed the target directory will look like-
For our example, the file generated can be found in the wasm32-unknown-unknown/release
folder under the target
directory with the file name helloworld.wasm
The bytecode file generated post compilation tends to be large in size, and thus if deployed on to the chain in it's current form, a large amount of space would be required to store it, implying higher costs. To optimize storage, we use the ontio-wasm-build tool to optimize the bytecode and compress it.
More details for those curious about ontio-wasm-build can be found by following this link.
The tools can be used by executing the following command with the WASM bytecode file-
The file with the optimized bytecode goes by the name of helloworld_optimized. Another file with a .str extension will also be generated. While deploying the contract we will be using this string file that contains hex code obtained by converting the bytecode, since we are carrying out this entire process using the CLI (Command Line Interface).
Deploying and Invoking the Contract
To able to perform any transaction related operations we will first need to deploy our private test node, and a private test node requires a wallet file to work with. Here we make a new account and generate a .dat
wallet file. Navigate to the Ontology root directory and execute the following command.
Next, open a new terminal window and run the following command to start the private node.
--testmode
selects the test mode to start the node, and --loglevel 1
selects the debug mode so that the debug information gets recorded in the logs section for us to refer to, as and when needed.
We need to ensure that this particular window stays up and running in the background since this is what serves as our private node, practically speaking.
Then, return to the original window and execute the following command to deploy the contract.
Let us go through the series of options used here one by one.
Option | Value | Description |
--vmtype | 3 | The VM type to be used to run the contract |
--code | Path | Path of the file containing the bytecode |
--name | String | Name of the smart contract |
--author | String | Name of the author of the smart contract |
String | Email address of the contract author | |
--desc | String | Brief description of the contract |
--gaslimit | Integer | Gas limit to calculate the Gas cost for deploying the smart contract |
After the deploy command, --vmtype 3
indicates that the contract is a WASM contract. We specify this since apart from WASM contracts, Ontology also supports NeoVM smart contract development in Python and C#. Interested developers feel free to check out the the relevant details.
Finally, if the contract was deployed successfully, we can proceed to invoke the contract using the contract details.
The invoke()
function serves as the entry point for a smart contract. Any other functions that we may define in the scope of the contract can be called by passing parameters to the invoke()
function and then calling the respective function from within invoke()
. The functions that are defined under the #[test]
annotation can be run locally from the IDE to test logic.
When we invoke the invoke() function here, it will return a "hello world" if the contract is invoked successfully, since the function we're calling is hello, and we have passed the string "hello world" as parameters. We just need to ensure that we use the --prepare
option when invoking the contract. This means that we are pre-executing it, the reason for which being the result returned by the contract will not be displayed if we invoke and run the contract directly.
More details on contract invocation and CLI operation in general can be found here.
The command is as follows-
If the executed successfully, 68656c6c6f20776f726c64 along with the transaction hash will be returned in the command line. The value is in fact "hello world" in hexadecimal.
You now have the fundamental understanding of Ontology's WASM contracts such that you can develop your own smart contracts and implement complex logic.
Code for Reference
For examples and sample code that implement more complex logic, please follow the link below.
Last updated