Questo avvio rapido utilizza il progetto iniziale del
framework Anchor generato da anchor init.
Creerai il progetto in locale, eseguirai i suoi test, compilerai il programma ed
esaminerai il codice che Anchor genera.
Prerequisiti
Prima di iniziare, installa gli strumenti di sviluppo Solana. L'installazione include Rust, la Solana CLI e la Anchor CLI.
Usa la versione 1.1.2 o superiore della Anchor CLI per questo template. Verifica la versione installata:
$anchor --version
Crea il progetto
Esegui i seguenti comandi nel tuo terminale:
$anchor init my-program$cd my-program
Il progetto iniziale include un programma Solana sotto programs/my-program. Il
programma include due istruzioni: una per inizializzare un account contatore e
una per incrementare il contatore.
Alcune parti del template illustrano pattern comuni dei programmi Solana: derivare indirizzi di account PDA, eseguire una Cross Program Invocation (CPI) per trasferire SOL e utilizzare controlli di errore personalizzati per bloccare un'istruzione quando una condizione non viene soddisfatta.
Compila il Programma
Esegui anchor build per compilare il programma iniziale:
$anchor build
Il programma compilato viene scritto in target/deploy/my_program.so. Quando il
programma viene distribuito, il contenuto di questo file .so viene memorizzato
in un account onchain.
Esegui il test
Esegui il test predefinito:
$anchor test
Il Anchor.toml di questo template utilizza il comando di test Rust:
skip_local_validator = true[scripts]test = "cargo test"
Il test carica il programma compilato in LiteSVM,
crea un pagante, invia le istruzioni initialize e increment, quindi
verifica lo stato dell'account del contatore.
L'esecuzione di anchor test compila anche il programma, quindi non è
necessario eseguire anchor build prima durante i test in locale.
Distribuisci il programma
I test in locale rappresentano il ciclo di feedback più rapido. Quando sei pronto per distribuire su una rete, ad esempio devnet, prima compila, poi distribuisci su un cluster.
La distribuzione di un programma Solana richiede SOL perché il programma è memorizzato in un account, e l'account deve pagare per lo spazio che utilizza. Su devnet, richiedi SOL devnet gratuito dal Solana Faucet oppure tramite la Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
File sorgente
La directory src contiene il programma Solana. La documentazione di Anchor
sulla
Struttura del programma
spiega le macro principali utilizzate qui, tra cui declare_id!,
#[program], #[derive(Accounts)] e #[account]. Questa sezione
illustri i file del template.
lib.rs
lib.rs è il punto di ingresso del programma. Collega i file sorgente,
definisce l'indirizzo del programma e definisce le istruzioni del programma che
gli utenti possono richiamare.
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");
Lo stesso indirizzo del programma appare nella configurazione e nel codice.
Anchor.toml indica ad Anchor quale indirizzo distribuire o chiamare per un
cluster. declare_id! definisce l'indirizzo del programma nel codice per i
controlli di sicurezza.
constants.rs
constants.rs mantiene i valori condivisi in un unico posto. In questo
template, COUNTER_SEED deriva il PDA del contatore,
HELLO_WORLD_LAMPORTS viene trasferito durante l'inizializzazione, e
MAX_COUNT viene verificato prima dell'incremento.
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 usa due costanti:
COUNTER_SEEDricava l'indirizzo PDA del contatore.HELLO_WORLD_LAMPORTSimposta la quantità trasferita dal pagante all'account del contatore.
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 usa MAX_COUNT come limite superiore
del contatore. Se il conteggio attuale ha già raggiunto il massimo,
require! restituisce CounterOverflow e i dati dell'account non
vengono modificati.
state.rs
state.rs definisce tipi di dati personalizzati per gli account che il
programma crea e gestisce. Il programma definisce istruzioni per creare,
inizializzare e aggiornare tali dati, ma i dati del contatore non sono
memorizzati nel programma stesso. Sono memorizzati in un account separato con il
proprio indirizzo.
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 definisce i dati dell'account Counter. I file delle istruzioni
utilizzano quel tipo quando creano e aggiornano l'account:
Counter::INIT_SPACEdimensiona l'account per i campi definiti instate.rs.counteauthoritysono i valori dei campi scritti quando l'account viene inizializzato.count += 1aggiorna il valore del contatore memorizzato dopo che la validazione è superata.
error.rs
error.rs definisce gli errori personalizzati del programma. In questo
template, gli errori dimostrano come i gestori delle istruzioni si interrompono
quando a un chiamante non è consentito aggiornare il contatore o il contatore ha
già raggiunto 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 elenca gli errori che l'istruzione può restituire:
ErrorCode::Unauthorizedviene restituito quando il firmatario non è l'autorità memorizzata nell'account del contatore.ErrorCode::CounterOverflowviene restituito quando il contatore ha già raggiuntoMAX_COUNT.
instructions.rs
instructions.rs collega i file delle istruzioni al crate del programma, così
lib.rs può accedere al codice delle istruzioni initialize e
increment. Ciascun file di istruzione definisce gli account richiesti da
quell'istruzione e la logica del gestore che viene eseguita dopo che Anchor ha
validato quegli account.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs definisce gli account necessari per creare l'account del
contatore, quindi scrive i valori iniziali dell'account. La struct
#[derive(Accounts)] utilizza i
vincoli degli account
di Anchor per specificare quali account sono richiesti e come viene creato il
nuovo account del contatore.
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(())}
Contesto dell'Account
La struct Initialize definisce gli account che devono essere inclusi
quando un utente richiama l'istruzione initialize. Anchor verifica questi
account prima che il gestore venga eseguito.
#[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>,}
Account del Pagante
L'account payer paga per creare l'account del contatore. Il tipo
Signer<'info> indica che il pagante deve firmare la transazione, e
#[account(mut)] indica che l'account del pagante può essere modificato
perché i lamport verranno sottratti.
#[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>,}
Account del Contatore
L'account counter memorizza i dati Counter da state.rs. init
indica ad Anchor di creare questo account prima che il gestore venga eseguito, e
payer = payer indica ad Anchor quale account paga per la creazione.
#[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>,}
Dimensione dell'Account
Il vincolo space indica ad Anchor quanti dati allocare per l'account.
Anchor memorizza prima un discriminatore a 8 byte, poi i byte necessari per i
campi Counter. Il discriminatore consente ad Anchor di riconoscere questo
account come un account Counter prima di deserializzarne i dati.
#[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>,}
Indirizzo del Counter
I vincoli seeds e bump definiscono l'indirizzo PDA atteso per
l'account counter. Anchor verifica che l'account counter fornito
corrisponda a quell'indirizzo. Il template utilizza un PDA in modo che gli
utenti possano ricavare l'indirizzo del counter dall'ID del programma e dal
seed, rendendo l'indirizzo del counter deterministico.
#[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
L'account system_program è necessario perché la creazione di un nuovo
account utilizza il 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>,}
Funzione Handler
La funzione handle_initialize viene eseguita dopo che Anchor ha validato
gli account in Initialize. Il valore ctx fornisce all'handler
l'accesso a quegli account verificati.
#[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(())}
Dati Iniziali
L'handler scrive i primi valori nel nuovo account counter. Il conteggio parte da
0, e il pagante diventa l'autorità abilitata ad incrementare il counter in
seguito.
#[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 di Trasferimento
Questo CPI di trasferimento è incluso solo per dimostrare come un CPI passa gli
account a un altro programma. La struct Transfer elenca gli account
utilizzati dal trasferimento del 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(())}
Contesto CPI
CpiContext::new combina il programma da chiamare con gli account passati a
quel programma. Questa è la struttura di base di una CPI: scegli il programma da
invocare, raccogli gli account che quel programma si aspetta, poi passa entrambi
all'invocazione. Qui, il programma chiamato è il 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(())}
Invocare il Trasferimento
anchor_lang::system_program::transfer invoca l'istruzione di trasferimento
del System Program. In questo template, il trasferimento è un piccolo esempio di
come chiamare un altro programma dal proprio programma. Se la CPI di
trasferimento fallisce, anche l'istruzione initialize fallisce.
#[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(())}
Registrare un Messaggio
msg! scrive un messaggio nei log del programma. Ok(()) indica che
l'istruzione è stata completata con successo.
#[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 definisce gli account necessari per aggiornare un account
contatore esistente. Il gestore verifica che il firmatario sia l'autorità
memorizzata, controlla che il conteggio non abbia raggiunto il MAX_COUNT
specificato, e poi incrementa il conteggio.
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(())}
Account Context
La struct Increment definisce gli account che devono essere inclusi quando
un utente chiama l'istruzione increment. Anchor verifica questi account
prima che il gestore venga eseguito.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Counter Account
L'account counter memorizza i dati Counter. Il vincolo mut
consente al gestore di aggiornare il conteggio memorizzato, mentre i vincoli
seeds e bump verificano l'indirizzo PDA del contatore.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Authority Signer
L'account authority deve firmare la transazione. Il gestore verifica in
seguito che questo firmatario corrisponda all'autorità memorizzata nell'account
contatore.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Funzione Handler
La funzione handle_increment viene eseguita dopo che Anchor valida gli
account in Increment. Il valore ctx fornisce all'handler l'accesso a
quegli account verificati.
#[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(())}
Verifica dell'Autorità
Il primo controllo verifica che il firmatario sia autorizzato ad aggiornare
questo contatore. Se l'indirizzo del firmatario non corrisponde a
counter.authority, l'istruzione si interrompe con
ErrorCode::Unauthorized. Questo dimostra l'autorizzazione a livello
applicativo: il programma possiede i dati del contatore, ma implementa una
regola che determina quale firmatario può modificare tali dati.
#[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(())}
Verifica del Contatore Massimo
Il secondo controllo impedisce al contatore di superare MAX_COUNT. Se il
contatore ha già raggiunto il limite, l'istruzione si interrompe con
ErrorCode::CounterOverflow. Questo limite è una regola artificiale nel
template che consente di vedere come gli errori personalizzati interrompano
un'istruzione prima che i dati dell'account vengano modificati.
#[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(())}
Aggiornamento del Contatore
Solo dopo che entrambi i controlli sono superati l'handler aggiorna i dati dell'account. Questa riga aggiunge uno al valore del contatore memorizzato.
#[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(())}
Messaggio di Log
msg! scrive il contatore aggiornato nei log del programma. Ok(())
indica che l'istruzione è stata completata con successo.
#[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(())}
File di test
programs/my-program/tests/test_initialize.rs è un test di integrazione Rust.
Non avvia un validator locale. Carica invece il file .so compilato in LiteSVM,
costruisce transazioni che chiamano il programma e legge l'account del contatore
dopo ogni transazione. Il test costruisce le istruzioni per una transazione
Solana specificando l'ID del programma da invocare, fornendo instruction data e
passando gli account richiesti.
#[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),);
Il contesto dell'account Initialize definisce gli account richiesti
dall'istruzione initialize. Il test passa quegli stessi account all'helper
my_program::accounts::Initialize generato:
payerviene passato comepayer: payer.pubkey().counterviene passato comecounter.system_programviene passato comesystem_program::ID.
my_program::instruction::Initialize {}.data()
crea l'instruction data. È qui che verrebbero codificati gli argomenti
dell'istruzione, ma questa istruzione initialize non richiede alcun
argomento.
#[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),);
Il contesto dell'account Increment definisce gli account richiesti
dall'istruzione increment. Il test passa quegli stessi account all'helper
my_program::accounts::Increment generato:
counterviene passato comecounter.authorityviene passato comeauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
crea l'instruction data. È qui che verrebbero codificati gli argomenti
dell'istruzione, ma questa istruzione increment non richiede alcun
argomento.
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());}
Configurazione del progetto
I file di root del progetto indicano ad Anchor e Cargo come compilare, testare e distribuire il programma. Per un riferimento completo, consulta la documentazione di Anchor per la configurazione di Anchor.toml e l'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?