Anweisungen & CPIs
Anweisungen sind die Bausteine von Solana-Programmen und definieren die Aktionen, die ausgeführt werden können. In Anchor werden Anweisungen als Funktionen mit spezifischen Attributen und Einschränkungen implementiert. Lassen Sie uns erkunden, wie man effektiv mit ihnen arbeitet.
Anweisungsstruktur
In Anchor werden Anweisungen mithilfe des #[program] Moduls und einzelner Anweisungsfunktionen definiert. Hier ist die grundlegende Struktur:
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
// Instruction logic here
Ok(())
}
}Anweisungskontext
Jede Anweisungsfunktion erhält eine Context Struktur als ersten Parameter. Dieser Kontext enthält:
accounts: Die Konten, die an die Anweisung übergeben werdenprogram_id: Der öffentliche Schlüssel des Programmsremaining_accounts: Alle zusätzlichen Konten, die nicht explizit in der Kontextstruktur definiert sindbumps: DasbumpsFeld ist besonders nützlich bei der Arbeit mit PDAs, da es die Bump-Seeds bereitstellt, die zur Ableitung der PDA-Adressen verwendet wurden (nur wenn Sie diese in der Kontostruktur ableiten)
Darauf kann zugegriffen werden durch:
// 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;Anweisungsdiskriminator
Wie Konten verwenden auch Anweisungen in Anchor Diskriminatoren, um verschiedene Anweisungstypen zu identifizieren. Der Standard-Diskriminator ist ein 8-Byte-Präfix, das mit sha256("global:<instruction_name>")[0..8] generiert wird. Der Anweisungsname sollte in snake_case sein.
Benutzerdefinierter Anweisungsdiskriminator
Sie können auch einen benutzerdefinierten Diskriminator für Ihre Anweisungen angeben:
#[instruction(discriminator = 1)]
pub fn custom_discriminator(ctx: Context<Custom>) -> Result<()> {
// Instruction logic
Ok(())
}Anweisungsgerüst
Sie können Ihre Anweisungen auf verschiedene Arten schreiben. In diesem Abschnitt werden wir einige der Stile und Möglichkeiten erläutern, wie Sie diese tatsächlich einrichten können
Instruction Logic
Die Logik von Instruktionen kann je nach Komplexität deines Programms und deinem bevorzugten Programmierstil unterschiedlich organisiert werden. Hier sind die wichtigsten Ansätze:
Inline-Instruktionslogik
Für einfache Instruktionen kannst du die Logik direkt in der Instruktionsfunktion schreiben:
pub fn initialize(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Transfer tokens logic
// Close token account logic
Ok(())
}Separate Modulimplementierung
Für sehr komplexe Programme kannst du die Logik in separaten Modulen organisieren:
// 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 Kontextimplementierung
Für komplexere Instruktionen kannst du die Logik in die Implementierung der Kontext-Struktur verschieben:
// 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
Instruktionen können über den Kontext hinaus Parameter akzeptieren. Diese Parameter werden von Anchor automatisch serialisiert und deserialisiert. Hier sind die wichtigsten Punkte zu Instruktionsparametern:
Grundlegende Typen
Anchor unterstützt alle primitiven Rust-Typen und gängige Solana-Typen:
pub fn complex_instruction(
ctx: Context<Complex>,
amount: u64,
pubkey: Pubkey,
vec_data: Vec<u8>,
) -> Result<()> {
// Instruction logic
Ok(())
}Benutzerdefinierte Typen
Du kannst benutzerdefinierte Typen als Parameter verwenden, aber sie müssen AnchorSerialize und AnchorDeserialize implementieren:
#[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
Halte Instruktionen fokussiert: Jede Instruktion sollte eine Sache gut erledigen. Wenn eine Instruktion zu viel macht, erwäge sie in mehrere Instruktionen aufzuteilen.
Verwende Kontextimplementierung: Für komplexe Instruktionen nutze den Kontextimplementierungsansatz, um:
Deinen Code organisiert zu halten
Das Testen zu erleichtern
Die Wiederverwendbarkeit zu verbessern
Angemessene Dokumentation hinzuzufügen
Fehlerbehandlung: Verwende immer eine angemessene Fehlerbehandlung und gib aussagekräftige Fehlermeldungen zurück:
#[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(())
}
}Dokumentation: Dokumentiere immer deine Instruktionslogik, besonders wenn du die Kontextimplementierung verwendest:
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) beziehen sich auf den Prozess, bei dem ein Programm Anweisungen eines anderen Programms aufruft, was die Komponierbarkeit von Solana-Programmen ermöglicht. Anchor bietet eine bequeme Möglichkeit, CPIs durch CpiContext und programmspezifische Builder durchzuführen.
Hinweis: Alle System Program CPIs können Sie über das Haupt-Anchor-Crate finden, indem Sie Folgendes ausführen: use anchor_lang::system_program::*; und für die CPIs des SPL-Token-Programms müssen wir das anchor_spl Crate importieren und Folgendes ausführen: use anchor_spl::token::*
Grundlegende CPI-Struktur
So führen Sie einen grundlegenden CPI durch:
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 mit PDA-Signierern
Bei CPIs, die PDA-Signaturen erfordern, verwenden Sie 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(())
}Fehlerbehandlung
Anchor bietet ein robustes Fehlerbehandlungssystem für Anweisungen. So implementieren Sie benutzerdefinierte Fehler und behandeln sie in Ihren Anweisungen:
#[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(())
}