Develop a Smart Contract
Develop a smart contract that increments a value.
Last updated
Develop a smart contract that increments a value.
Last updated
In , you learned the basic steps for building and deploying a smart contract on a Substrate-based blockchain using a default first project.
For this tutorial, you'll develop a new smart contract that increments a counter value each time you execute a function call.
Before you begin, verify the following:
You have good internet connection and access to a shell terminal on your local computer.
You are generally familiar with software development and using command-line interfaces.
You are generally familiar with blockchains and smart contract platforms.
You have installed Rust and set up your development environment as described in .
You have completed and have the Substrate contracts node installed locally.
By completing this tutorial, you will accomplish the following objectives:
Learn how to use a smart contract template.
Store simple values using a smart contract.
Increment and retrieve stored values using a smart contract.
Add public and private functions to a smart contract.
This language enables you to write WebAssembly-based smart contracts using the Rust programming language.
The language uses standard Rust patterns with specialized #[ink(...)]
attribute macros.
These attribute macros describe what different parts of your smart contract represent so they can be transformed into Substrate-compatible WebAssembly bytecode.
Smart Contracts that run on Substrate start as projects. You create projects using cargo contract
commands.
For this tutorial, you'll create a new project for the incrementer
smart contract.
Creating a new project adds a new project directory and default starter files — also called template files — to the project directory.
You will modify these starter template files to build the smart contract logic for the incrementer
project.
To create a new project for your smart contract:
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
.
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.
Now that you have some starter source code for the incrementer
smart contract, you can introduce some new functionality.
For example, this smart contract requires storage of simple values.
You can store simple values for a contract using the #[ink(storage)]
attribute macro:
ink! smart contracts support most Rust common data types, including booleans, unsigned and signed integers, strings, tuples, and arrays.
In addition to common Rust types that can be encoded and decoded using the scale codec, the ink! language supports Substrate-specific types — like AccountId
, Balance
, and Hash
— as if they were primitive types.
The following code illustrates how to store an AccountId
and Balance
for this contract:
Every ink! smart contract must have at least one constructor that runs when the contract is created. However, a smart contract can have multiple constructors, if needed.
The following code illustrates using multiple constructors:
Now that you have learned about storing simple values, declaring data types, and using constructors, you can update your smart contract source code to implement the following:
Create a storage value called value
with a data type of i32
.
Create a new Incrementer
constructor and set its value
to init_value
.
Create a second constructor function named default
that has no input, and creates a new Incrementer
with its value
set to 0
.
To update the 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.
Now that you have created and initialized a storage value, you can interact with it using public and private functions. For this tutorial, you add a public function (a.k.a message) to get a storage value.
Note that all public functions must use the #[ink(message)]
attribute macro.
To add the public function to the 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.
At this point, the smart contract does not allow users modify the storage. To enable users to modify storage items, you must explicitly mark value
as a mutable variable.
To add a function for incrementing the stored value:
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:
After you test the incrementer
contract, you are ready to compile this project to WebAssembly.
To build the WebAssembly for 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:
Then you can use cargo-contract
to deploy and test the smart contract.
To deploy on the local node:
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
As you can see the value
being read from the contract is 42
, which matches our previous step!
In this tutorial, you learned some basic techniques for creating smart contracts using the ink! programming language and attribute macros.
For example, this tutorial illustrated:
How to add storage items, specify data types, and implement constructors in a new smart contract project.
How to add functions to a smart contract.
How to add a test to a smart contract.
How to upload and instantiate the contract using cargo-contract
.
You can learn more about smart contract development in the following topics:
In , you installed the cargo-contract
package for command-line access to the ink! programming language.
The ink! language is an .
Replace the default template source code with new source code
The following code in this section is intended to illustrate the functionality of the ink! language. The code you will be using in the rest of this tutorial begins in the next section, .
These data types are encoded and decoded using the for efficient transmission over the network.
If you have the node installed locally, you can start a local blockchain node for your smart contract.
You can find an example of the final code for this tutorial in the assets for the .