Develop the counter smart-contract
Last updated
Last updated
In the Flipper , you were introduced to the fundamental process for creating and deploying a smart contract on a Substrate-based blockchain, starting with a basic project. In this tutorial, you will create a new smart contract designed to increase a counter value every time a function is executed.
Before getting started, make sure you have the following ready:
You are generally familiar with command-line interfaces (CLI).
You have installed Rust and set up your development environment as described in one of the sources below:
In the , you set up the cargo-contract package to gain command-line access to the ink! programming language.
Ink! is an embedded domain-specific language tailored for writing WebAssembly-based smart contracts in Rust. It incorporates standard Rust conventions along with specialized #[ink(...)]
attribute macros.
These macros help delineate various components of your smart contract, facilitating their conversion into WebAssembly bytecode that is compatible with Substrate.
Smart contracts designed to operate on Substrate begin as projects, which are initiated through the use of cargo contract commands
.
In this tutorial, we will embark on creating a new project specifically for the incrementer smart contract. This process involves generating a new project directory and populating it with default starter files, also referred to as template files.
These initial files will serve as the foundation that you will then alter to develop the smart contract's logic tailored for the incrementer project.
Start the creation of your smart contract's new project:
Open a terminal shell on your local computer, if you don’t already have one open.
Create a new project named incrementer
by running the following command:
Change to the new project directory by running the following command:
Open the lib.rs
file in a text editor.
By default, the template lib.rs
file contains the source code for the flipper
smart contract with instances of the flipper
contract name renamed incrementer
.
Replace the default template source code with new source code
Save the changes to the lib.rs
file, then close the file.
Verify that the program compiles and passes the trivial test by running the following command:
You can ignore any warnings because this template code is simply a skeleton. The command should display output similar to the following to indicate successful test completion:
Verify that you can build the WebAssembly for the contract by running the following command:
If the program compiles successfully, you are ready to start programming.
The code example below demonstrates the method for storing an AccountId and Balance within this contract:
Every ink! smart contract is required to have a minimum of one constructor, which is executed at the time of contract creation. Nonetheless, it is possible for a smart contract to include several constructors if necessary. The code below provides an example of implementing multiple constructors:
Having familiarized yourself with the basics of storing simple values, defining data types, and utilizing constructors, you're now ready to enhance your smart contract's source code with the following implementations:
Establish a storage value named value
with the data type i32
.
Introduce a new Incrementer
constructor, initializing value
with init_value
.
Implement an additional constructor function called default
, which takes no arguments and instantiates a new Incrementer
with value
initialized to 0.
To proceed with the updates to your smart contract:
Open the lib.rs
file in a text editor.
Replace the Storage Declaration
comment by declaring the storage item named value
with the data type of i32
.
Modify the Incrementer
constructor to set its value
to init_value
.
Add a second constructor function named default
that creates a new Incrementer
with its value
set to 0
.
Save your changes and close the file.
Try running the test
subcommand again and you will see that the tests are now failing. This is because we need to update the get
function and modify the tests to match the changes we implemented. We will do that in the next section.
Having established and initialized a storage value, you're now set to update it through both public and private functions. In this tutorial, we'll introduce a public function that retrieves a storage value. It's important to note that all public functions are required to utilize the #[ink(message)]
attribute macro.
To add the public function into your smart contract:
Open the lib.rs
file in a text editor.
Update the get
public function to return the data for the value
storage item that has the i32
data type.
Because this function only reads from the contract storage, it uses the &self
parameter to access the contract functions and storage items.
This function does not allow changes to the state of the value
storage item.
If the last expression in a function does not have a semicolon (;), Rust treats it as the return value.
Replace the Test Your Contract
comment in the private default_works
function with code to test the get
function.
Save your changes and close the file.
Check your work using the test
subcommand, and you will see that it is still failing, because we need to update the it_works
test and add a new public function to increment the value
storage item.
Currently, the smart contract is configured in a way that prevents users from modifying the storage. To allow users to update storage items, it's necessary to designate value
as a mutable variable.
To add a for incrementing the stored value in your smart contract:
Open the lib.rs
file in a text editor.
Add a new inc
public function to increment the value
stored using the by
parameter that has data type of i32
.
Add a new test to the source code to verify this function.
Save your changes and close the file.
Check your work using the test
subcommand:
The command should display output similar to the following to indicate successful test completion:
Once you have tested the incrementer
contract, you're prepared to compile this project into WebAssembly.
To build the WebAssembly version of this smart contract:
Open a terminal shell on your computer, if needed.
Verify that you are in the incrementer
project folder.
Compile the incrementer
smart contract by running the following command:
The command displays output similar to the following:
Open a terminal shell on your computer, if needed.
Start the contracts node in local development mode by running the following command:
Upload and instantiate the contract
Increment the value
Get the current value
You should see the value
retrieved from the contract: 42
This particular smart contract requires the storage of straightforward values.
The code presented in this segment aims to showcase the capabilities of the ink! language. The specific code that will be utilized throughout the remainder of this tutorial is introduced in the following section: . Simple values within a contract can be stored utilizing the #[ink(storage)]
attribute macro:
ink! smart contracts are compatible with a wide array of Rust's standard data types, such as booleans, unsigned and signed integers, strings, tuples, and arrays. These types are efficiently serialized and deserialized for network transmission via the . Beyond these common Rust types, ink! also accommodates Substrate-specific types, including AccountId, Balance, and Hash, treating them akin to native types.
You should have the substrate-contracts-node installed on your system, from the . You can start a local blockchain node specifically for your smart contract. Following this, cargo-contract
can be utilized for deploying and testing your smart contract. To deploy it on the local node: