Quickstart này sử dụng dự án khởi đầu của
Anchor framework được tạo bởi anchor init.
Bạn sẽ tạo dự án trên máy cục bộ, chạy các bài kiểm tra, build chương trình và
xem qua mã nguồn chương trình mà Anchor tạo ra.
Điều Kiện Tiên Quyết
Trước khi bắt đầu, hãy cài đặt các công cụ phát triển Solana. Bộ cài đặt bao gồm Rust, Solana CLI và Anchor CLI.
Sử dụng Anchor CLI phiên bản 1.1.2 trở lên cho template này. Kiểm tra phiên bản đã cài đặt của bạn:
$anchor --version
Tạo Dự Án
Chạy các lệnh sau trong terminal của bạn:
$anchor init my-program$cd my-program
Dự án khởi đầu bao gồm một chương trình Solana nằm trong programs/my-program.
Chương trình này có hai instruction: một để khởi tạo tài khoản counter và một để
tăng giá trị counter.
Một số phần của template minh họa các mẫu chương trình Solana phổ biến: dẫn xuất địa chỉ tài khoản PDA, thực hiện Cross Program Invocation (CPI) để chuyển SOL, và sử dụng kiểm tra lỗi tùy chỉnh để dừng một instruction khi một điều kiện không được thỏa mãn.
Build Chương Trình
Chạy anchor build để biên dịch chương trình khởi đầu:
$anchor build
Chương trình đã biên dịch được ghi vào target/deploy/my_program.so. Khi chương
trình được triển khai, nội dung của tệp .so này sẽ được lưu trữ trong một tài
khoản trên chuỗi.
Chạy Kiểm Thử
Chạy kiểm thử mặc định:
$anchor test
Anchor.toml của template này sử dụng lệnh kiểm thử Rust:
skip_local_validator = true[scripts]test = "cargo test"
Kiểm thử tải chương trình đã biên dịch vào LiteSVM,
tạo một payer, gửi các lệnh initialize và increment, sau đó kiểm tra
trạng thái của tài khoản counter.
Chạy anchor test cũng sẽ biên dịch chương trình, vì vậy bạn không cần chạy
anchor build trước khi kiểm thử cục bộ.
Triển Khai Chương Trình
Kiểm thử cục bộ là vòng phản hồi nhanh nhất. Khi bạn sẵn sàng triển khai lên một mạng, ví dụ như devnet, hãy build trước, sau đó triển khai lên một cluster.
Việc triển khai một chương trình Solana yêu cầu SOL vì chương trình được lưu trữ trong một account, và account đó phải trả phí cho không gian lưu trữ. Trên devnet, hãy yêu cầu devnet SOL miễn phí từ Solana Faucet hoặc qua Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
Các tệp nguồn
Thư mục src chứa chương trình Solana. Tài liệu
Cấu Trúc Chương Trình
của Anchor giải thích các macro cốt lõi được sử dụng ở đây, bao gồm
declare_id!, #[program], #[derive(Accounts)], và
#[account]. Phần này hướng dẫn qua các tệp template.
lib.rs
lib.rs là điểm vào của chương trình. Nó kết nối các tệp nguồn, định nghĩa địa
chỉ chương trình, và định nghĩa các lệnh của chương trình mà người dùng có thể
gọi.
pub mod constants;pub mod error;pub mod instructions;pub mod state;use anchor_lang::prelude::*;pub use constants::*;pub use instructions::*;pub use state::*;declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");#[program]pub mod my_program {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {crate::instructions::initialize::handle_initialize(ctx)}pub fn increment(ctx: Context<Increment>) -> Result<()> {crate::instructions::increment::handle_increment(ctx)}}
[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");
Địa chỉ chương trình giống nhau xuất hiện trong cả cấu hình và mã nguồn.
Anchor.toml cho Anchor biết địa chỉ cần triển khai hoặc gọi cho một cluster.
declare_id! định nghĩa địa chỉ chương trình trong chương trình để kiểm tra
bảo mật.
constants.rs
constants.rs lưu các giá trị dùng chung ở một nơi. Trong template này,
COUNTER_SEED dẫn xuất PDA của bộ đếm, HELLO_WORLD_LAMPORTS được
chuyển trong quá trình khởi tạo, và MAX_COUNT được kiểm tra trước khi tăng
giá trị.
use anchor_lang::prelude::*;#[constant]pub const COUNTER_SEED: &[u8] = b"counter";#[constant]pub const HELLO_WORLD_LAMPORTS: u64 = 1;#[constant]pub const MAX_COUNT: u64 = 10;
#[derive(Accounts)]pub struct Initialize<'info> {// ...#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {// ...anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;Ok(())}
initialize.rs sử dụng hai hằng số:
COUNTER_SEEDlấy địa chỉ PDA của bộ đếm.HELLO_WORLD_LAMPORTSđặt số lượng được chuyển từ người thanh toán sang tài khoản bộ đếm.
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;Ok(())}
increment.rs sử dụng MAX_COUNT làm giới hạn trên
của bộ đếm. Nếu giá trị hiện tại đã đạt tối đa, require! trả về
CounterOverflow và dữ liệu tài khoản không bị thay đổi.
state.rs
state.rs định nghĩa các kiểu dữ liệu tùy chỉnh cho các tài khoản mà chương
trình tạo ra và sở hữu. Chương trình định nghĩa các lệnh để tạo, khởi tạo và cập
nhật dữ liệu đó, nhưng dữ liệu bộ đếm không được lưu trữ bên trong chính chương
trình. Nó được lưu trữ trong một tài khoản riêng biệt với địa chỉ của chính nó.
use anchor_lang::prelude::*;#[account]#[derive(InitSpace)]pub struct Counter {pub count: u64,pub authority: Pubkey,}
#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();Ok(())}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...ctx.accounts.counter.count += 1;Ok(())}
state.rs định nghĩa dữ liệu tài khoản Counter. Các tệp lệnh sử dụng kiểu
đó khi tạo và cập nhật tài khoản:
Counter::INIT_SPACExác định kích thước tài khoản cho các trường được định nghĩa trongstate.rs.countvàauthoritylà các giá trị trường được ghi khi tài khoản được khởi tạo.count += 1cập nhật giá trị bộ đếm đã lưu sau khi xác thực thành công.
error.rs
error.rs định nghĩa các lỗi tùy chỉnh của chương trình. Trong mẫu này, các lỗi
minh họa cách các trình xử lý lệnh dừng lại khi người gọi không được phép cập
nhật bộ đếm hoặc bộ đếm đã đạt đến MAX_COUNT.
use anchor_lang::prelude::*;#[error_code]pub enum ErrorCode {#[msg("Only the counter authority can update this counter")]Unauthorized,#[msg("Counter has reached the maximum value")]CounterOverflow,}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
error.rs liệt kê các lỗi mà lệnh có thể trả về:
ErrorCode::Unauthorizedđược trả về khi người ký không phải là authority được lưu trong tài khoản bộ đếm.ErrorCode::CounterOverflowđược trả về khi bộ đếm đã đạt đếnMAX_COUNT.
instructions.rs
instructions.rs kết nối các tệp lệnh với crate chương trình để lib.rs có thể
truy cập mã lệnh initialize và increment. Mỗi tệp lệnh định nghĩa
các tài khoản mà lệnh đó yêu cầu và logic xử lý chạy sau khi Anchor xác thực các
tài khoản đó.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs định nghĩa các tài khoản cần thiết để tạo tài khoản counter, sau
đó ghi các giá trị đầu tiên của tài khoản. Struct #[derive(Accounts)] sử
dụng các
ràng buộc tài khoản
của Anchor để chỉ định các tài khoản nào là bắt buộc và cách tạo tài khoản
counter mới.
use anchor_lang::prelude::*;use crate::{constants::*, state::Counter};#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Ngữ cảnh tài khoản
Struct Initialize định nghĩa các tài khoản phải được đưa vào khi người
dùng gọi lệnh initialize. Anchor kiểm tra các tài khoản này trước khi
trình xử lý chạy.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Tài khoản người thanh toán
Tài khoản payer trả phí để tạo tài khoản counter. Kiểu Signer<'info>
có nghĩa là người thanh toán phải ký giao dịch, và #[account(mut)] có
nghĩa là tài khoản người thanh toán có thể bị thay đổi vì lamport sẽ bị trừ đi.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Tài khoản Counter
Tài khoản counter lưu trữ dữ liệu Counter từ state.rs. init
yêu cầu Anchor tạo tài khoản này trước khi trình xử lý chạy, và
payer = payer cho Anchor biết tài khoản nào trả phí tạo.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Kích Thước Tài Khoản
Ràng buộc space cho Anchor biết cần cấp phát bao nhiêu dữ liệu cho tài
khoản. Anchor lưu trữ một bộ phân biệt 8 byte trước, sau đó là các byte cần
thiết cho các trường Counter. Bộ phân biệt cho phép Anchor nhận dạng tài
khoản này là tài khoản Counter trước khi giải mã hóa dữ liệu tài khoản.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Địa Chỉ Bộ Đếm
Các ràng buộc seeds và bump xác định địa chỉ PDA dự kiến cho tài
khoản bộ đếm. Anchor xác minh rằng tài khoản counter được cung cấp khớp
với địa chỉ đó. Mẫu sử dụng PDA để người dùng có thể suy ra địa chỉ bộ đếm từ ID
chương trình và seed, giúp địa chỉ bộ đếm trở nên xác định.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
System Program
Tài khoản system_program là bắt buộc vì việc tạo tài khoản mới sử dụng
System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Hàm Xử Lý
Hàm handle_initialize chạy sau khi Anchor xác thực các tài khoản trong
Initialize. Giá trị ctx cho phép hàm xử lý truy cập vào các tài
khoản đã được kiểm tra đó.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Dữ Liệu Ban Đầu
Hàm xử lý ghi các giá trị đầu tiên vào tài khoản bộ đếm mới. Số đếm bắt đầu tại
0, và người thanh toán trở thành quyền hạn được phép tăng bộ đếm sau này.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Tài Khoản Chuyển Khoản
CPI chuyển khoản này được đưa vào chỉ để minh họa cách một CPI truyền tài khoản
đến chương trình khác. Cấu trúc Transfer liệt kê các tài khoản được sử
dụng bởi lệnh chuyển khoản System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Ngữ Cảnh CPI
CpiContext::new kết hợp chương trình được gọi với các tài khoản được
truyền vào chương trình đó. Đây là cấu trúc cơ bản của một CPI: chọn chương
trình cần gọi, thu thập các tài khoản mà chương trình đó yêu cầu, sau đó truyền
cả hai vào lời gọi. Ở đây, chương trình được gọi là System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Gọi Transfer
anchor_lang::system_program::transfer gọi lệnh transfer của System
Program. Trong mẫu này, transfer là một ví dụ đơn giản về việc gọi một chương
trình khác từ chương trình của bạn. Nếu CPI transfer thất bại, lệnh
initialize cũng sẽ thất bại theo.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Ghi Log
msg! ghi một thông báo vào log của chương trình. Ok(()) cho biết
lệnh đã trả về thành công.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
increment.rs
increment.rs xác định các tài khoản cần thiết để cập nhật một tài khoản
counter hiện có. Hàm xử lý kiểm tra rằng người ký là authority đã lưu trữ, kiểm
tra rằng count chưa đạt đến MAX_COUNT được chỉ định, sau đó tăng count
lên.
use anchor_lang::prelude::*;use crate::{constants::*, error::ErrorCode, state::Counter};#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Account Context
Struct Increment xác định các tài khoản phải được đưa vào khi người dùng
gọi lệnh increment. Anchor kiểm tra các tài khoản này trước khi hàm xử lý
chạy.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Counter Account
Tài khoản counter lưu trữ dữ liệu Counter. Ràng buộc mut cho
phép hàm xử lý cập nhật count đã lưu trữ, còn các ràng buộc seeds và
bump xác minh địa chỉ PDA của counter.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Authority Signer
Tài khoản authority phải ký giao dịch. Hàm xử lý sau đó kiểm tra rằng
người ký này khớp với authority được lưu trong tài khoản counter.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Hàm Handler
Hàm handle_increment chạy sau khi Anchor xác thực các tài khoản trong
Increment. Giá trị ctx cho phép handler truy cập vào các tài khoản
đã được kiểm tra đó.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Kiểm Tra Quyền
Kiểm tra đầu tiên đảm bảo rằng người ký được phép cập nhật bộ đếm này. Nếu địa
chỉ của người ký không khớp với counter.authority, lệnh sẽ dừng lại với
ErrorCode::Unauthorized. Điều này minh họa cho việc phân quyền ở cấp độ
ứng dụng: chương trình sở hữu dữ liệu bộ đếm, nhưng nó thực thi một quy tắc xác
định người ký nào có thể thay đổi dữ liệu đó.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Kiểm Tra Giá Trị Tối Đa
Kiểm tra thứ hai ngăn bộ đếm vượt quá MAX_COUNT. Nếu bộ đếm đã đạt đến
giới hạn, lệnh sẽ dừng lại với ErrorCode::CounterOverflow. Giới hạn này là
một quy tắc nhân tạo trong template để bạn có thể thấy cách các lỗi tùy chỉnh
dừng một lệnh trước khi dữ liệu tài khoản bị thay đổi.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Cập Nhật Bộ Đếm
Chỉ sau khi cả hai kiểm tra đều vượt qua, handler mới cập nhật dữ liệu tài khoản. Dòng này cộng thêm một vào giá trị bộ đếm đã lưu trữ.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Ghi Log
msg! ghi số đếm đã cập nhật vào nhật ký chương trình. Ok(()) cho
biết lệnh đã trả về thành công.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Tệp kiểm thử
programs/my-program/tests/test_initialize.rs là một bài kiểm thử tích hợp
Rust. Nó không khởi động một validator cục bộ. Thay vào đó, nó tải tệp .so đã
biên dịch vào LiteSVM, xây dựng các giao dịch gọi chương trình và đọc tài khoản
counter sau mỗi giao dịch. Bài kiểm thử xây dựng các lệnh cho một giao dịch
Solana bằng cách chỉ định ID chương trình cần gọi, cung cấp instruction data và
truyền các tài khoản cần thiết.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);
Ngữ cảnh tài khoản Initialize xác định các tài khoản cần thiết cho lệnh
initialize. Bài kiểm tra truyền những tài khoản đó vào hàm trợ giúp
my_program::accounts::Initialize được tạo tự động:
payerđược truyền vào làpayer: payer.pubkey().counterđược truyền vào làcounter.system_programđược truyền vào làsystem_program::ID.
my_program::instruction::Initialize {}.data()
tạo ra instruction data. Đây là nơi các đối số lệnh sẽ được mã hóa, nhưng lệnh
initialize này không yêu cầu bất kỳ đối số nào.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);
Ngữ cảnh tài khoản Increment xác định các tài khoản cần thiết cho lệnh
increment. Bài kiểm tra truyền những tài khoản đó vào hàm trợ giúp
my_program::accounts::Increment được tạo tự động:
counterđược truyền vào làcounter.authorityđược truyền vào làauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
tạo ra instruction data. Đây là nơi các đối số lệnh sẽ được mã hóa, nhưng lệnh
increment này không yêu cầu bất kỳ đối số nào.
use {anchor_lang::{prelude::Pubkey,solana_program::{instruction::Instruction, system_program},AccountDeserialize, InstructionData, ToAccountMetas,},litesvm::LiteSVM,solana_keypair::Keypair,solana_message::{Message, VersionedMessage},solana_signer::Signer,solana_transaction::versioned::VersionedTransaction,};#[test]fn test_initialize() {let program_id = my_program::id();let payer = Keypair::new();let counter = Pubkey::find_program_address(&[my_program::constants::COUNTER_SEED],&program_id,).0;let mut svm = LiteSVM::new();let bytes = include_bytes!(concat!(env!("CARGO_TARGET_TMPDIR"),"/../deploy/my_program.so"));svm.add_program(program_id, bytes).unwrap();svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 0);assert_eq!(counter_state.authority, payer.pubkey());let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 1);assert_eq!(counter_state.authority, payer.pubkey());}
Cấu hình dự án
Các tệp dự án gốc cho Anchor và Cargo biết cách build, test và triển khai chương trình. Để tham khảo đầy đủ, xem tài liệu Anchor cho cấu hình Anchor.toml và Anchor CLI.
skip_local_validator = true[toolchain][features]resolution = trueskip-lint = false[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"[provider]cluster = "localnet"wallet = "~/.config/solana/id.json"[scripts]test = "cargo test"[hooks]
Is this page helpful?