Create a dApp using Substrate Framework

Posted By : Mohd

Mar 15, 2023

Substrate is a framework designed to make it easier for the developer to build custom blockchain applications. To use Substrate, developers can download the Substrate development kit and start building their own blockchain applications. The development kit includes tools for building, testing, and deploying Substrate-based applications.

 

In this blog post, we will be building a simple dApp which stores a value and multiplies it by some value whenever a function is executed. We will also write unit test cases for testing the contract.

 

Developing a dApp with Substrate

 

Installation:

 

Before starting the development process you need to have Rust installed. For installing Rust you can refer to this link. After installing rust you need to update your development environment to develop smart contracts. Follow the steps below to update your development environment.

 

  • Add the rust-src compiler component: rustup component add rust-src
  • Install the latest version of cargo-contract: cargo install --force --locked cargo-contract --version 2.0.0-rc
  • Verify the installation and explore the commands available by running the following command: cargo contract --help
  • Install the substrate contracts node: cargo install contracts-node --git https://github.com/paritytech/substrate-contracts-node.git --tag <latest-tag> --force --locked

 

Note - You can find the latest tag here.

 

  • You can verify the installation by running the following command: substrate-contracts-node --version

 

Before moving forward make sure that you have followed all the above steps and that you have:

 

  • Rust installed and set up your development environment.
  • Substrate contracts node installed locally.

 

Building the Dapp:

 

Smart contracts on the substrate start as projects, which are created by using the cargo contract commands. Creating a new project provides us with the template starter files, we will modify these starter files to build the logic of our dApp.

 

  • Open a terminal in your working directory and run the command to create a new project:

cargo contract new multiplicator

  • Change to the new project directory and open the lib.rs file in a code editor.
  • By default, the flipper smart contract source code in the template lib.rs file has instances of "flipper" modified to "multiplicator" (our project name).
  • Replace the code in lib.rs file with the code below.

 

#![cfg_attr(not(feature = "std"), no_std)]

 

#[ink::contract]

mod multiplicator {

 

   /// 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 Multiplicator {

       /// Stores a single `i32` value on the storage.

       value: i32,

   }

 

   impl Multiplicator {

       /// Constructor that initializes the `i32` value to the given `init_value`.

       #[ink(constructor)]

       pub fn new(init_value: i32) -> Self {

           Self { value: init_value }

       }

 

       /// Constructor that initializes the `i32` value to 1.

       /// Constructors can delegate to other constructors.

       #[ink(constructor)]

       pub fn default() -> Self {

           Self::new(1)

       }

 

       /// Multiply value by 2.

       #[ink(message)]

       pub fn mul_by_2(&mut self) {

           self.value = self.value * 2;

       }

 

       /// Multiply value by the passed parameter.

       #[ink(message)]

       pub fn mul(&mut self, by: i32) {

           self.value = self.value * by;

       }

 

       /// Simply returns the current value of our `i32`.

       #[ink(message)]

       pub fn get(&self) -> i32 {

           self.value

       }

   }

 

   /// Unit tests

   #[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 multiplicator = Multiplicator::default();

           assert_eq!(multiplicator.get(), 1);

       }

 

       /// We test if the other constructor does its job.

       #[ink::test]

       fn other_constructor_works() {

           let multiplicator = Multiplicator::new(2);

           assert_eq!(multiplicator.get(), 2);

       }

 

       /// We test if the other mul_by_2 function does its job.

       #[ink::test]

       fn mul_by_2_works() {

           let mut multiplicator = Multiplicator::default();

           assert_eq!(multiplicator.get(), 1);

           multiplicator.mul_by_2();

           assert_eq!(multiplicator.get(), 2);

       }

 

       /// We test if the other mul function does its job.

       #[ink::test]

       fn mul_works() {

           let mut multiplicator = Multiplicator::new(2);

           assert_eq!(multiplicator.get(), 2);

           multiplicator.mul(2);

           assert_eq!(multiplicator.get(), 4);

       }

   }

}

 

Now, let's understand the code and the test cases:

 

  • Storage fields:

#[ink(storage)] - This macro indicates that the struct defined below defines the storage fields of the contract.

 

  • Constructors:

#[ink(constructor)] - This macro indicates that it is a constructor. We have 2 constructors in our code. One [ default() ] which initializes the ‘value’ with a default value of 1. The other [ new(init_value: i32) ] which initializes the ‘value’ with the ‘init_value’ passed as parameter.

 

  • Functions:

 

We have 3 public functions defined in the contract. #[ink(message)] - This macro indicates that it is a public function.

 

  1. mul_by_2(&mut self) - It does not take any arguments. ‘mut’ indicates that this function can mutate the state variables of the contract. ‘self’ is passed to give reference to the contract. This function just simply multiples the current value by 2 and updates the ‘value’ with the result.
  2. mul(&mut self, by: i32) - It takes one argument ‘by’ which is multiplied with the current value and updates the ‘value’ with the result.
  3. get(&self) - It just simply returns the current value of ‘value’. Note that ‘mut’ keyword is not present in this function as this function does not change the state of the contract. 

 

  • Tests:

#[cfg(test)] - This macro defines the test cases for the contract.

#[ink::test] - This macro defines the functions which test the contract.

 

If you have a project in mind and need information on getting started with dApp development with Substrate, you may connect with our skilled blockchain developers.
 

 

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

November 21, 2024 at 12:43 pm

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Telegram Button
Youtube Button

Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text