Ten szybki start wykorzystuje projekt startowy
frameworka Anchor wygenerowany przez
anchor init. Utworzysz projekt lokalnie, uruchomisz jego testy, zbudujesz
program i przejrzysz kod programu, który generuje Anchor.
Wymagania wstępne
Zanim zaczniesz, zainstaluj narzędzia deweloperskie Solana. Instalacja obejmuje Rust, Solana CLI oraz Anchor CLI.
Użyj Anchor CLI w wersji 1.1.2 lub wyższej dla tego szablonu. Sprawdź zainstalowaną wersję:
$anchor --version
Utwórz projekt
Uruchom następujące polecenia w swoim terminalu:
$anchor init my-program$cd my-program
Projekt startowy zawiera jeden program Solana w katalogu programs/my-program.
Program zawiera dwie instrukcje: jedną do inicjalizacji konta licznika i jedną
do inkrementacji licznika.
Niektóre części szablonu demonstrują typowe wzorce programów Solana: wyprowadzanie adresów kont PDA, wykonywanie Cross Program Invocation (CPI) w celu transferu SOL oraz używanie niestandardowych sprawdzeń błędów do zatrzymania instrukcji, gdy warunek nie jest spełniony.
Zbuduj Program
Uruchom anchor build, aby skompilować program startowy:
$anchor build
Skompilowany program jest zapisywany do target/deploy/my_program.so. Gdy
program zostanie wdrożony, zawartość tego pliku .so jest przechowywana na
koncie w łańcuchu.
Uruchom test
Uruchom domyślny test:
$anchor test
Anchor.toml tego szablonu używa polecenia testowego Rust:
skip_local_validator = true[scripts]test = "cargo test"
Test ładuje skompilowany program do LiteSVM, tworzy
płatnika, wysyła instrukcje initialize i increment, a następnie
sprawdza stan konta licznika.
Uruchomienie anchor test kompiluje również program, więc nie musisz uruchamiać
anchor build przed testowaniem lokalnym.
Wdróż program
Testy lokalne zapewniają najszybszą pętlę informacji zwrotnej. Gdy będziesz gotowy do wdrożenia w sieci, na przykład devnet, najpierw zbuduj projekt, a następnie wdróż go do klastra.
Wdrożenie programu Solana wymaga SOL, ponieważ program jest przechowywany na koncie, a konto musi opłacić zajmowaną przestrzeń. W sieci devnet możesz bezpłatnie pozyskać devnet SOL z Solana Faucet lub za pomocą Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
Pliki źródłowe
Katalog src zawiera program Solana. Dokumentacja Anchor dotycząca
struktury programu
omawiа podstawowe makra używane tutaj, w tym declare_id!,
#[program], #[derive(Accounts)] i #[account]. Ta sekcja
przechodzi przez pliki szablonu.
lib.rs
lib.rs jest punktem wejścia programu. Łączy pliki źródłowe, definiuje adres
programu oraz instrukcje programu, które użytkownicy mogą wywoływać.
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");
Ten sam adres programu pojawia się w konfiguracji i kodzie. Anchor.toml
informuje Anchor, który adres wdrożyć lub wywołać dla klastra. declare_id!
definiuje adres programu w programie na potrzeby kontroli bezpieczeństwa.
constants.rs
constants.rs przechowuje wspólne wartości w jednym miejscu. W tym szablonie
COUNTER_SEED wyprowadza PDA licznika, HELLO_WORLD_LAMPORTS jest
przekazywany podczas inicjalizacji, a MAX_COUNT jest sprawdzany przed
inkrementacją.
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 używa dwóch stałych:
COUNTER_SEEDwyznacza adres PDA licznika.HELLO_WORLD_LAMPORTSokreśla kwotę przenoszoną od płatnika na konto licznika.
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 używa MAX_COUNT jako górnej granicznej
wartości licznika. Jeśli bieżąca wartość osiągnęła już maksimum, require!
zwraca CounterOverflow i dane konta nie są zmieniane.
state.rs
state.rs definiuje niestandardowe typy danych dla kont tworzonych i
posiadanych przez program. Program definiuje instrukcje tworzenia, inicjowania i
aktualizowania tych danych, jednak dane licznika nie są przechowywane w samym
programie. Są przechowywane w oddzielnym koncie z własnym adresem.
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 definiuje dane konta Counter. Pliki instrukcji używają tego
typu podczas tworzenia i aktualizowania konta:
Counter::INIT_SPACEokreśla rozmiar konta dla pól zdefiniowanych wstate.rs.countiauthorityto wartości pól zapisywane podczas inicjalizacji konta.count += 1aktualizuje przechowywaną wartość licznika po pomyślnej weryfikacji.
error.rs
error.rs definiuje niestandardowe błędy programu. W tym szablonie błędy
przedstawiają, jak handlery instrukcji zatrzymują się, gdy wywołujący nie ma
uprawnień do aktualizacji licznika lub licznik osiągnął już 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 określa błędy, które instrukcja może zwrócić:
ErrorCode::Unauthorizedjest zwracany, gdy podpisujący nie jest authority przechowywanym na koncie licznika.ErrorCode::CounterOverflowjest zwracany, gdy licznik osiągnął jużMAX_COUNT.
instructions.rs
instructions.rs łączy pliki instrukcji z kratem programu, dzięki czemu
lib.rs może uzyskać dostęp do kodu instrukcji initialize i
increment. Każdy plik instrukcji definiuje konta wymagane przez daną
instrukcję oraz logikę obsługi wykonywaną po tym, jak Anchor zweryfikuje te
konta.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs definiuje konta wymagane do utworzenia konta licznika, a
następnie zapisuje jego pierwsze wartości. Struktura #[derive(Accounts)]
korzysta z
ograniczeń kont
Anchor, aby określić, które konta są wymagane i w jaki sposób tworzone jest nowe
konto licznika.
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(())}
Kontekst konta
Struktura Initialize definiuje konta, które muszą być uwzględnione, gdy
użytkownik wywołuje instrukcję initialize. Anchor weryfikuje te konta
przed uruchomieniem obsługi.
#[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>,}
Konto płatnika
Konto payer pokrywa koszty utworzenia konta licznika. Typ
Signer<'info> oznacza, że płatnik musi podpisać transakcję, a
#[account(mut)] oznacza, że konto płatnika może być modyfikowane, ponieważ
lamport zostaną pobrane.
#[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>,}
Konto licznika
Konto counter przechowuje dane Counter z state.rs. init
informuje Anchor, aby utworzył to konto przed uruchomieniem obsługi, a
payer = payer wskazuje Anchor, które konto pokrywa koszty utworzenia.
#[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>,}
Rozmiar konta
Ograniczenie space informuje Anchor, ile miejsca na dane konta należy
przydzielić. Anchor najpierw przechowuje 8-bajtowy dyskryminator, a następnie
bajty potrzebne dla pól Counter. Dyskryminator pozwala Anchor rozpoznać to
konto jako konto Counter przed deserializacją danych konta.
#[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>,}
Adres licznika
Ograniczenia seeds i bump definiują oczekiwany adres PDA dla konta
licznika. Anchor weryfikuje, czy podane konto counter pasuje do tego
adresu. Szablon używa PDA, aby użytkownicy mogli wyprowadzić adres licznika z
identyfikatora programu i seed, co sprawia, że adres licznika jest
deterministyczny.
#[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
Konto system_program jest wymagane, ponieważ tworzenie nowego konta
korzysta z 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>,}
Funkcja obsługi
Funkcja handle_initialize jest uruchamiana po tym, jak Anchor zweryfikuje
konta w Initialize. Wartość ctx zapewnia funkcji obsługi dostęp do
tych zweryfikowanych kont.
#[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(())}
Dane początkowe
Funkcja obsługi zapisuje pierwsze wartości do nowego konta licznika. Licznik
zaczyna od 0, a płatnik staje się uprawnionym do późniejszego
inkrementowania licznika.
#[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(())}
Konta transferu
Ten transfer CPI jest dołączony wyłącznie w celu zademonstrowania, jak CPI
przekazuje konta do innego programu. Struktura Transfer zawiera listę kont
używanych przez transfer 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(())}
Kontekst CPI
CpiContext::new łączy wywoływany program z kontami przekazywanymi do tego
programu. To jest podstawowa struktura CPI: wybierz program do wywołania, zbierz
konta, których ten program oczekuje, a następnie przekaż oba do wywołania. Tutaj
wywoływanym programem jest 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(())}
Wywołaj Transfer
anchor_lang::system_program::transfer wywołuje instrukcję transferu System
Program. W tym szablonie transfer jest prostym przykładem wywoływania innego
programu z twojego programu. Jeśli transfer CPI się nie powiedzie, instrukcja
initialize również zakończy się niepowodzeniem.
#[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(())}
Zapisz Wiadomość
msg! zapisuje wiadomość do logów programu. Ok(()) oznacza, że
instrukcja zakończyła się pomyślnie.
#[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 definiuje konta wymagane do aktualizacji istniejącego konta
licznika. Handler sprawdza, czy podpisujący jest zapisanym administratorem,
sprawdza, czy licznik nie osiągnął określonego MAX_COUNT, a następnie
inkrementuje licznik.
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(())}
Kontekst konta
Struktura Increment definiuje konta, które muszą być uwzględnione, gdy
użytkownik wywołuje instrukcję increment. Anchor weryfikuje te konta przed
uruchomieniem handlera.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Konto licznika
Konto counter przechowuje dane Counter. Ograniczenie mut
pozwala handlerowi na aktualizację zapisanego licznika, a ograniczenia
seeds i bump weryfikują adres PDA licznika.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Podpisujący z uprawnieniami
Konto authority musi podpisać transakcję. Handler następnie sprawdza, czy
ten podpisujący zgadza się z administratorem zapisanym na koncie licznika.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Funkcja obsługi
Funkcja handle_increment uruchamia się po tym, jak Anchor zweryfikuje
konta w Increment. Wartość ctx daje procedurze obsługi dostęp do
tych sprawdzonych kont.
#[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(())}
Weryfikacja uprawnień
Pierwsza weryfikacja sprawdza, czy sygnatariusz ma prawo zaktualizować ten
licznik. Jeśli adres sygnatariusza nie zgadza się z counter.authority,
instrukcja zatrzymuje się z błędem ErrorCode::Unauthorized. Pokazuje to
autoryzację na poziomie aplikacji: program jest właścicielem danych licznika,
ale implementuje regułę określającą, który sygnatariusz może te dane zmieniać.
#[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(())}
Weryfikacja maksymalnej wartości licznika
Druga weryfikacja zapobiega przekroczeniu przez licznik wartości
MAX_COUNT. Jeśli licznik osiągnął już limit, instrukcja zatrzymuje się z
błędem ErrorCode::CounterOverflow. Ten limit jest sztuczną regułą w
szablonie, dzięki której możesz zobaczyć, jak niestandardowe błędy zatrzymują
instrukcję przed zmianą danych konta.
#[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(())}
Aktualizacja licznika
Dopiero po przejściu obu weryfikacji procedura obsługi aktualizuje dane konta. Ta linia dodaje jeden do przechowywanej wartości licznika.
#[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(())}
Komunikat dziennika
msg! zapisuje zaktualizowaną wartość licznika do dzienników programu.
Ok(()) oznacza, że instrukcja zakończyła się pomyślnie.
#[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(())}
Plik testowy
programs/my-program/tests/test_initialize.rs to test integracyjny w języku
Rust. Nie uruchamia lokalnego validator. Zamiast tego ładuje skompilowany plik
.so do LiteSVM, buduje transakcje wywołujące program i odczytuje konto
licznika po każdej transakcji. Test buduje instrukcje dla transakcji Solana
poprzez podanie identyfikatora programu do wywołania, dostarczenie instruction
data oraz przekazanie wymaganych kont.
#[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),);
Kontekst konta Initialize definiuje konta wymagane przez instrukcję
initialize. Test przekazuje te same konta do wygenerowanego pomocnika
my_program::accounts::Initialize:
payerjest przekazywany jakopayer: payer.pubkey().counterjest przekazywany jakocounter.system_programjest przekazywany jakosystem_program::ID.
my_program::instruction::Initialize {}.data()
tworzy instruction data. W tym miejscu kodowane byłyby argumenty instrukcji,
jednak ta instrukcja initialize nie wymaga żadnych argumentów.
#[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),);
Kontekst konta Increment definiuje konta wymagane przez instrukcję
increment. Test przekazuje te same konta do wygenerowanego pomocnika
my_program::accounts::Increment:
counterjest przekazywany jakocounter.authorityjest przekazywany jakoauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
tworzy instruction data. W tym miejscu kodowane byłyby argumenty instrukcji,
jednak ta instrukcja increment nie wymaga żadnych argumentów.
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());}
Konfiguracja projektu
Pliki główne projektu informują Anchor i Cargo, jak budować, testować i wdrażać program. Pełną dokumentację znajdziesz w dokumentacji Anchor dla konfiguracji Anchor.toml oraz dla 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?