Rust smart contract development diary (1)
BlockSec
2022-03-29 10:25
本文约3914字,阅读全文需要约16分钟
In addition to providing audit services, BlockSec also hopes to give more support to the community from the perspective of security development.

1. EVM or WASM?

With the popularity of Ethereum, when we talk about smart contracts, we often use the Solidity language to develop smart contracts based on EVM by default. However, due to the shortcomings of Ethereum's slow block time and high transaction fees, more and more optimization technologies and new public chains have been launched. WASM is one of the representative technologies. As a brand-new binary syntax, WASM has many advantages, such as small instruction size, fast operation speed, and memory safety. Therefore, smart contracts running on WASM can greatly reduce the occupied blockchain resources, significantly improve the speed and efficiency of block generation, and run more stably, allowing users to obtain a better experience. WASM supports many different front-end development languages, including Rust, C, C++, TypeScript, AssemblyScript, etc. Considering the adaptation and tool chain, and the security of the language itself, Rust is one of the very good choices.

2. Choice of BlockSec

BlockSec's mission is to make the entire Defi ecosystem more secure. Therefore, in addition to providing audit services, we also hope to give more support to the community from the perspective of security development. Based on the many advantages of Rust and WASM, we decided to bring you a series of sharing specifically for this technology stack, and hope that you can continue to pay attention to us. We have investigated some of the more popular public chain projects today, and the NEAR public chain also uses the same technology stack. NEAR natively supports WASM contracts, and supports Rust language and AssemblyScript to develop smart contracts. Therefore, we will start our sharing and discussion based on the NEAR public chain.

3. Develop smart contracts with Rust

The Rust language is developed by Mozilla. After the program is compiled, it runs at an amazing speed, has a very high memory utilization rate, and supports functional and object-oriented programming styles. Perhaps many students are still relatively new to the Rust language. But don’t worry, starting from this blog, BlockSec will work with you to remove the fog of Rust, so that everyone can use Rust to develop efficient and safe smart contracts.

4. Environment configuration

4.1 IDE use

When we are learning to use a new language to develop, it must be necessary to choose an excellent IDE. Here, BlockSec recommends that you use Visual Studio Code with Rust plugins (such as Rust-analyzer), which can almost meet your daily needs. If you have the conditions, you can also try the Jetbrains Clion + Rust plugin, students can use it for free.

4.2 Install the Rust toolchain

When we have an excellent IDE, we naturally need to download and install Rust. Rust provides a very simple and convenient installation method. In the Linux system, we only need to run the following line of code to automatically download and install Rust.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

After the installation is complete, we can check whether the installation was successful by executing $ rustup --version. rustup acts as a manager for Rust toolchains, providing methods for installing, removing, updating, selecting, and managing these toolchains and their associated components. Then we need to add the WASM (WebAssembly) target to the toolchain by executing the following command:

$ rustup target add wasm32-unknown-unknown

5. The first Rust contract

Finally, we got to the point. Here, we will take you to understand and master how to use Rust to write smart contracts by deeply analyzing each smart contract project. If you are interested in the Rust language itself, there are many tutorials on the Internet, you can also refer to it.

5.1 Rust's package manager

With the support of the entire open source community for Rust, various third-party libraries emerge in an endless stream. In order to better manage these libraries, Cargo came into being. The above installation command will also help you install Cargo at the same time. Cargo assists developers with tasks such as creating new Rust projects, downloading and compiling libraries that Rust projects depend on, and fully building entire projects.

5.2 Create the first Rust contract project

When we are ready for the development environment, first use Cargo to create a new contract project and name it StatusMessage.

$ cargo init --lib StatusMessage

The directory tree of the project is as follows:

StatusMessage/
├── Cargo.toml
└── src
          └── lib.rs

5.3 Declare a contract

A smart contract (Smart Contract) often needs to maintain a set of contract status data. The following piece of code written in src/lib.rs declares a simple contract called StatusMessage.

1  #[near_bindgen]
2  #[derive(BorshDeserialize, BorshSerialize)]
3  pub struct StatusMessage {
4      records: LookupMap,
5 }

Next, we will carefully analyze the above five lines of code. Lines 1 and 2 start with #, similar to comments. In fact, this is a form of macro in Rust. It will receive lines 3-5 as input and, according to the definition of the macro, produce output. For example, #[nearbindgen] in the first line is actually defined by the nearbindgen function in the near-sdk-macros-version package, which is where macros are used to automatically generate injection code (Macros-Auto-Generated Injected Code, referred to as MAGIC ).

It's okay if you don't understand. We only need to know the function of lines 1 and 2. Specifically, the struct annotated with #[nearbindgen] will become a smart contract on NEAR. And the other structs are just ordinary structs. So [nearbindgen] is a package developed by NEAR and made available to developers. The #[derive(BorshDeserialize, BorshSerialize)] in line 2 is used for serialization and deserialization, so that the state of the contract can be transmitted in binary format on the chain. Lines 3-5 are a structure called StatusMessage, which maintains the status of a smart contract. And the content of the status is described in line 4. This structure contains only one member variable named records. Its type is LookupMap, which can be simply regarded as a dictionary type. Both key and value are normal string types.

5.4 Setting contract defaults

When we declare a contract, we often need to define its default value. The following code sets the default value of the contract StatusMessage.

1  impl Default for StatusMessage {
2      fn default() -> Self {
3          Self {
4              records: LookupMap::new(b"r".to_vec()),
5         }
6     }
7 }

Among them, line 1 declares that this is an implementation of the default value for StatusMessage. Line 2 declares that the method name is default and the return value is Self. Self represents the current module scope in Rust, specifically, represents a StatusMessage instance. Lines 3-5 are the definition of the instance. Since the instance only contains records a variable of type LookupMap. By passing in a binary array b"r".tovec(),The LookupMap can be initialized. The new method of LookupMap is defined by NEAR itself, b"r".tovec() Indicates the prefix of the keys stored in this LookupMap.

5.5 Define contract methods

After we use a structure to define the state of the contract, we also need to define a series of methods, so that these exposed methods can be called through external transactions. The following are two defined methods, which can modify and obtain the records value in the current contract respectively. Note that when defining the method of the contract, we also need to add #[near_bindgen], as shown in line 1:

1  #[near_bindgen]
2  impl StatusMessage {
3      pub fn set_status(&mut self, message: String) {
4          let account_id = env::signer_account_id();
5          self.records.insert(&account_id, &message);
6     }
7
8      pub fn get_status(&self, account_id: String) -> Option   {
9          return self.records.get(&account_id);
10     }
11 }

The impl keyword on line 2 indicates that we are implementing the StatusMessage concretely.

Lines 3-6 define the method setstatus. This function is used to set the state of the current contract. The third of these declares method names and variables. This function has two variables, namely &mut self and message: String. &mut represents a reference to self, and may modify the contents of self. And message: String indicates that the type of message is String. At the same time, the function is decorated with the keyword pub. Note that only the function decorated with pub fn can be called by external transactions, indicating that it is public.

Line 4 defines a local variable accountid, whose value is obtained through env::signeraccountid(), indicating the user id who initiated the transaction signature.

Line 5 inserts accountid as key and message as value into records. Note that message is a variable of String type, which is passed in by the user. And &message means a reference to message.

Lines 8-10 declare another function called getstatus. Different from setstatus, getstatus will return a value of None or String type, here we use Option to represent it.

Summary and preview of this issue

Summary and preview of this issue

This is BlockSec's first blog for Rust contracts. In this issue, we describe the background of Rust contracts and how to create a simple contract based on the NEAR chain. In the next issue, we will further describe how to use Rust to write unit test cases for the contract we created to debug our contract.

BlockSec
作者文库