Writing smart contracts in ink!
Tags: polkadot, rust, beginners, web3
Published: 2023-07-30 12:51:31

If you are looking for a soft landing into polkadot and you don't want to bother about writing pallets yet while still enjoying solidity's style of writing smart contract. Then you have come to the right place. 

In this article, you will learn how to write smart contracts in polkadot and deploy the smart contract on a Testnet.

### Prerequisites 
You need to know the following to be able to follow this tutorial. 

- Some programming knowledge working with structures.

- Experience working with solidity but not compulsory.

- Some programming knowledge on rust.

- A polkadot account, if you don't have one you can follow this guide to create one [here](https://subdaily.io/learn/tutorials/how-to-set-up-a-polkadot-account/)


With that out of the way, let's start coding. 

We are going to build a simplified burger shop using ink smart contracts. 

### What is Ink! programming language?
Ink! is a safe, secure, and efficient language for writing smart contracts. It is based on the Rust programming language, which is known for its safety and security features.

Ink! smart contracts are also portable, which means that they can be deployed on any blockchain that supports WebAssembly.

### Setting up
To run contracts, instantiate contracts and more, we need to download the Ink! CLI. Open your terminal and run `rustup component add rust-src`. 
After the process finishes, run `cargo install --force --locked cargo-contract`. 
To verify the package is installed in your pc, run `cargo contract -V` or `cargo contract --version`. You also run `cargo contract --help` to see other available commands. 

Next, we install substrate-contracts-node. This is a simple substrate blockchain configured for smart contract functionality using `contracts-pallets`. You can use this instead of a Testnet, we will cover this in another article.
You could install the [binary](https://github.com/paritytech/substrate-contracts-node/releases) which is faster. This is supported for linux and Mac systems.
  
You could also install it using `cargo` instead, by running `cargo install contracts-node --git https://github.com/paritytech/substrate-contracts-node.git --tag v0.23.0 --force --locked`. 
To verify that it is downloaded, run `substrate-contracts-node -V` also to check for other commands run `substrate-contracts-node --help`. 

### What are pallets?
Substrate framework is the framework used in building blockchains in polkadot which comprises of different modules. These modules make up different parts of a blockchain from the FRAME, Consensus Engines, Primitives, Libraries, etc. 

FRAME stands for "Framework for Runtime Aggregation of Modularized Entities." It is designed to enable flexibility and composability in blockchain development, allowing developers to pick and choose different modules to include in their custom blockchain's runtime logic. Borrowing some of the artistic term of pallets used for drawing, I assume you know about colour pallets used in drawing. You can think of substrate pallets as different tools available for you to build the blockchain you want. For example, the `substrate-contracts-node` comprises of pallets configured for smart contracts. You could build a blockchain tailored for a social media platform or a staking platform.

### Building the project
Ink! smart contracts relies on the use of macros which are code that generate code to achieve some function. We will use macros while working on this project, don't worry. You won't need to write a custom macro.

Let's create our project, we'll call it "burger_shop", in your project's directory, run `cargo contract new burger_shop`.
This should create a project and generate an ink! smart contract template. I encourage to play around or read the code first, to try to understand what's going on. You'll notice there are contract calls, tests and integration tests. We'll write our own contract calls and tests for our smart contract. To keep this tutorial short, tests will be covered in another article. 


Next, let's update our `.toml` file with the necessary deps, copy this code.

```toml
[package]
name = "burger_shop"
version = "0.1.0"
authors = ["Ayomide Bajo <oluwashinabajo@gmail.com>"] ## replace with your name and email
edition = "2021"

[dependencies]
ink = { version = "4.0.1", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = "4.0.1"

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
    "ink/std",
    "scale/std",
    "scale-info/std",
]
ink-as-dependency = []
e2e-tests = []
```

### Setting up the storage
Our smart contract needs a storage to keep all the data coming from the shop, this storage will contain orders from customers. 

Firstly, let's do some imports, we'll need them, so it's better to get that out of the way.
 
Delete everything, with `mod` braces, (trust me, I didn't waste your time, lol). Your file should look like this. 
![empty file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzt561307poip50y4cvh.png)
Add the following code within `mod` braces.

```rust

    use ink::prelude::vec::Vec;
    use ink::prelude::format;
    use ink::storage::Mapping;
    use scale::{Decode, Encode};
```
We imported from the `prelude` module, this includes the types, we usually use with `std` that are also supported for `no_std` compilations. We also imported from the `storage` module which is the storage type for storing our data. Also the `scale` crate for serializations properties and conversions.

Now we can setup our first storage.
Copy this code block.

```rust

   #[ink(storage)]
    pub struct BurgerShop {
        orders: Vec<(u32, Order)>,
        orders_mapping: Mapping<u32, Order>,
    }
```

If you notice, there is an attribute `#[ink(storage)]`, this tells the compiler that the data structure is going to be entering the ink! storage. It also contains fields, I assume you know `Vec` already. There's an interesting type `Mapping`. This type is for storing key-value pairs, like hashmaps and it's quite different from hashmaps, you can read more [here](https://docs.rs/ink_storage/4.0.0/ink_storage/struct.Mapping.html)

Next we are going to create the types that will be used in interacting with the storage in our smart contract.

> If, you're using `rust-analyzer` on your Vscode. You might be seeing some errors, saying something about a `missing constructor`, we will fix that soon! I recommend deactivating it for now so that it won't distract you.

Copy this code block. 

```rust 
// The order type
  #[derive(Encode, Decode, Debug, Clone)]
    #[cfg_attr(
        feature = "std",
        derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
    )]
    pub struct Order {
        list_of_items: Vec<FoodItem>,
        customer: AccountId,
        total_price: Balance,
        paid: bool,
        order_id: u32,
    }

  // Food Item type, basically for each food item
    #[derive(Encode, Decode, Debug, Clone)]
    #[cfg_attr(
        feature = "std",
        derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
    )]
    pub struct FoodItem {
        burger_menu: BurgerMenu,
        amount: u32,
    }

// Burger Type
    #[derive(Encode, Decode, Debug, Clone)]
    #[cfg_attr(
        feature = "std",
        derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
    )]
    pub enum BurgerMenu {
        CheeseBurger,
        ChickenBurger,
        VeggieBurger,
    }
```

There are a bunch of attributes derived for the types we've created, `#[derive(Encode, Decode, Debug, Clone)]` (skipping the other traits in rust). This attribute derives the following traits, `Encode` and `Decode`. `Encode` makes the type encodable (i.e can be hashed) and `Decode` makes type decodable (i.e can be decoded for interaction in the frontend and other operations). The `cfg_attr` also does some derivations, in here, `scale_info::TypeInfo` derives the `TypeInfo` trait that does some serializations like `serde`, this helps to compile the struct `Order`(for example) into a `Portable` form. The `ink::storage::traits::StorageLayout` type implements `StorageLayout` trait, which will allow the compiler to add the type(s) into storage. 

Also, notice we have types `Balance` and `AccountId`. These are special types indicating the value in tokens to be sent and the address/account of the caller, respectively. These are the expected types for our operations. You can read more about them [here](https://docs.rs/ink_env/4.2.1/ink_env/trait.Environment.html#associatedtype.AccountId)

We created the following types for the following reasons:

- `Order`: This type is for each order a customer creates to buy something from the shop(which is burger in the case), it also contains the `total_price` for the order and other fields. 

- `FoodItem`: This type is for each food item stored in each order, it also contains the amount of food items. 

- `BurgerMenu`: This is for each type of burger available in our shop.


We are going to implement some methods for our types, as you have noticed our smart contract follows rust's standard way of writing structures and code, plus you still get to enjoy solidity's style of writing smart contract, isn't that wonderful?😀 
Copy the next code blocks and put them in their appropriate positions like this. 

```rust
// derive attributes skipped for brevity sake
pub struct Order {
list_of_items: Vec<FoodItem>,
// other code here...
}

impl Order {
        fn new(list_of_items: Vec<FoodItem>, customer: AccountId, id: u32) -> Self {
            let total_price = Order::total_price(&list_of_items);
            Self {
                list_of_items,
                customer,
                total_price,
                paid: false,
                order_id: id, // Default is "getting ingredients" in this case
            }
        }

        fn total_price(list_of_items: &Vec<FoodItem>) -> Balance {
            let mut total = 0;
            for item in list_of_items {
                total += item.price()
            }
            total
        }
    }
```

```rust
// derive attributes skipped for brevity sake
pub struct FoodItem {
burger_menu: BurgerMenu,
// other code here...
}

 impl FoodItem {
        fn price(&self) -> Balance {
            match self.burger_menu {
                BurgerMenu::CheeseBurger => BurgerMenu::CheeseBurger.price() * self.amount as u128,
                BurgerMenu::ChickenBurger => {
                    BurgerMenu::ChickenBurger.price() * self.amount as u128
                }
                BurgerMenu::VeggieBurger => BurgerMenu::VeggieBurger.price() * self.amount as u128,
            }
        }
    }
```

```rust

//you already guessed it, attributes skipped here😀
 pub enum BurgerMenu {
 CheeseBurger,
// Yup! other code here...
}

 impl BurgerMenu {
        fn price(&self) -> Balance {
            match self {
                Self::CheeseBurger => 12,
                Self::VeggieBurger => 10,
                Self::ChickenBurger => 15,
            }
        }
    }
```

Here, we set the necessary methods, for tracking prices for each burger on the menu and for food item(s) when we are adding them into storage.


### Setting up events, creating error types and result type.

Events are very important in your code, especially when you want to query informations about the contract's executions and current state. We will setup different event types for our smart contract. We will also use Ink! attributes for setting up our event types. Copy the following code.

```rust

  /// Event emitted when a token transfer occurs.
    #[ink(event)]
    pub struct Transfer {
        #[ink(topic)]
        from: Option<AccountId>,
        #[ink(topic)]
        to: Option<AccountId>,
        value: Balance,
    }

    /// Event when shop owner get all orders in storage
    #[ink(event)]
    pub struct GetAllOrders {
        #[ink(topic)]
        orders: Vec<(u32, Order)>,
    }

    /// Event when shop owner gets a single order
    #[ink(event)]
    pub struct GetSingleOrder {
        #[ink(topic)]
        single_order: Order,
    }

    /// Event when the shop_owner creates his shop
    #[ink(event)]
    pub struct CreatedShopAndStorage {
        #[ink(topic)]
        orders: Vec<(u32, Order)>, // this only contains a vector because `Mapping` doesn't implement "encode" trait, this means you can't encode or decode it for operational purposes, it also means you can't return `Mapping` as a result for your contract calls
    }
```

We also want to handle errors by returning custom error types. 

```rust

// For catching errors that happens during shop operations
    #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
    pub enum BurgerShopError {
        /// Errors types for different errors.
        PaymentError,
        OrderNotCompleted,
    }
```

Now we can finally create our result type.

```rust
 // result type
    pub type Result<T> = core::result::Result<T, BurgerShopError>;
```

### Writing smart contract call functions
We've set up the necessary types that we will need, now we can start writing functions that will interact with our smart contract.

Let's implement the function that instantiates the smart contract. 

```rust

 impl BurgerShop {
        #[ink(constructor)]
       pub fn new() -> Self {
            let order_storage_vector: Vec<(u32, Order)> = Vec::new();
            let order_storage_mapping = Mapping::new();

            Self {
                orders: order_storage_vector,
                orders_mapping: order_storage_mapping,
            }
        }
}
```

If you noticed, there's a new attribute, this attribute basically flags the function as a special function for instantiating the smart contract, a smart contract must have at least one `constructor`. You can also have multiple constructors. There are also list of other [attributes](https://use.ink/macros-attributes), we will go through the ones we use for this project, I recommend you check the others too!

Next, let's implement the function that takes in an order and also accepts payment.

```rust 

   /// takes the order and makes the payment, we aren't implementing cart feature here for simplicity purposes, ideally the cart feature should be implemented in the frontend
        #[ink(message, payable)]
        pub fn take_order_and_payment(&mut self, list_of_items: Vec<FoodItem>) -> Result<Order> {
            // Gets the caller account id
            let caller = Self::env().caller();

            // this is assertion is opinionated, if you don't want to limit the shop owner from creating an order, you can remove this line
            assert!(
                caller != self.env().account_id(),
                "You are not the customer!"
            );

            // assert the order contains at least 1 item
            for item in &list_of_items {
                assert!(item.amount > 0, "Can't take an empty order")
            }

            // our own local id, you can change this to a hash if you want, but remember to make the neccessary type changes too!
            let id = self.orders.len() as u32;

            // Calculate and set order price
            let total_price = Order::total_price(&list_of_items);
            let mut order = Order::new(list_of_items, caller, id);
            order.total_price = total_price;

            assert!(
                order.paid == false,
                "Can't pay for an order that is paid for already"
            );

            let multiply: Balance = 1_000_000_000_000; // this equals to 1 Azero, so we doing some conversion
            let transfered_val = self.env().transferred_value();

            // assert the value sent == total price
            assert!(
                transfered_val
                    == order
                        .total_price
                        .checked_mul(multiply)
                        .expect("Overflow!!!"),
                "{}", format!("Please pay complete amount which is {}", order.total_price)
            );

            ink::env::debug_println!(
                "Expected value: {}",
                order.total_price
            );
            ink::env::debug_println!(
                "Expected received payment without conversion: {}",
                transfered_val
            );  // we are printing the expected value as is

            // make payment
            match self
                .env()
                .transfer(self.env().account_id(), order.total_price)
            {
                Ok(_) => {
                    // get current length of the list orders in storage, this will act as our unique id
                    let id = self.orders.len() as u32;
                    // mark order as paid
                    order.paid = true;

                    // Emit event
                    self.env().emit_event(Transfer {
                        from: Some(order.customer),
                        to: Some(self.env().account_id()),
                        value: order.total_price,
                    });

                    // Push to storage
                    self.orders_mapping.insert(id, &order);
                    self.orders.push((id, order.clone()));
                    Ok(order)
                }
                Err(_) => Err(BurgerShopError::PaymentError),
            }
        }
```

We see different attributes `message` and `payable`. The `message` attribute indicates that the function can be called publicly, notice `pub` keyword too. Any function(contract call) that implements a `message` attribute, implements a special functionality that allows them to be called by users.

While `payable` means the contract call will receive some value as part of the call, you can also verify the value you're expecting, we did this here in this code block. 

```rust 

let multiply: Balance = 1_000_000_000_000;
let transfered_val = self.env().transferred_value();
  assert!(
                transfered_val
                    == order
                        .total_price
                        .checked_mul(multiply)
                        .expect("Overflow!!!"),
                "{}", format!("Please pay complete amount which is {}", order.total_price)
            );
```

The value that will be sent will come from the frontend or the [contracts UI](https://contracts-ui.substrate.io/). In this article, we will interact with the UI to test our smart contract.

We used `self` a lot here. This refers to the smart contract itself, it contains a lot of rich methods and types including the ones we created in our _storage struct_. 

The `env` method is one of the most used methods, it's basically the smart contract environment, you have access to things like `account_id` of the smart contract, `balance` which means you can transfer native token to your smart contract. Also, the `caller` returns the address of who is actually calling the contract, in our case, whenever a customer makes an order, we have access to the customer's address. 

Whoo! that was a lot! If you've come this far, Congratulations🎉 you're half way, already!

Next up, we are adding the remaining functions for our burger shop.

```rust 

  #[ink(message)]
        /// gets a single order from storage
        pub fn get_single_order(&self, id: u32) -> Order {
            // get single order
            let single_order = self.orders_mapping.get(id).expect("Oh no, Order not found");

            single_order
        }

        #[ink(message)]
        /// gets the orders in storage
        pub fn get_orders(&self) -> Option<Vec<(u32, Order)>> {
            // Get all orders
            let get_all_orders = &self.orders;

            if get_all_orders.len() > 0 {
                Some(get_all_orders.to_vec()) // converts ref to an owned/new vector
            } else {
                None
            }
        }
```

Now, we are done with code. Go ahead, format with this command `cargo fmt`, compile with this command `cargo contract build`.  
Check your `target` folder, you should see this. You can check the source code [here](https://github.com/ayomidebajo/burger_shop)

![ink! target folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qbfvtthcd0ln3rr1mh8.png)

### Deploying our smart contract and interaction

Ink! compiles down to WASM, which means it can be deployed on any platform that supports WASM, it also means you can do some embedded software development😉 that could call smart contract at the end of the program. So many possibilities here!😃

To test our smart contract, we will be using the [contracts ui](https://contracts-ui.substrate.io/). We will be using the `.contract` file, the contract ui supports this file type, it contains all our compiled code.

For you to deploy you contract, you need to use faucet tokens. In this tutorial, we'll be using Aleph zero tokens, they also have a really reliable testnet🤗. You can head to their [website](https://faucet.test.azero.dev/) to get some faucet tokens. Since you're using polkadot address, you can use aleph zero and yes, you can see your tokens [here](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fws.test.azero.dev#/accounts).

Now, let's deploy our smart contract like this. Notice the drop down at the top left corner of your screen, it contains different test nets available in polkadot, in this case we are using **aleph zero testnet**, so choose that like this screenshot here. 


![deploying smart contract on aleph zero testnet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lj0677938nejrl6wyb9p.png)

Then upload your `.contract` file into the uploader.

Click on Next

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7jxjve8l9xs6dy2azly.png)

Notice the page contains all the functions we talked about, especially the **public ones**.

Upload and instantiate your contract

![upload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6uz7ly4xqzeyzb45g3wt.png)


![instantiate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/moarjwt0qcwra4cup2nb.png)

It should ask for your password, it interacts with your polkadot wallet (assuming you have the extension installed on your browser) 

### Interacting with our smart contract
Let's create a new order. I love chicken, so I'm gonna order 1 chicken burger, without adding any amount. 

![contracts ui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pf58cn7so936759tk99s.png)

Notice the error on the right, polkadot blockchains also have the power to check if the outcome of a transaction is going to successful without signing the transaction, It's called "DryRun". You can see our assertion fails and it tells you what line this is located. Let's fix this by adding an amount. 

Now we have a different error. 

![contracts ui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unog0cgnw3ftfzwejhmt.png)

Our assertion fails here. Remember I talked about how to assert the price of the burger is actually inputed in the UI. This is where you input the price you're supposed to pay for a transaction. You can see that if the value isn't up to the `total_price`, it returns an error. 

Let's fix this by inputing the amount that is expected, the error also tells us the expected value. 

![contract ui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6wffjzibcyyfvxc25mp.png)

We get a successful outcome, you can go ahead and click the`call contract` button below. You'll also be asked to input your password for the wallet address that's calling the contract. You'll also get flash messages on the UI indicating the events that took place.

Notice the new output under the dry run outcome. 

![events](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wgso1w8puw39sju5ko9a.png)

You guessed it, these are events emitted. It contains the function that emitted the event, the actual event parameters which is `Transfer` in this case and also the order in which the tokens where transferred. Tbh, I personally love the events output, so satisfying.

Now, you can call the other functions to get all the orders or a single order. 

Congratulations!🎉 You've just written your smart contract and deployed it, You're now on your way to becoming a world class smart contract developer in polkadot!

Ink! smart contracts offer a lot more features, this tutorial introduced you to the fundamentals you need to know when writing smart contracts. You can add more features to your smart contract. We will cover writing unit tests in another article. I hope you found this tutorial helpful.