This is step-by-step explanation of the process behind building an ink! smart contract, using a simple app called Flipper. The examples provided within this guide will help you develop an understanding of the basic elements and structure of ink! smart contracts.
Flipper is a basic smart contract that allows the user to toggle a boolean value located in storage to either true or false. When the flip function is called, the value will change from one to the other.
$ cargo contract new flipper # flipper is introduced from the beginning.
$ cd flipper/
$ cargo contract build #build flipper app
💡 If you receive an error such as:
ERROR: cargo-contract cannot build using the "stable" channel. Switch to nightly.
Execute:
$ rustup default nightly
to reconfigure the default Rust toolchain to use the nightly build, or
$ cargo +nightly contract build
to use the nightly build explicitly, which may be appropriate for developers working exclusively with ink!
Once the operation has finished and the Flipper project environment has been initialized, we can perform an examination of the file and folder structure. Let’s dive a bit deeper into the project structure:
#![cfg_attr(not(feature = "std"), no_std)]
#[ink::contract]
mod flipper {
/// Defines the storage of your contract.
/// Add new fields to the below struct in order
/// to add new static storage fields to your contract.
#[ink(storage)]
pub struct Flipper {
/// Stores a single `bool` value on the storage.
value: bool,
}
impl Flipper {
/// Constructor that initializes the `bool` value to the given `init_value`.
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
Self { value: init_value }
}
/// Constructor that initializes the `bool` value to `false`.
///
/// Constructors can delegate to other constructors.
#[ink(constructor)]
pub fn default() -> Self {
Self::new(Default::default())
}
/// A message that can be called on instantiated contracts.
/// This one flips the value of the stored `bool` from `true`
/// to `false` and vice versa.
#[ink(message)]
pub fn flip(&mut self) {
self.value = !self.value;
}
/// Simply returns the current value of our `bool`.
#[ink(message)]
pub fn get(&self) -> bool {
self.value
}
}
/// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
/// module and test functions are marked with a `#[test]` attribute.
/// The below code is technically just normal Rust code.
#[cfg(test)]
mod tests {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;
/// We test if the default constructor does its job.
#[ink::test]
fn default_works() {
let flipper = Flipper::default();
assert_eq!(flipper.get(), false);
}
/// We test a simple use case of our contract.
#[ink::test]
fn it_works() {
let mut flipper = Flipper::new(false);
assert_eq!(flipper.get(), false);
flipper.flip();
assert_eq!(flipper.get(), true);
}
}
}
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod flipper {
// This section defines storage for the contract.
#[ink(storage)]
pub struct Flipper {
}
// This section defines the functional logic of the contract.
impl Flipper {
}
// This section is used for testing, in order to verify contract validity.
#[cfg(test)]
mod tests {
}
}
Contracts can also contain multiple constructors. Here is a constructor that assigns a default value to bool. As in other languages, the default value of bool is false.
The following will permit a function to be publicly dispatchable, meaning that the function can be called through a message, which is a way for contracts and external accounts to interact with the contract. Find more information here). Note that all public functions must use the #[ink(message)] attribute.
#[ink(message)]
The flip function modifies storage items, and get function retrieves a storage item.
💡 If you are simply reading from contract storage, you will only need to pass &self, but if you wish to modify storage items, you will need to explicitly mark it as mutable &mut self.
impl Flipper {
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
Self { value: init_value }
}
/// Constructor that initializes the `bool` value to `false`.
///
/// Constructors can delegate to other constructors.
#[ink(constructor)]
pub fn default() -> Self {
Self::new(Default::default())
}
/// A message that can be called on instantiated contracts.
/// This one flips the value of the stored `bool` from `true`
/// to `false` and vice versa.
#[ink(message)]
pub fn flip(&mut self) {
self.value = !self.value;
}
/// Simply returns the current value of our `bool`.
#[ink(message)]
pub fn get(&self) -> bool {
self.value
}
}
#[cfg(test)]
mod tests {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;
/// Imports `ink_lang` so we can use `#[ink::test]`.
use ink_lang as ink;
/// We test if the default constructor does its job.
#[ink::test]
fn default_works() {
let flipper = Flipper::default();
assert_eq!(flipper.get(), false);
}
/// We test a simple use case of our contract.
#[ink::test]
fn it_works() {
let mut flipper = Flipper::new(false);
assert_eq!(flipper.get(), false);
flipper.flip();
assert_eq!(flipper.get(), true);
}
}