Dieses Schnellstart-Tutorial verwendet das
Anchor-Framework Starterprojekt, das von
anchor init generiert wird. Du erstellst das Projekt lokal, führst seine Tests
aus, baust das Programm und gehst den Programmcode durch, den Anchor generiert.
Voraussetzungen
Bevor du beginnst, installiere die Solana-Entwicklungswerkzeuge. Die Installation umfasst Rust, die Solana CLI und die Anchor CLI.
Verwende Anchor CLI Version 1.1.2 oder höher für diese Vorlage. Überprüfe deine installierte Version:
$anchor --version
Projekt erstellen
Führe die folgenden Befehle in deinem Terminal aus:
$anchor init my-program$cd my-program
Das Starterprojekt enthält ein Solana-Programm unter programs/my-program. Das
Programm enthält zwei Anweisungen: eine zum Initialisieren eines Zähler-Konten
und eine zum Erhöhen des Zählers.
Einige Teile der Vorlage veranschaulichen gängige Solana-Programmierungsmuster: Ableiten von PDA-Konten-Adressen, Durchführen einer Cross Program Invocation (CPI) zum Übertragen von SOL und Verwenden benutzerdefinierter Fehlerprüfungen, um eine Anweisung zu stoppen, wenn eine Bedingung nicht erfüllt ist.
Programm bauen
Führe anchor build aus, um das Starterprogramm zu kompilieren:
$anchor build
Das kompilierte Programm wird in target/deploy/my_program.so geschrieben. Wenn
das Programm bereitgestellt wird, werden die Inhalte dieser .so-Datei in einem
Konten onchain gespeichert.
Test ausführen
Führen Sie den Standardtest aus:
$anchor test
Das Anchor.toml dieses Templates verwendet den Rust-Testbefehl:
skip_local_validator = true[scripts]test = "cargo test"
Der Test lädt das kompilierte Programm in LiteSVM,
erstellt einen Zahler, sendet die initialize- und increment
Anweisungen und überprüft dann den Zustand des Zähler-Konten.
Das Ausführen von anchor test kompiliert das Programm ebenfalls, sodass Sie
beim lokalen Testen anchor build nicht zuerst ausführen müssen.
Programm deployen
Lokale Tests bieten die schnellste Feedbackschleife. Wenn Sie bereit sind, auf einem Netzwerk zu deployen, beispielsweise auf dem Devnet, erstellen Sie zunächst einen Build und deployen Sie dann auf einem Cluster.
Das Deployment eines Solana-Programms erfordert SOL, da das Programm in einem program account gespeichert wird und der program account für den genutzten Speicherplatz bezahlen muss. Auf dem Devnet können Sie kostenloses Devnet-SOL über den Solana Faucet oder mit der Solana CLI anfordern:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
Quelldateien
Das Verzeichnis src enthält das Solana-Programm. Anchors
Programmstruktur
Dokumentation erklärt die hier verwendeten Kern-Makros, einschließlich
declare_id!, #[program], #[derive(Accounts)] und
#[account]. Dieser Abschnitt führt durch die Template-Dateien.
lib.rs
lib.rs ist der Einstiegspunkt des Programms. Es verbindet die Quelldateien,
definiert die Programmadresse und definiert die Programm- Anweisungen, die
Benutzer aufrufen können.
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");
Dieselbe Programmadresse erscheint in der Konfiguration und im Code.
Anchor.toml teilt Anchor mit, welche Adresse für ein Cluster deployed oder
aufgerufen werden soll. declare_id! definiert die Programmadresse im
Programm für Sicherheitsprüfungen.
constants.rs
constants.rs hält gemeinsam genutzte Werte an einem Ort. In dieser Vorlage
leitet COUNTER_SEED die Counter-PDA ab, HELLO_WORLD_LAMPORTS wird
bei der Initialisierung übertragen, und MAX_COUNT wird vor dem
Inkrementieren geprüft.
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 verwendet zwei Konstanten:
COUNTER_SEEDleitet die PDA-Adresse des Zählers ab.HELLO_WORLD_LAMPORTSlegt den Betrag fest, der vom Zahler auf das Zählerkonto übertragen wird.
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 verwendet MAX_COUNT als obere Grenze
des Zählers. Wenn der aktuelle Zählerstand bereits das Maximum erreicht hat,
gibt require! den Wert CounterOverflow zurück und die Kontodaten
werden nicht geändert.
state.rs
state.rs definiert benutzerdefinierte Datentypen für Konten, die das Programm
erstellt und verwaltet. Das Programm definiert Anweisungen zum Erstellen,
Initialisieren und Aktualisieren dieser Daten, die Zählerdaten werden jedoch
nicht im Programm selbst gespeichert. Sie werden in einem separaten Konto mit
eigener Adresse gespeichert.
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 definiert die Counter Kontendaten. Die Anweisungsdateien
verwenden diesen Typ beim Erstellen und Aktualisieren der Konten:
Counter::INIT_SPACElegt die Größe der Konten für die instate.rsdefinierten Felder fest.countundauthoritysind die Feldwerte, die beim Initialisieren der Konten geschrieben werden.count += 1aktualisiert den gespeicherten Zählerwert, nachdem die Validatoren erfolgreich abgeschlossen wurden.
error.rs
error.rs definiert die benutzerdefinierten Fehler des Programms. In dieser
Vorlage zeigen die Fehler, wie Anweisungs-Handler abbrechen, wenn ein Aufrufer
nicht berechtigt ist, den Zähler zu aktualisieren, oder der Zähler bereits
MAX_COUNT erreicht hat.
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 benennt die Fehler, die die Anweisung zurückgeben kann:
ErrorCode::Unauthorizedwird zurückgegeben, wenn der Signer nicht die im Zähler-Konten gespeicherte Autorität ist.ErrorCode::CounterOverflowwird zurückgegeben, wenn der Zähler bereitsMAX_COUNTerreicht hat.
Anweisungen.rs
instructions.rs verbindet die Anweisungen-Dateien mit der Programm-Crate,
damit lib.rs auf den initialize- und increment-Anweisungs-Code
zugreifen kann. Jede Anweisungs-Datei definiert die Konten, die von dieser
Anweisung benötigt werden, sowie die Handler-Logik, die ausgeführt wird, nachdem
Anchor diese Konten validiert hat.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs definiert die Konten, die zum Erstellen des Zähler-Kontos
erforderlich sind, und schreibt dann die ersten Werte des Kontos. Die
#[derive(Accounts)]-Struktur verwendet Anchor
account constraints,
um anzugeben, welche Konten erforderlich sind und wie das neue Zähler-Konto
erstellt wird.
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(())}
Account Context
Die Initialize-Struktur definiert die Konten, die angegeben werden müssen,
wenn ein Benutzer die initialize- Anweisung aufruft. Anchor prüft diese
Konten, bevor der Handler ausgeführt wird.
#[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>,}
Payer Account
Das payer-Konto bezahlt die Erstellung des Zähler-Kontos. Der Typ
Signer<'info> bedeutet, dass der Zahler die Transaktion unterzeichnen
muss, und #[account(mut)] bedeutet, dass das Zahler-Konto geändert werden
kann, da lamports abgezogen werden.
#[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>,}
Counter Account
Das counter-Konto speichert die Counter-Daten aus state.rs.
init weist Anchor an, dieses Konto vor der Ausführung des Handlers zu
erstellen, und payer = payer teilt Anchor mit, welches Konto die
Erstellung bezahlt.
#[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>,}
Konten-Größe
Der space Constraint teilt Anchor mit, wie viel Kontenspeicher allokiert
werden soll. Anchor speichert zuerst einen 8-Byte-Diskriminator, dann die für
die Counter Felder benötigten Bytes. Der Diskriminator ermöglicht es
Anchor, dieses Konto als Counter Konten zu erkennen, bevor die Kontendaten
deserialisiert werden.
#[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>,}
Zähler-Adresse
Die Constraints seeds und bump definieren die erwartete PDA-Adresse
für das Zähler-Konten. Anchor überprüft, ob das angegebene counter Konten
mit dieser Adresse übereinstimmt. Das Template verwendet eine PDA, damit Nutzer
die Zähleradresse aus der Programm-ID und dem seed ableiten können, was die
Zähleradresse deterministisch macht.
#[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
Das system_program Konten wird benötigt, da das Erstellen eines neuen
Kontos das System Program verwendet.
#[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>,}
Handler-Funktion
Die Funktion handle_initialize wird ausgeführt, nachdem Anchor die Konten
in Initialize validiert hat. Der Wert ctx gibt dem Handler Zugriff
auf diese geprüften Konten.
#[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(())}
Anfangsdaten
Der Handler schreibt die ersten Werte in das neue Zähler-Konten. Der Zähler
startet bei 0, und der Zahler wird zur Autorität, die berechtigt ist, den
Zähler später zu erhöhen.
#[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(())}
Übertragungs-Konten
Dieser Transfer-CPI ist nur enthalten, um zu demonstrieren, wie ein CPI Konten
an ein anderes Programm weitergibt. Das Transfer Struct listet die vom
System Program Transfer verwendeten Konten auf.
#[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(())}
CPI-Kontext
CpiContext::new kombiniert das aufgerufene Programm mit den Konten, die an
dieses Programm übergeben werden. Dies ist die grundlegende Struktur eines CPI:
das aufzurufende Programm auswählen, die von diesem Programm erwarteten Konten
sammeln und dann beides in den Aufruf übergeben. Hier ist das aufgerufene
Programm das 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(())}
Transfer aufrufen
anchor_lang::system_program::transfer ruft die Transfer- Anweisungen des
System Program auf. In dieser Vorlage ist der Transfer ein einfaches Beispiel
für den Aufruf eines anderen Programms aus Ihrem Programm heraus. Wenn der
Transfer-CPI fehlschlägt, schlägt auch die initialize- Anweisungen fehl.
#[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(())}
Nachricht protokollieren
msg! schreibt eine Nachricht in die Programmprotokolle. Ok(()) gibt
an, dass die Anweisungen erfolgreich zurückgegeben wurde.
#[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 definiert die Konten, die zum Aktualisieren eines bestehenden
Zähler-Kontos erforderlich sind. Der Handler prüft, ob der Signer die
gespeicherte Autorität ist, ob der Zählerstand das angegebene MAX_COUNT
noch nicht erreicht hat, und erhöht dann den Zählerstand.
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(())}
Konto-Kontext
Das Increment-Struct definiert die Konten, die angegeben werden müssen,
wenn ein Benutzer die increment Anweisungen aufruft. Anchor prüft diese
Konten, bevor der Handler ausgeführt wird.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Zähler-Konto
Das counter-Konto speichert die Counter-Daten. Die
mut-Einschränkung erlaubt dem Handler, den gespeicherten Zählerstand zu
aktualisieren, und die Einschränkungen seeds und bump überprüfen die
PDA-Adresse des Zählers.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Autorität-Signer
Das authority-Konto muss die Transaktion signieren. Der Handler prüft
anschließend, ob dieser Signer mit der im Zähler-Konto gespeicherten Autorität
übereinstimmt.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Handler-Funktion
Die Funktion handle_increment wird ausgeführt, nachdem Anchor die Konten
in Increment validiert hat. Der Wert ctx gibt dem Handler Zugriff
auf diese geprüften Konten.
#[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(())}
Autorisierungsprüfung
Die erste Prüfung stellt sicher, dass der Signer berechtigt ist, diesen Zähler
zu aktualisieren. Wenn die Adresse des Signers nicht mit counter.authority
übereinstimmt, wird die Anweisung mit ErrorCode::Unauthorized abgebrochen.
Dies demonstriert die Autorisierung auf Anwendungsebene: Das Programm besitzt
die Zählerdaten, implementiert aber eine Regel, welcher Signer diese Daten
ändern darf.
#[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(())}
Prüfung des Maximalwerts
Die zweite Prüfung verhindert, dass der Zähler MAX_COUNT überschreitet.
Wenn der Zähler bereits den Grenzwert erreicht hat, wird die Anweisung mit
ErrorCode::CounterOverflow abgebrochen. Dieser Grenzwert ist eine
künstliche Regel in der Vorlage, damit Sie sehen können, wie benutzerdefinierte
Fehler eine Anweisung stoppen, bevor Kontodaten geändert werden.
#[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(())}
Zähler aktualisieren
Erst nachdem beide Prüfungen bestanden wurden, aktualisiert der Handler die Kontodaten. Diese Zeile addiert eins zum gespeicherten Zählerwert.
#[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(())}
Log-Nachricht
msg! schreibt den aktualisierten Zählerstand in die Programm-Logs.
Ok(()) zeigt an, dass die Anweisung erfolgreich zurückgegeben wurde.
#[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(())}
Testdatei
programs/my-program/tests/test_initialize.rs ist ein Rust-Integrationstest. Es
wird kein lokaler validator gestartet. Stattdessen wird die kompilierte
.so-Datei in LiteSVM geladen, Transaktionen erstellt, die das Programm
aufrufen, und das Zählerkonto nach jeder Transaktion ausgelesen. Der Test
erstellt Anweisungen für eine Solana-Transaktion, indem die Programm-ID des
aufzurufenden Programms angegeben, instruction data bereitgestellt und die
erforderlichen Konten übergeben werden.
#[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),);
Der Initialize Kontokontex definiert die Konten, die von der
initialize Anweisung benötigt werden. Der Test übergibt dieselben Konten
an den generierten my_program::accounts::Initialize Helfer:
payerwird alspayer: payer.pubkey()übergeben.counterwird alscounterübergeben.system_programwird alssystem_program::IDübergeben.
my_program::instruction::Initialize {}.data()
erstellt die instruction data. Hier würden Anweisungsargumente kodiert werden,
aber diese initialize Anweisung erfordert keine Argumente.
#[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),);
Der Increment Kontokontex definiert die Konten, die von der
increment Anweisung benötigt werden. Der Test übergibt dieselben Konten an
den generierten my_program::accounts::Increment Helfer:
counterwird alscounterübergeben.authoritywird alsauthority: payer.pubkey()übergeben.
my_program::instruction::Increment {}.data()
erstellt die instruction data. Hier würden Anweisungsargumente kodiert werden,
aber diese increment Anweisung erfordert keine Argumente.
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());}
Projektkonfiguration
Die Stammprojektdateien teilen Anchor und Cargo mit, wie das Programm gebaut, getestet und deployed werden soll. Eine vollständige Referenz findest du in der Anchor-Dokumentation für die Anchor.toml-Konfiguration und die 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?