Crea il Tuo Primo Programma Solana

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:

Terminal
$
anchor --version

Crea il progetto

Esegui i seguenti comandi nel tuo terminale:

Terminal
$
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.

Anchor.toml
Cargo.toml
Cargo.toml
lib.rs
constants.rs
error.rs
instructions.rs
initialize.rs
increment.rs
state.rs
test_initialize.rs

Compila il Programma

Esegui anchor build per compilare il programma iniziale:

Terminal
$
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:

Terminal
$
anchor test

Il Anchor.toml di questo template utilizza il comando di test Rust:

Anchor.toml
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:

Terminal
$
solana airdrop 2 --url devnet
Terminal
$
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.

programs/my-program/src/lib.rs
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)
}
}
Anchor.toml
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
lib.rs
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.

programs/my-program/src/constants.rs
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;
initialize.rs
#[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_SEED ricava l'indirizzo PDA del contatore.
  • HELLO_WORLD_LAMPORTS imposta la quantità trasferita dal pagante all'account del contatore.
increment.rs
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.

programs/my-program/src/state.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}
initialize.rs
#[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(())
}
increment.rs
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_SPACE dimensiona l'account per i campi definiti in state.rs.
  • count e authority sono i valori dei campi scritti quando l'account viene inizializzato.
  • count += 1 aggiorna 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.

programs/my-program/src/error.rs
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,
}
increment.rs
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::Unauthorized viene restituito quando il firmatario non è l'autorità memorizzata nell'account del contatore.
  • ErrorCode::CounterOverflow viene restituito quando il contatore ha già raggiunto MAX_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.

programs/my-program/src/instructions.rs
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.

programs/my-program/src/instructions/initialize.rs
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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

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.

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.

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.

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.

System Program

L'account system_program è necessario perché la creazione di un nuovo account utilizza il System Program.

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.

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.

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.

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.

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.

Registrare un Messaggio

msg! scrive un messaggio nei log del programma. Ok(()) indica che l'istruzione è stata completata con successo.

initialize.rs
#[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>,
}

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.

programs/my-program/src/instructions/increment.rs
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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

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.

Authority Signer

L'account authority deve firmare la transazione. Il gestore verifica in seguito che questo firmatario corrisponda all'autorità memorizzata nell'account contatore.

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.

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.

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.

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.

Messaggio di Log

msg! scrive il contatore aggiornato nei log del programma. Ok(()) indica che l'istruzione è stata completata con successo.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

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.

initialize.rs
#[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>,
}
test_initialize.rs
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:

  • payer viene passato come payer: payer.pubkey().
  • counter viene passato come counter.
  • system_program viene passato come system_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.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
test_initialize.rs
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:

  • counter viene passato come counter.
  • authority viene passato come authority: 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.

programs/my-program/tests/test_initialize.rs
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.

Anchor.toml
skip_local_validator = true
[toolchain]
[features]
resolution = true
skip-lint = false
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "cargo test"
[hooks]

Is this page helpful?

Indice dei contenuti

Modifica pagina
© 2026 Solana Foundation. Tutti i diritti riservati.