Workshop 4: Use the Rust programming language, Anchor
Bài viết này sẽ cung cấp cho bạn đầy đủ thông tin để có cái nhìn tổng quan về Smart Contract và nắm các kiến thức căn bản của Anchor, Rust. Tham khảo ngay!

Bài viết này sẽ cung cấp cho bạn đầy đủ thông tin để có cái nhìn tổng quan về Smart Contract và nắm các kiến thức căn bản của Anchor, Rust. Tham khảo ngay!
NGÔN NGỮ RUST
Rust là ngôn ngữ lập trình hệ thống có tốc độ thực thi cực kỳ nhanh, ngăn chặn các lỗi segfaults và đảm bảo thread safety.
Module hệ thống
- Rust tổ chức code bằng cách sử dụng “Modules System" bao gồm:
- Modules: chia code thành các đơn vị logic riêng để sử dụng
- Crates: là một thư viện hay chương trình thực thi. Source code của crate thường được chia thành nhiều module
- Packages: chứa đựng bộ sưu tập các crates cũng như tệp manifest cho riêng metadata và các dependencies giữa các packages
Paths và scope
- Trong Rust crates chứa các modules định nghĩa các chức năng mà có thể chia sẻ với nhiều projects.
- Nói về cấu trúc của crate giống như một cây mà crate là thân và các modules là các nhánh. Mỗi nhánh sẽ có các module, mục phụ được bổ sung vào.
- Path: đường dẫn đến một module hoặc mục cụ thể là tên của mỗi bước từ crate đến module đó, trong đó mỗi bước được phân tách bằng `::`. Ví dụ cấu trúc sau:
- Crate cơ sở: `solana_program`
- `solana_program` chứa module là `account_info`
- `account_info` chứa cấu trúc là `AccountInfo`->
-> đường dẫn đến `AccountInfo` là `solana_program::account_info::AccountInfo`
- Sử dụng từ khoá `use` để khai báo sử dụng:
use solana_program::account_info::AccountInfo
Khai báo function trong Rust
- Trong Rust khai báo một function bằng cách sử dụng `fn`
fn process_instruction()
Chúng ta có thể truyền vào các tham số và định dạng kiểu dữ liệu cho các biến số đó
- Rust được biết đến là một ngôn ngữ “statically typed” và mỗi giá trị trong Rust đều phải có kiểu dữ liệu “data type” để nói cho Rust biết được kiểu của tất cả các biến khi biên dịch chương trình. Ví dụ:
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
)
Và chúng ta có thể định nghĩa giá trị trả về của function bằng cách sử dụng `->`
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult
Result enums
- Result là một thư viện đại diện cho kết quả trả về: success (Ok) và fail (Err)
- Sử dụng “Ok” hoặc “Err”:
Ok(String::from("Success!"));
Err(404);
Variables
- Trong Rust, biến được gán bởi từ khoá “let”
let age = 33;
- Biến mặc định là immutable (bất biến) nghĩa là giá trị của biến không thể thay đổi khi đã được gán giá trị. Để tạo biến có thể cập nhật giá trị, cần sử dụng từ khoá “mut”. Ví dụ định nghĩa biến dưới đây sẽ gây ra lỗi do “age” là immutable giống như const (hằng số) không thể thay đổi cập nhật.
// compiler will throw error
let age = 33;
age = 34;
// this is allowed
let mut mutable_age = 33;
mutable_age = 34;
Structs
- Struct hay structure là một kiểu dữ liệu tuỳ chỉnh mà có thể đóng gói lại với nhau và đặt tên cho nhiều giá trị có liên quan tạo nên một nhóm có ý nghĩa. Lấy ví dụ:
struct User {
active: bool,
email: String,
age: u64
}
- Sau khi định nghĩa cấu trúc dữ liệu, chúng ta có thể sử dụng:
let mut user1 = User {
active: true,
email: String::from("test@test.com"),
age: 36
};
- Và có thể get và set giá trị cho chúng bằng cách:
user1.age = 37;
Enums
- Enumerations (hoặc Enums) là một cấu trúc dữ liệu cho phép bạn xác định một kiểu bằng cách liệt kê các biến thể có thể có của nó. Ví dụ:
enum LightStatus {
On,
Off
}
- Một tín hiêu đèn enum “LightStatus” có 2 trạng thái là: “On” hoặc “Off”. Bạn có thể thêm vào giá trị như đèn sáng màu đỏ, xanh, … như dưới:
enum LightStatus {
On {
color: String
},
Off
}
let light_status = LightStatus::On { color: String::from("red") };
Match statements
- Match statements giống như mệnh đề “switch” chúng ta hay sử dụng trong C/C++/JS. Ví dụ:
enum Coin {
Penny,
Nickel,
Dime,
Quarter
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25
}
}
Implementations
- Từ khoá “impl” được sử dụng để xác định kiểu implementations. Hàm và hằng số có thể được định nghĩa bên trong nó. Ví dụ:
struct Example {
number: i32
}
impl Example {
fn boo() {
println!("boo! Example::boo() was called!");
}
fn answer(&mut self) {
self.number += 42;
}
fn get_number(&self) -> i32 {
self.number
}
}
- Bạn có thể sử dụng hàm “boo” bằng cách gọi:
Example::boo();
- Sử dụng hàm “answer”
let mut example = Example { number: 3 };
example.answer();
Tổng quan về Anchor, Rust, Smart Contract
Đối với lập trình viên, quá trình đi từ ý tưởng đến sản phẩm thực tiễn là một con đường gian nan với vô số rào cản, trong đó phần lập trình thô gây nhàm chán nhất.
Anchor ra đời với các bản soạn sẵn phong phú và cơ số bước kiểm tra bảo mật, giúp developer rút ngắn thời gian lập trình thô và tập trung vào công đoạn quan trọng nhất - phát triển sản phẩm của chính mình.
Anchor là gì?
Anchor(1) là một framework giúp các developer phát triển Smart Contract trên Solana nhanh chóng, dễ dàng và an toàn hơn.
Anchor có cách đặc điểm sau:
- Sử dụng ngôn ngữ Rust(2)
- IDL để chỉ rõ cách hoạt động, sử dụng chương trình
- Tự động tạo TypeScript Package từ IDL
- Hỗ trợ nhiều câu lệnh, CLI ngắn gọn dễ dùng
Cài đặt môi trường
Tìm hiểu thêm thông tin tại (3).
Bước 1. Cài đặt Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup component add rustfmt
Tham khảo tại https://www.rust-lang.org/tools/install
Bước 2. Cài đặt Solana
sh -c "$(curl -sSfL https://release.solana.com/v1.14.8/install)"
Tham khảo tại https://docs.solana.com/cli/install-solana-cli-tools
Sau đó chạy solana-keygen new để tạo một cặp khóa ở vị trí mặc định. Anchor sẽ sử dụng cặp khóa này để chạy thử nghiệm chương trình của bạn.
Bước 3. Cài đặt Yarn
npm install -g yarn
Bước 4. Cài đặt Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install latest
avm use latest
Tham khảo thêm tại:https://www.anchor-lang.com/docs/installation
Tạo ứng dụng đầu tiên
Để khởi tạo một project mới, bạn hãy chạy lệnh sau:
anchor init my_program
cd my_program
Tham khảo thêm tại: https://www.anchor-lang.com/docs/installation
Tạo ứng dụng đầu tiên
Để khởi tạo một project mới, bạn hãy chạy lệnh sau:
anchor init my_program
cd my_program
Trong đó my_app là tên project của bạn.
Sau khi tạo thành công, cấu trúc project sẽ như sau:
- programs/my_program/src/lib.rs: Nơi định nghĩa, lập trình Smart Contract
- programs/my_program/Cargo.toml: Nơi cài đặt các thư viện sử dụng
- target: Nơi chứa dữ liệu sau khi build. Bao gồm IDL
- test: Viết test cho Smart Contract
Chạy test chương trình bằng dòng lệnh:
anchor test
Hệ thống sẽ tự động thực thi các bước sau và cho ra kết quả:
- Build smart contract từ file src/lib.rs
- Tự động Deploy localhost
- Ghi kết quả vào thư mục target
- Chạy file test/my_program.ts → phân tích cấu trúc Smart Contract được build từ target/idl/my_program.json → tạo data phù hợp gọi lên Program đã được tự động deploy ở bước 2
- Trả về kết quả
Khi chạy anchor test, hệ thống đã tự build giúp mình. Nếu muốn build thủ công, bạn có thể dùng câu lệnh: anchor build
Sau khi build IDL được tạo ra ở thư mục target/idl/my_program.json
Một instruction cũng giống như một hàm thông thường, bao gồm ba thông tin quan trọng là:
- name: Tên hàm, dùng program.rpc để gọi
- accounts: Danh sách tài khoản sẽ tương tác
- arg: tham số truyền vào hàm
Để hiểu rõ hơn, chúng ta sẽ cùng thực hành một ví dụ: Viết một chương trình tính tổng bao gồm:
Một hàm như vậy còn được gọi là instruction. Để viết và sử dụng một instruction, chúng ta cần đi qua các bước sau:
Khai báo cấu trúc dữ liệu cho trường hợp tạo mới
- Khai báo các tài khoản tương tác
- Khai bao instruction trong main
Bước 1. Khởi tạo
1. Khai báo cấu trúc dữ liệu
#[account]
pub struct SumAccount {
pub sum: u64,
}
2. Khai báo các tài khoản sẽ tương tác
#[derive(Accounts)]
pub struct Initialize<'info> {
// địa chỉ thuê
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
// người trả phí giao dịch
#[account(mut)]
pub user: Signer<'info>,
// địa chỉ chương trình giúp thuê tài khoản
pub system_program: Program<'info, System>,
}
3. Khai báo hàm thực thi
pub fn initialize(ctx: Context<Initialize>, sum_init: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.sum = sum_init;
Ok(())
}
Chương trình của chúng ta sẽ trông như thế này:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod my_program {
use super::*;
pub fn initialize_sum(ctx: Context<InitializeSum>, sum_init: u64) -> Result<()> {
ctx.accounts.sum_account.sum = sum_init;
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeSum<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub sum_account: Account<'info, SumAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct SumAccount {
pub sum: u64,
}
4. Chạy anchor build để chương trình tạo IDL
"instructions": [
{
"name": "initializeSum",
"accounts": [
{
"name": "sumAccount",
"isMut": true,
"isSigner": true
},
{
"name": "user",
"isMut": true,
"isSigner": true
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "sumInit",
"type": "u64"
}
]
}
],
Dựa vào IDL, chúng ta biết được cách sử dụng hàm này từ client bằng typescript. Cập nhật file test như sau:
import { Program, setProvider, web3, Provider, workspace, BN } from "@project-serum/anchor";
import { SystemProgram } from "@solana/web3.js";
import { MyProgram } from "../target/types/my_program";
describe("my_program", () => {
setProvider(Provider.env());
const program = workspace.MyProgram as Program<MyProgram>;
// Tạo địa chỉ thuê
const sumAccount = web3.Keypair.generate();
it("Is initialized!", async () => {
await program.rpc.initializeSum(new BN(1), {
accounts: {
sumAccount: sumAccount.publicKey,
user: program.provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [sumAccount],
});
let sumAccountData = await program.account.sumAccount.fetch(sumAccount.publicKey);
console.log("sumAccountData", sumAccountData.sum);
});
});
Chạy anchor test cho ra kết quả:
Vậy là chúng ta đã thuê thành công một tài khoản để chứa dữ liệu tính tổng.
Bước 2. Cập nhật dữ liệu
Lúc này, account chứa dữ liệu đã được tạo nên không cần khai báo cấu trúc SumAccount nữa. Cập nhật các tài khoản tương tác và khai báo intruction:
#[derive(Accounts)]
pub struct UpdateSum<'info> {
#[account(mut)]
pub sum_account: Account<'info, SumAccount>,
}
pub fn update_sum(ctx: Context<UpdateSum>, number: u64) -> Result<()> {
ctx.accounts.sum_account.sum += number;
Ok(())
}
Chương trình sẽ trông như thế này:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod my_program {
use super::*;
pub fn initialize_sum(ctx: Context<InitializeSum>, sum_init: u64) -> Result<()> {
ctx.accounts.sum_account.sum = sum_init;
Ok(())
}
pub fn update_sum(ctx: Context<UpdateSum>, number: u64) -> Result<()> {
ctx.accounts.sum_account.sum += number;
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeSum<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub sum_account: Account<'info, SumAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateSum<'info> {
#[account(mut)]
pub sum_account: Account<'info, SumAccount>,
}
#[account]
pub struct SumAccount {
pub sum: u64,
}
Gọi từ client:
it("Update sum!", async () => {
await program.rpc.updateSum(new BN(2), {
accounts: {
sumAccount: sumAccount.publicKey,
},
});
let sumAccountData = await program.account.sumAccount.fetch(sumAccount.publicKey);
console.log("sumAccountData", sumAccountData.sum);
});
Chạy anchor test và cho ra kết quả:
Sum đã được cập nhật từ 1 lên 3.
Vậy là chúng ta vừa đi qua 2 ví dụ cơ bản của lập trình trên blockchain. Hãy xem video để thực hành và bình luận nếu có thắc mắc nhé!
Các tài liệu tham khảo
- https://project-serum.github.io/anchor/getting-started/introduction.html
- https://doc.rust-lang.org/book/https://project-serum.github.io/anchor/getting-started/installation.html
Các bài viết liên quan
Workshop 1: Introduction to Solana
Workshop 2: Xây dựng giao diện tương tác với Solana
Workshop 3: Client interaction with common Solana programs (SPLT, Metaplex)
Về Sentre Protocol
Sentre Protocol là nền tảng mở All-in-One hoạt động trên mạng lưới Solana, cung cấp DApp Store và giao thức mở giúp tích lũy thanh khoản. Với Sentre:
- Người dùng có thể cài đặt các DApp yêu thích của mình và trải nghiệm thế giới DeFi trên một nền tảng duy nhất;
- Lập trình viên và đối tác có thể phát hành DApp thông qua Sen Store, tận dụng tài nguyên có sẵn và tự do đóng góp cho nền tảng.