Instructions & CPIs
Instruction là các khối xây dựng của các chương trình Solana, định nghĩa các hành động có thể được thực hiện. Trong Anchor, instruction được triển khai như các hàm với các thuộc tính và ràng buộc cụ thể. Hãy khám phá cách làm việc với chúng một cách hiệu quả.
Cấu trúc Instruction
Trong Anchor, instruction được định nghĩa bằng module #[program]
và các hàm instruction riêng lẻ. Đây là cấu trúc cơ bản:
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
// Instruction logic here
Ok(())
}
}
Ngữ cảnh của Instruction
Mỗi hàm instruction nhận một struct Context
làm tham số đầu tiên. Context này chứa:
accounts
: Các tài khoản được truyền cho instructionprogram_id
: Public key của chương trìnhremaining_accounts
: Bất kỳ tài khoản bổ sung nào không được định nghĩa rõ ràng trong struct contextbumps
: Trườngbumps
đặc biệt hữu ích khi làm việc với PDAs, vì nó cung cấp các bump seeds đã được sử dụng để tạo ra địa chỉ PDA (chỉ khi bạn đang tạo chúng trong account struct)
Có thể được truy cập bằng cách:
// Accessing accounts
ctx.accounts.account_1
ctx.accounts.account_2
// Accessing program ID
ctx.program_id
// Accessing remaining accounts
for remaining_account in ctx.remaining_accounts {
// Process remaining account
}
// Accessing bumps for PDAs
let bump = ctx.bumps.pda_account;
Instruction Discriminator
Giống như accounts, instructions trong Anchor sử dụng discriminators để xác định các loại instruction khác nhau. Discriminator mặc định là một prefix 8-byte được tạo bằng cách sử dụng sha256("global:<instruction_name>")[0..8]
. Tên instruction nên được viết theo snake_case.
Custom Instruction Discriminator
Bạn cũng có thể chỉ định một discriminator tùy chỉnh cho instructions của mình:
#[instruction(discriminator = 1)]
pub fn custom_discriminator(ctx: Context<Custom>) -> Result<()> {
// Instruction logic
Ok(())
}
Instruction Scaffold
Bạn có thể viết instruction theo nhiều cách khác nhau, trong phần này chúng ta sẽ học một số phong cách và cách bạn có thể thiết lập chúng
Instruction Logic
Logic instruction có thể được tổ chức theo nhiều cách khác nhau, tùy thuộc vào độ phức tạp của chương trình và phong cách lập trình ưa thích của bạn. Dưới đây là các phương pháp chính:
- Inline Instruction Logic
Đối với các instruction đơn giản, bạn có thể viết logic trực tiếp trong hàm instruction:
pub fn initialize(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Transfer tokens logic
// Close token account logic
Ok(())
}
- Separate Module Implementation
Đối với các chương trình rất phức tạp, bạn có thể tổ chức logic trong các module riêng biệt:
// In a separate file: transfer.rs
pub fn execute(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Transfer tokens logic
// Close token account logic
Ok(())
}
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
transfer::execute(ctx, amount)
}
- Separate Context Implementation
Đối với các instruction phức tạp hơn, bạn có thể chuyển logic sang implementation của context struct:
// In a separate file: transfer.rs
pub fn execute(ctx: Context<Transfer>, amount: u64) -> Result<()> {
ctx.accounts.transfer_tokens(amount)?;
ctx.accounts.close_token_account()?;
Ok(())
}
impl<'info> Transfer<'info> {
/// Transfers tokens from source to destination account
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
// Transfer tokens logic
Ok(())
}
/// Closes the source token account after transfer
pub fn close_token_account(&mut self) -> Result<()> {
// Close token account logic
}
}
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
transfer::execute(ctx, amount)
}
Instruction Parameters
Instructions có thể nhận các tham số ngoài context. Các tham số này được serialize và deserialize tự động bởi Anchor. Dưới đây là những điểm chính về instruction parameters:
- Basic Types
Anchor hỗ trợ tất cả các kiểu dữ liệu nguyên thủy của Rust và các kiểu Solana phổ biến:
pub fn complex_instruction(
ctx: Context<Complex>,
amount: u64,
pubkey: Pubkey,
vec_data: Vec<u8>,
) -> Result<()> {
// Instruction logic
Ok(())
}
- Custom Types
Bạn có thể sử dụng các kiểu tùy chỉnh làm tham số, nhưng chúng phải implement AnchorSerialize
và AnchorDeserialize
:
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct InstructionData {
pub field1: u64,
pub field2: String,
}
pub fn custom_type_instruction(
ctx: Context<Custom>,
data: InstructionData,
) -> Result<()> {
// Instruction logic
Ok(())
}
Best Practices
-
Giữ Instructions Tập Trung: Mỗi instruction nên làm một việc và làm tốt. Nếu một instruction đang làm quá nhiều việc, hãy xem xét chia nó thành nhiều instructions.
-
Sử Dụng Context Implementation: Đối với các instruction phức tạp, hãy sử dụng phương pháp context implementation để:
- Giữ code của bạn được tổ chức
- Làm cho việc test dễ dàng hơn
- Cải thiện khả năng tái sử dụng
- Thêm tài liệu phù hợp
-
Xử Lý Lỗi: Luôn sử dụng xử lý lỗi phù hợp và trả về các thông báo lỗi có ý nghĩa:
#[error_code]
pub enum TransferError {
#[msg("Insufficient balance")]
InsufficientBalance,
#[msg("Invalid amount")]
InvalidAmount,
}
impl<'info> Transfer<'info> {
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
require!(amount > 0, TransferError::InvalidAmount);
require!(
self.source.amount >= amount,
TransferError::InsufficientBalance
);
// Transfer logic
Ok(())
}
}
- Tài Liệu: Luôn tài liệu hóa logic instruction của bạn, đặc biệt khi sử dụng context implementation:
impl<'info> Transfer<'info> {
/// # Transfers tokens
///
/// Transfers tokens from source to destination account
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
// Implementation
Ok(())
}
}
Cross-Program Invocations (CPIs)
Cross Program Invocations (CPI) đề cập đến quá trình một chương trình gọi các instructions của chương trình khác, điều này cho phép tính kết hợp của các chương trình Solana. Anchor cung cấp một cách thuận tiện để thực hiện CPIs thông qua CpiContext
và các builders cụ thể cho từng chương trình.
Lưu ý: Bạn có thể tìm thấy tất cả System Program CPI bằng cách sử dụng anchor crate chính và thực hiện: use anchor_lang::system_program::*
; và đối với những cái liên quan đến SPL token program, chúng ta sẽ cần import anchor_spl crate và thực hiện: use anchor_spl::token::*
Basic CPI Structure
Đây là cách thực hiện một CPI cơ bản:
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
pub fn transfer_lamport(ctx: Context<TransferLamport>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
};
let cpi_program = ctx.accounts.system_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
transfer(cpi_ctx, amount)?;
Ok(())
}
CPI with PDA Signers
Khi thực hiện CPIs yêu cầu chữ ký PDA, hãy sử dụng CpiContext::new_with_signer
:
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
pub fn transfer_lamport_with_pda(ctx: Context<TransferLamportWithPda>, amount: u64) -> Result<()> {
let seeds = &[
b"vault".as_ref(),
&[ctx.bumps.vault],
];
let signer = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
};
let cpi_program = ctx.accounts.system_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
transfer(cpi_ctx, amount)?;
Ok(())
}
Error Handling
Anchor cung cấp một hệ thống xử lý lỗi mạnh mẽ cho instructions. Đây là cách implement custom errors và xử lý chúng trong instructions của bạn:
#[error_code]
pub enum MyError {
#[msg("Custom error message")]
CustomError,
#[msg("Another error with value: {0}")]
ValueError(u64),
}
pub fn handle_errors(ctx: Context<HandleErrors>, value: u64) -> Result<()> {
require!(value > 0, MyError::CustomError);
require!(value < 100, MyError::ValueError(value));
Ok(())
}