avatarBloqarl

Summary

This text is a tutorial on how to get started with Rust for smart contracts, specifically for the NEAR protocol, based on the author's experience converting a Solidity smart contract to Rust.

Abstract

The tutorial begins by explaining the author's background in Solidity and their decision to learn Rust for smart contracts. They provide a link to a YouTube playlist of their learning journey and instructions on how to install Rust and the rust-analyzer extension for VS Code. The tutorial then covers the basics of Rust, such as the package manager Cargo, and how to create a new project. The main focus of the tutorial is on converting a simple Solidity smart contract to Rust and integrating it with the NEAR protocol. The author explains the differences between Solidity and Rust, such as the use of self, &self, and &mut self in Rust, and how to add the wasm target to the toolchain. The tutorial concludes with the final version of the Rust smart contract for the NEAR protocol.

Bullet points

  • The author has a background in Solidity and wants to learn Rust for smart contracts.
  • The tutorial provides a YouTube playlist of the author's learning journey.
  • Instructions are given on how to install Rust and the rust-analyzer extension for VS Code.
  • The tutorial covers the basics of Rust, such as the package manager Cargo, and how to create a new project.
  • The main focus of the tutorial is on converting a simple Solidity smart contract to Rust and integrating it with the NEAR protocol.
  • The author explains the differences between Solidity and Rust, such as the use of self, &self, and &mut self in Rust.
  • Instructions are given on how to add the wasm target to the toolchain.
  • The tutorial concludes with the final version of the Rust smart contract for the NEAR protocol.

Getting Started with Rust for Smart Contracts

If you can’t read this article because of the firewall, go here to read it for free!

Hey there people, I’m the Blockchainer and I have been involved with Web3 Security and Smart contracts with Solidity for a few months now.

Recently, I discovered that the demand for Rust smart contract developers and auditors is much higher than I expected, and since learning Rust has been rounding my mind for a while I decided it’s time to start.

The approach I chose to learn Rust is not going step by step but I want to go all in directly to understand how to write and audit smart contracts.

And since I have a Solidity background I decided that I would start by grabbing a Smart Contract written in Solidity and implementing it in Rust.

I had a good experience learning in public as it encouraged me and others to keep pushing for more, so this time, I started to log every single step I took and every single thing I’d learned about smart contracts with Rust.

I didn’t prepare any fancy slides or similar, I will be going through my notes and commenting on how I got to those things if needed.

If you have some Solidity background and would like to learn Rust for Smart Contracts, this YouTube playlist is for you.

Let’s start then…

Installing Rust

curl — proto ‘=https’ — tlsv1.2 -sSf https://sh.rustup.rs | sh

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
> 1

To configure your current shell, run:

source "$HOME/.cargo/env"

rustc --version

You can keep your Rust installation up to date with the latest version by running:

rustup update

Install the rust-analyzer extension on VS code

Cargo

When you install Rust with rustup, the toolset includes cargo among others. You also get Cargo, the Rust package manager, to help download Rust dependencies and build and run Rust programs.

Cargo new

cargo new hello_world

cd hello_world
code .

Learn Rust by Converting a Smart Contract in Solidity

Let’s convert the Smart Contract below to Rust and learn the language as we build it.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;


contract SimpleStorage {
    uint256 myFavoriteNumber;

    struct Person {
        uint256 favoriteNumber;
        string name;
    }
    Person[] public listOfPeople;

    mapping(string => uint256) public nameToFavoriteNumber;

    function store(uint256 _favoriteNumber) public {
        myFavoriteNumber = _favoriteNumber;
    }

    function retrieve() public view returns (uint256) {
        return myFavoriteNumber;
    }

    function addPerson(string memory _name, uint256 _favoriteNumber) public {
        listOfPeople.push(Person(_favoriteNumber, _name));
        nameToFavoriteNumber[_name] = _favoriteNumber;
    }
}

What’s the Rust equivalent of a “contract” declaration in Solidity?

In Rust, there isn’t a direct equivalent to Solidity’s contract keyword since Rust is a general-purpose programming language, whereas Solidity is designed specifically for smart contract development on the Ethereum blockchain.

However, when developing blockchain applications in Rust, one typically uses a struct to encapsulate data and an impl block to define methods operating on that data, somewhat similar to a Solidity contract.

Here’s a simplified version of the solidity smart contract above to illustrate:

pub struct SimpleStorage {
    my_favorite_number: u64,
}

impl SimpleStorage {
    pub fn new() -> Self {
        SimpleStorage {
            my_favorite_number: 0,
        }
    }

    pub fn store(&mut self, favorite_number: u64) {
        self.my_favorite_number = favorite_number;
    }

    pub fn retrieve(&self) -> u64 {
        self.my_favorite_number
    }
}
  • SimpleStorage is defined as a struct instead of a contract.
  • The store and retrieve methods are defined within an impl block associated with SimpleStorage.
  • We added a new method as a constructor, which is a common pattern in Rust.
  • pub indicates that the function is public
  • -> is the equivalent of returns in Solidity

Where are the State variables from Solidity in Rust?

As you can notice my_favorite_number has been moved from the main block or impl.

In Rust, the impl block is used for defining methods associated with a particular struct, enum, or trait. These methods can operate on instances of the struct (or enum), and can access and modify their fields.

Here are some of the elements placed outside the impl block:

  • Struct and Enum Definitions: The actual definitions of structs and enums are placed outside of any impl block.
  • Function Definitions: Standalone functions that are not associated with a particular struct or enum are defined outside of any impl block.
  • Constant Definitions: Constants can also be defined outside of impl blocks.
  • Use Statements: Import statements (use) are placed outside of impl blocks.
  • Module Definitions: If you’re organizing your code into modules, the mod keyword and module definitions go outside of impl blocks.

And here is what goes inside the impl block:

  • Method Definitions: Methods, which are functions that can operate on an instance of the struct or enum, are defined inside the impl block. They always have a self, &self, or &mut self parameter, which refers to the instance they are operating on.
  • Associated Function Definitions: Functions that are related to the struct or enum but do not operate on an instance of it are defined inside the impl block but do not take a self parameter. The new function, which is often used as a constructor, is a common example of an associated function.

which takes me to the next questions

How and when to use `&mut self`, `&self`, or `self` in the Rust?

In Rust, self, &self, and &mut self are used within method signatures in an impl block to specify how the method accesses the data of the struct it's associated with.

Here's a breakdown:

self:

  • Ownership Transfer: When a method has self as its first parameter, it takes ownership of the instance. This means that the instance can no longer be used after the method is called unless the method returns the instance.
  • Usage: This is typically used for methods that transform self into something else and where it makes sense for the original instance to be consumed.

I want to show you now both how would self be used and what would be the equivalent in Solidity:

impl SimpleStorage {
    // ... other methods ...

    pub fn total_people(self) -> usize {
        let total = self.list_of_people.len();
        // self will be dropped at the end of this method
        total
    }
}

In Solidity, the equivalent to Rust’s self is this. However, Solidity doesn't have the concept of consuming a contract instance like in Rust.

contract SimpleStorage {
    // ... other members ...

    function totalPeople() public view returns (uint256) {
        return listOfPeople.length;
    }
}

I hope providing this kind of examples is helping understand it. Let’s continue.

&self:

  • Immutable Borrow: When a method has &self as its first parameter, it borrows the instance immutably. This means that the method can read the data in the instance, but cannot modify it.
  • Usage: This is used for methods that need to access the data of the instance but do not need to modify it.

This is easier seen in the example:

pub fn retrieve(&self) -> u64 {
    self.my_favorite_number
}

&mut self:

  • Mutable Borrow: When a method has &mut self as its first parameter, it borrows the instance mutably. This means that the method can read and modify the data in the instance.
  • Usage: This is used for methods that need to modify the instance.
pub fn store(&mut self, favorite_number: u64) {
    self.my_favorite_number = favorite_number;
}

Small parenthesis

I’ve just discovered that the best way to continue learning to write smart contracts with Rust is to base it on a specific chain. And after some recommendations, I will be basing the smart contracts to work for NEAR protocol.

Hence it seems I need to add wasm target to my toolchain with:

rustup target add wasm32-unknown-unknown

I had some compilation errors and the solution was to add the right dependencies’ version to my Cargo.toml file. At the moment are these:

[dependencies]
near-sdk = "4.1.1"  
borsh = "0.10.3"
serde = "1.0.189"

Sssso…

This is how my SimpleStorage.rs rust file looks like at the moment, with the integration to NEAR protocol:

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::Vector;
use near_sdk::near_bindgen;
use serde::Serialize;


#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct SimpleStorage {
    my_favorite_number: u64,
    list_of_people: Vector<Person>,
    name_to_favorite_number: near_sdk::collections::LookupMap<String, u64>,
}

#[derive(BorshDeserialize, BorshSerialize, Serialize)]
pub struct Person {
    favorite_number: u64,
    name: String,
}

#[near_bindgen]
impl SimpleStorage {
    #[init]
    pub fn new() -> Self {
        Self {
            my_favorite_number: 0,
            list_of_people: Vector::new(b"list_of_people".to_vec()),
            name_to_favorite_number: near_sdk::collections::LookupMap::new(b"name_to_favorite_number".to_vec()),
        }
    }

    pub fn store(&mut self, favorite_number: u64) {
        self.my_favorite_number = favorite_number;
    }

    pub fn retrieve(&self) -> u64 {
        self.my_favorite_number
    }

    pub fn add_person(&mut self, name: String, favorite_number: u64) {
        let person = Person {
            favorite_number,
            name: name.clone(),
        };
        self.list_of_people.push(&person);
        self.name_to_favorite_number.insert(&name, &favorite_number);
    }
}
Rust
Web3
Defi
Blockchain Development
Smart Contracts
Recommended from ReadMedium