

1. EVM or WASM?
Với sự phổ biến của Ethereum, khi nói về hợp đồng thông minh, chúng ta thường mặc định sử dụng ngôn ngữ Solidity để phát triển hợp đồng thông minh dựa trên EVM. Tuy nhiên, do những hạn chế về thời gian tạo khối chậm và phí giao dịch cao của Ethereum, ngày càng có nhiều công nghệ tối ưu hóa và chuỗi công khai mới được tung ra. WASM là một trong những công nghệ tiêu biểu. Là một cú pháp nhị phân hoàn toàn mới, WASM có nhiều ưu điểm, chẳng hạn như kích thước lệnh nhỏ, tốc độ hoạt động nhanh và an toàn bộ nhớ. Do đó, các hợp đồng thông minh chạy trên WASM có thể giảm đáng kể tài nguyên blockchain bị chiếm dụng, cải thiện đáng kể tốc độ và hiệu quả của việc tạo khối và chạy ổn định hơn, cho phép người dùng có được trải nghiệm tốt hơn. WASM hỗ trợ nhiều ngôn ngữ phát triển front-end khác nhau, bao gồm Rust, C, C++, TypeScript, AssemblyScript, v.v. Xem xét khả năng thích ứng và chuỗi công cụ cũng như tính bảo mật của ngôn ngữ, Rust là một trong những lựa chọn rất tốt.
2. Lựa chọn BlockSec
Nhiệm vụ của BlockSec là làm cho toàn bộ hệ sinh thái Defi trở nên an toàn hơn. Do đó, ngoài việc cung cấp dịch vụ kiểm toán, chúng tôi cũng hy vọng sẽ hỗ trợ nhiều hơn cho cộng đồng từ góc độ phát triển bảo mật. Dựa trên nhiều ưu điểm của Rust và WASM, chúng tôi quyết định mang đến cho bạn một loạt bài chia sẻ dành riêng cho ngăn xếp công nghệ này và hy vọng rằng bạn có thể tiếp tục chú ý đến chúng tôi. Chúng tôi đã điều tra một số dự án chuỗi công khai phổ biến hơn hiện nay và chuỗi công khai NEAR cũng sử dụng cùng một nhóm công nghệ. NEAR vốn hỗ trợ các hợp đồng WASM, đồng thời hỗ trợ ngôn ngữ Rust và AssemblyScript để phát triển các hợp đồng thông minh. Do đó, chúng tôi sẽ bắt đầu chia sẻ và thảo luận dựa trên chuỗi công khai NEAR.
3. Phát triển hợp đồng thông minh với Rust
Ngôn ngữ Rust do Mozilla phát triển, sau khi chương trình được biên dịch, nó chạy với tốc độ đáng kinh ngạc, tỷ lệ sử dụng bộ nhớ rất cao và hỗ trợ các phong cách lập trình hướng đối tượng và chức năng. Có lẽ nhiều sinh viên vẫn còn khá mới mẻ với ngôn ngữ Rust. Nhưng đừng lo lắng, bắt đầu từ blog này, BlockSec sẽ làm việc với bạn để loại bỏ sương mù của Rust, để mọi người có thể sử dụng Rust để phát triển các hợp đồng thông minh hiệu quả và an toàn.
4. Cấu hình môi trường
4.1 Sử dụng IDE
Khi chúng ta học cách sử dụng một ngôn ngữ mới để phát triển, cần phải chọn một IDE xuất sắc. Ở đây, BlockSec khuyên bạn nên sử dụng Visual Studio Code với các plugin Rust (chẳng hạn như Rust-analyzer), hầu như có thể đáp ứng nhu cầu hàng ngày của bạn. Nếu có điều kiện bạn cũng có thể dùng thử plugin Jetbrains Clion + Rust, sinh viên có thể sử dụng miễn phí.
4.2 Cài đặt chuỗi công cụ Rust
Khi chúng ta có một IDE xuất sắc, đương nhiên chúng ta cần tải xuống và cài đặt Rust. Rust cung cấp một phương pháp cài đặt rất đơn giản và thuận tiện. Trong hệ thống Linux, chúng ta chỉ cần chạy dòng mã sau để tự động tải xuống và cài đặt Rust.
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Sau khi cài đặt hoàn tất, chúng ta có thể kiểm tra xem cài đặt có thành công hay không bằng cách thực thi $rustup --version. Rustup đóng vai trò là người quản lý chuỗi công cụ Rust, cung cấp các phương thức để cài đặt, xóa, cập nhật, chọn và quản lý các chuỗi công cụ này cũng như các thành phần liên quan của chúng. Sau đó, chúng ta cần thêm mục tiêu WASM (WebAssugging) vào chuỗi công cụ bằng cách thực hiện lệnh sau:
$ rustup target add wasm32-unknown-unknown
5. Hợp đồng Rust đầu tiên
Cuối cùng, chúng tôi đã đến điểm. Sau đây, chúng tôi sẽ giúp bạn hiểu và nắm vững cách sử dụng Rust để viết hợp đồng thông minh bằng cách phân tích sâu từng dự án hợp đồng thông minh. Nếu bạn quan tâm đến ngôn ngữ Rust, có rất nhiều hướng dẫn trên Internet, bạn cũng có thể tham khảo nó.
5.1 Trình quản lý gói của Rust
Với sự hỗ trợ của toàn bộ cộng đồng nguồn mở dành cho Rust, nhiều thư viện bên thứ ba khác nhau xuất hiện trong một dòng vô tận. Để quản lý tốt hơn các thư viện này, Cargo đã ra đời. Lệnh cài đặt trên cũng sẽ giúp bạn cài đặt Cargo cùng lúc. Cargo hỗ trợ các nhà phát triển thực hiện các nhiệm vụ như tạo các dự án Rust mới, tải xuống và biên dịch các thư viện mà các dự án Rust phụ thuộc vào cũng như xây dựng toàn bộ dự án.
5.2 Tạo dự án hợp đồng Rust đầu tiên
Khi chúng ta đã sẵn sàng cho môi trường phát triển, trước tiên hãy sử dụng Cargo để tạo một dự án hợp đồng mới và đặt tên là StatusMessage.
$ cargo init --lib StatusMessage
Cây thư mục của project như sau:
StatusMessage/
├── Cargo.toml
└── src
└── lib.rs
5.3 Khai báo hợp đồng
Một hợp đồng thông minh (Smart Contract) thường cần duy trì một bộ dữ liệu trạng thái hợp đồng. Đoạn mã sau được viết trong src/lib.rs khai báo một hợp đồng đơn giản có tên là StatusMessage.
1 #[near_bindgen]
2 #[derive(BorshDeserialize, BorshSerialize)]
3 pub struct StatusMessage {
4 records: LookupMap
5 }
Tiếp theo, chúng ta sẽ phân tích kỹ năm dòng mã trên. Dòng 1 và 2 bắt đầu bằng #, tương tự như chú thích. Thực chất đây là một dạng macro trong Rust. Nó sẽ nhận các dòng 3-5 làm đầu vào và theo định nghĩa của macro, tạo ra đầu ra. Ví dụ: #[nearbindgen] trong dòng đầu tiên thực sự được xác định bởi hàm nearbindgen trong gói phiên bản gần sdk-macro, là nơi các macro được sử dụng để tự động tạo mã tiêm (Mã được tiêm tự động tạo macro, được gọi thành MAGIC ).
Không sao nếu bạn không hiểu. Chúng ta chỉ cần biết chức năng của dòng 1 và 2. Cụ thể, cấu trúc được chú thích bằng #[nearbindgen] sẽ trở thành hợp đồng thông minh trên NEAR. Và các cấu trúc khác chỉ là cấu trúc thông thường. Vì vậy, [nearbindgen] là một gói do NEAR phát triển và được cung cấp cho các nhà phát triển. #[derive(BorshDeserialize, BorshSerialize)] trong dòng 2 được sử dụng để tuần tự hóa và giải tuần tự hóa, để trạng thái của hợp đồng có thể được truyền ở định dạng nhị phân trên chuỗi. Dòng 3-5 là một cấu trúc được gọi là StatusMessage, duy trì trạng thái của hợp đồng thông minh. Và nội dung của trạng thái được mô tả ở dòng 4. Cấu trúc này chỉ chứa một biến thành viên có tên là bản ghi. Loại của nó là LookupMap, có thể được coi đơn giản là một loại từ điển. Cả khóa và giá trị đều là các loại chuỗi bình thường.
5.4 Đặt giá trị mặc định của hợp đồng
Khi khai báo một hợp đồng, chúng ta thường cần xác định giá trị mặc định của nó. Đoạn mã sau đặt giá trị mặc định của hợp đồng StatusMessage.
1 impl Default for StatusMessage {
2 fn default() -> Self {
3 Self {
4 records: LookupMap::new(b"r".to_vec()),
5 }
6 }
7 }
Trong số đó, dòng 1 tuyên bố rằng đây là việc triển khai giá trị mặc định cho StatusMessage. Dòng 2 tuyên bố rằng tên phương thức là mặc định và giá trị trả về là Self. Bản thân đại diện cho phạm vi mô-đun hiện tại trong Rust, cụ thể là đại diện cho một thể hiện StatusMessage. Dòng 3-5 là định nghĩa của thể hiện. Vì thể hiện chỉ chứa các bản ghi một biến kiểu LookupMap. Bằng cách chuyển vào một mảng nhị phân b"r".tovec(),LookupMap có thể được khởi tạo. Phương thức mới của LookupMap được định nghĩa bởi chính NEAR, b"r".tovec() Cho biết tiền tố của các khóa được lưu trữ trong Bản đồ tra cứu này.
5.5 Xác định các phương thức hợp đồng
Sau khi chúng tôi sử dụng một cấu trúc để xác định trạng thái của hợp đồng, chúng tôi cũng cần xác định một loạt các phương thức để các phương thức được hiển thị này có thể được gọi thông qua các giao dịch bên ngoài. Sau đây là hai phương thức được xác định, có thể sửa đổi và lấy giá trị bản ghi tương ứng trong hợp đồng hiện tại. Lưu ý rằng khi xác định phương thức của hợp đồng, chúng ta cũng cần thêm #[near_bindgen], như thể hiện trong dòng 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 }
Từ khóa impl trên dòng 2 chỉ ra rằng chúng tôi đang triển khai StatusMessage một cách cụ thể.
Các dòng 3-6 xác định phương thức setstatus. Chức năng này được sử dụng để thiết lập trạng thái của hợp đồng hiện tại. Phần thứ ba trong số này khai báo tên phương thức và biến. Hàm này có hai biến, đó là &mut self và message: String. &mut đại diện cho một tham chiếu đến bản thân và có thể sửa đổi nội dung của bản thân. Và thông báo: Chuỗi chỉ ra rằng loại thông báo là Chuỗi. Đồng thời, chức năng được trang trí với từ khóa pub Lưu ý rằng chỉ chức năng được trang trí với pub fn mới có thể được gọi bởi các giao dịch bên ngoài, cho biết rằng nó là công khai.
Dòng 4 xác định biến cục bộ accountid, có giá trị được lấy thông qua env::signeraccountid(), cho biết id người dùng đã khởi tạo chữ ký giao dịch.
Dòng 5 chèn accountid làm khóa và thông báo làm giá trị vào bản ghi. Lưu ý rằng thông báo là một biến kiểu Chuỗi, được người dùng truyền vào. Và &message có nghĩa là tham chiếu đến tin nhắn.
Các dòng 8-10 khai báo một chức năng khác gọi là getstatus. Khác với setstatus, getstatus sẽ trả về giá trị kiểu None hoặc String, ở đây chúng ta sử dụng Option để đại diện cho nó.
Tóm tắt và xem trước vấn đề này
Tóm tắt và xem trước vấn đề này
Đây là blog đầu tiên của BlockSec về các hợp đồng Rust. Trong số này, chúng tôi mô tả nền tảng của các hợp đồng Rust và cách tạo một hợp đồng đơn giản dựa trên chuỗi NEAR. Trong số tiếp theo, chúng tôi sẽ mô tả thêm cách sử dụng Rust để viết các trường hợp thử nghiệm đơn vị cho hợp đồng mà chúng tôi đã tạo để gỡ lỗi hợp đồng của mình.
