Anchor
Anchor für Einsteiger

Anchor für Einsteiger

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:

rust
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 werden

  • program_id: Der öffentliche Schlüssel des Programms

  • remaining_accounts: Alle zusätzlichen Konten, die nicht explizit in der Kontextstruktur definiert sind

  • bumps: Das bumps Feld 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:

rust
// 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.

Anchor Discriminator Calculator
Instruction
sha256("global:" + snake_case(seed))[0..8]
[0, 0, 0, 0, 0, 0, 0, 0]

Benutzerdefinierter Anweisungsdiskriminator

Sie können auch einen benutzerdefinierten Diskriminator für Ihre Anweisungen angeben:

rust
#[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:

  1. Inline-Instruktionslogik

Für einfache Instruktionen kannst du die Logik direkt in der Instruktionsfunktion schreiben:

rust
pub fn initialize(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    // Transfer tokens logic

    // Close token account logic
    
    Ok(())
}
  1. Separate Modulimplementierung

Für sehr komplexe Programme kannst du die Logik in separaten Modulen organisieren:

rust
// 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)
}
  1. Separate Kontextimplementierung

Für komplexere Instruktionen kannst du die Logik in die Implementierung der Kontext-Struktur verschieben:

rust
// 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:

  1. Grundlegende Typen

Anchor unterstützt alle primitiven Rust-Typen und gängige Solana-Typen:

rust
pub fn complex_instruction(
    ctx: Context<Complex>,
    amount: u64,
    pubkey: Pubkey,
    vec_data: Vec<u8>,
) -> Result<()> {
    // Instruction logic
    Ok(())
}
  1. Benutzerdefinierte Typen

Du kannst benutzerdefinierte Typen als Parameter verwenden, aber sie müssen AnchorSerialize und AnchorDeserialize implementieren:

rust
#[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

  1. Halte Instruktionen fokussiert: Jede Instruktion sollte eine Sache gut erledigen. Wenn eine Instruktion zu viel macht, erwäge sie in mehrere Instruktionen aufzuteilen.

  2. 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

  3. Fehlerbehandlung: Verwende immer eine angemessene Fehlerbehandlung und gib aussagekräftige Fehlermeldungen zurück:

rust
#[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(())
    }
}
  1. Dokumentation: Dokumentiere immer deine Instruktionslogik, besonders wenn du die Kontextimplementierung verwendest:

rust
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:

rust
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:

rust
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:

rust
#[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(())
}
Blueshift © 2025Commit: e573eab