Bouw Je Eerste Solana-programma

Deze quickstart gebruikt het Anchor framework startersproject gegenereerd door anchor init. Je maakt het project lokaal aan, voert de tests uit, bouwt het programma en doorloopt de programmacode die Anchor generiert.

Vereisten

Voordat je begint, installeer de Solana-ontwikkeltools. De installatie omvat Rust, de Solana CLI en de Anchor CLI.

Gebruik Anchor CLI versie 1.1.2 of hoger voor dit sjabloon. Controleer je geïnstalleerde versie:

Terminal
$
anchor --version

Maak het project aan

Voer de volgende opdrachten uit in je terminal:

Terminal
$
anchor init my-program
$
cd my-program

Het startersproject bevat één Solana-programma onder programs/my-program. Het programma bevat twee instructies: één om een telleraccount te initialiseren en één om de teller te verhogen.

Sommige onderdelen van het sjabloon demonstreren veelvoorkomende Solana-programmapatronen: het afleiden van PDA-accountadressen, het uitvoeren van een Cross Program Invocation (CPI) om SOL over te maken, en het gebruik van aangepaste foutcontroles om een instructie te stoppen wanneer een conditie mislukt.

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

Programma Bouwen

Voer anchor build uit om het startersprogramma te compileren:

Terminal
$
anchor build

Het gecompileerde programma wordt geschreven naar target/deploy/my_program.so. Wanneer het programma wordt uitgerold, wordt de inhoud van dit .so-bestand opgeslagen in een account onchain.

Test uitvoeren

Voer de standaardtest uit:

Terminal
$
anchor test

De Anchor.toml van dit sjabloon gebruikt de Rust-testopdracht:

Anchor.toml
skip_local_validator = true
[scripts]
test = "cargo test"

De test laadt het gecompileerde programma in LiteSVM, maakt een betaler aan, verstuurt de initialize en increment instructies, en controleert vervolgens de status van het telleraccount.

Het uitvoeren van anchor test compileert het programma ook, dus u hoeft anchor build niet eerst uit te voeren bij lokaal testen.

Programma implementeren

Lokale tests bieden de snelste feedbackcyclus. Wanneer u klaar bent om te implementeren op een netwerk, bijvoorbeeld devnet, bouw dan eerst en implementeer daarna op een cluster.

Voor het implementeren van een Solana-programma is SOL vereist, omdat het programma wordt opgeslagen in een account, en het account moet betalen voor de ruimte die het gebruikt. Op devnet kunt u gratis devnet SOL aanvragen via de Solana Faucet of met de Solana CLI:

Terminal
$
solana airdrop 2 --url devnet
Terminal
$
anchor build
$
anchor deploy --provider.cluster devnet

Bronbestanden

De map src bevat het Solana-programma. De Programmastructuur-documentatie van Anchor legt de kernmacro's uit die hier worden gebruikt, waaronder declare_id!, #[program], #[derive(Accounts)] en #[account]. Dit gedeelte beschrijft de sjabloonbestanden.

lib.rs

lib.rs is het toegangspunt van het programma. Het verbindt de bronbestanden, definieert het programmaadres en definieert de programma-instructies die gebruikers kunnen aanroepen.

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");

Hetzelfde programmaadres komt voor in de configuratie en de code. Anchor.toml vertelt Anchor welk adres moet worden ingezet of aangeroepen voor een cluster. declare_id! definieert het programmaadres in het programma voor beveiligingscontroles.

constants.rs

constants.rs bewaart gedeelde waarden op één plek. In dit sjabloon leidt COUNTER_SEED de teller-PDA af, wordt HELLO_WORLD_LAMPORTS overgedragen tijdens initialisatie, en wordt MAX_COUNT gecontroleerd vóór het ophogen.

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 gebruikt twee constanten:

  • COUNTER_SEED leidt het PDA-adres van de teller af.
  • HELLO_WORLD_LAMPORTS stelt het bedrag in dat van de betaler naar het telleraccount wordt overgemaakt.
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 gebruikt MAX_COUNT als de bovengrens van de teller. Als de huidige telling al op het maximum staat, geeft require! de waarde CounterOverflow terug en worden de accountgegevens niet gewijzigd.

state.rs

state.rs definieert aangepaste gegevenstypen voor accounts die het programma aanmaakt en beheert. Het programma definieert instructies om die gegevens aan te maken, te initialiseren en bij te werken, maar de tellergegevens worden niet in het programma zelf opgeslagen. Ze worden opgeslagen in een aparte account met een eigen adres.

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 definieert de Counter accountgegevens. De instructiebestanden gebruiken dat type wanneer ze het account aanmaken en bijwerken:

  • Counter::INIT_SPACE bepaalt de grootte van het account voor de velden gedefinieerd in state.rs.
  • count en authority zijn de veld- waarden die worden geschreven wanneer het account wordt geïnitialiseerd.
  • count += 1 werkt de opgeslagen tellerwaarde bij nadat de validatie is geslaagd.

error.rs

error.rs definieert de aangepaste fouten van het programma. In dit sjabloon laten de fouten zien hoe instructie-handlers stoppen wanneer een aanroeper niet bevoegd is om de teller bij te werken of de teller al MAX_COUNT heeft bereikt.

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 benoemt de fouten die de instructie kan retourneren:

  • ErrorCode::Unauthorized wordt geretourneerd wanneer de ondertekenaar niet de authority is die is opgeslagen in het telleraccount.
  • ErrorCode::CounterOverflow wordt geretourneerd wanneer de teller al MAX_COUNT heeft bereikt.

instructions.rs

instructions.rs verbindt de instructiebestanden met de programma-crate zodat lib.rs toegang heeft tot de initialize- en increment-instructiecode. Elk instructiebestand definieert de accounts die vereist zijn door die instructie en de handlerlogica die wordt uitgevoerd nadat Anchor die accounts heeft gevalideerd.

programs/my-program/src/instructions.rs
pub mod initialize;
pub mod increment;
pub use initialize::*;
pub use increment::*;

initialize.rs

initialize.rs definieert de accounts die nodig zijn om het tegenaccount aan te maken, en schrijft vervolgens de eerste waarden van het account. De #[derive(Accounts)]-struct gebruikt Anchor account constraints om aan te geven welke accounts vereist zijn en hoe het nieuwe tegenaccount wordt aangemaakt.

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(())
}

Account Context

De Initialize-struct definieert de accounts die moeten worden meegestuurd wanneer een gebruiker de initialize-instructie aanroept. Anchor controleert deze accounts voordat de handler wordt uitgevoerd.

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>,
}

Payer Account

Het payer-account betaalt voor het aanmaken van het tegenaccount. Het Signer<'info>-type betekent dat de betaler de transactie moet ondertekenen, en #[account(mut)] betekent dat het betaleraccount kan worden gewijzigd omdat er lamports zullen worden afgetrokken.

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>,
}

Counter Account

Het counter-account slaat de Counter-gegevens op uit state.rs. init vertelt Anchor dit account aan te maken vóór de handler wordt uitgevoerd, en payer = payer vertelt Anchor welk account de aanmaak betaalt.

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>,
}

Accountgrootte

De space constraint vertelt Anchor hoeveel accountdata er gealloceerd moet worden. Anchor slaat eerst een 8-byte discriminator op, gevolgd door de bytes die nodig zijn voor de Counter velden. De discriminator stelt Anchor in staat dit account te herkennen als een Counter account voordat de accountdata gedeserialiseerd wordt.

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>,
}

Tegeradres

De seeds en bump constraints definiëren het verwachte PDA-adres voor het tegeraccount. Anchor verifieert dat het opgegeven counter account overeenkomt met dat adres. De sjabloon gebruikt een PDA zodat gebruikers het tegeradres kunnen afleiden van de program-ID en seed, waardoor het tegeradres deterministisch is.

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

Het system_program account is vereist omdat het aanmaken van een nieuw account gebruik maakt van het 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>,
}

Handleerfunctie

De handle_initialize functie wordt uitgevoerd nadat Anchor de accounts in Initialize heeft gevalideerd. De ctx waarde geeft de handler toegang tot die gecontroleerde accounts.

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(())
}

Begindata

De handler schrijft de eerste waarden naar het nieuwe tegeraccount. De teller begint op 0, en de betaler wordt de autoriteit die gemachtigd is om de teller later te verhogen.

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(())
}

Overdrachtsaccounts

Deze overdrachts-CPI is alleen opgenomen om te demonstreren hoe een CPI accounts doorgeeft aan een ander programma. De Transfer struct vermeldt de accounts die worden gebruikt door de System Program overdracht.

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(())
}

CPI-context

CpiContext::new combineert het aangeroepen programma met de accounts die aan dat programma worden meegegeven. Dit is de basisvorm van een CPI: kies het programma om aan te roepen, verzamel de accounts die dat programma verwacht en geef beide door aan de aanroep. Hier is het aangeroepen programma het 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(())
}

Invoke Transfer

anchor_lang::system_program::transfer roept de System Program transfer-instructie aan. In dit sjabloon is de overdracht een klein voorbeeld van het aanroepen van een ander programma vanuit jouw programma. Als de transfer CPI mislukt, mislukt ook de initialize-instructie.

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(())
}

Log Message

msg! schrijft een bericht naar de programmalogboeken. Ok(()) geeft aan dat de instructie succesvol is teruggekeerd.

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 Context

De Initialize-struct definieert de accounts die moeten worden meegestuurd wanneer een gebruiker de initialize-instructie aanroept. Anchor controleert deze accounts voordat de handler wordt uitgevoerd.

Payer Account

Het payer-account betaalt voor het aanmaken van het tegenaccount. Het Signer<'info>-type betekent dat de betaler de transactie moet ondertekenen, en #[account(mut)] betekent dat het betaleraccount kan worden gewijzigd omdat er lamports zullen worden afgetrokken.

Counter Account

Het counter-account slaat de Counter-gegevens op uit state.rs. init vertelt Anchor dit account aan te maken vóór de handler wordt uitgevoerd, en payer = payer vertelt Anchor welk account de aanmaak betaalt.

Accountgrootte

De space constraint vertelt Anchor hoeveel accountdata er gealloceerd moet worden. Anchor slaat eerst een 8-byte discriminator op, gevolgd door de bytes die nodig zijn voor de Counter velden. De discriminator stelt Anchor in staat dit account te herkennen als een Counter account voordat de accountdata gedeserialiseerd wordt.

Tegeradres

De seeds en bump constraints definiëren het verwachte PDA-adres voor het tegeraccount. Anchor verifieert dat het opgegeven counter account overeenkomt met dat adres. De sjabloon gebruikt een PDA zodat gebruikers het tegeradres kunnen afleiden van de program-ID en seed, waardoor het tegeradres deterministisch is.

System Program

Het system_program account is vereist omdat het aanmaken van een nieuw account gebruik maakt van het System Program.

Handleerfunctie

De handle_initialize functie wordt uitgevoerd nadat Anchor de accounts in Initialize heeft gevalideerd. De ctx waarde geeft de handler toegang tot die gecontroleerde accounts.

Begindata

De handler schrijft de eerste waarden naar het nieuwe tegeraccount. De teller begint op 0, en de betaler wordt de autoriteit die gemachtigd is om de teller later te verhogen.

Overdrachtsaccounts

Deze overdrachts-CPI is alleen opgenomen om te demonstreren hoe een CPI accounts doorgeeft aan een ander programma. De Transfer struct vermeldt de accounts die worden gebruikt door de System Program overdracht.

CPI-context

CpiContext::new combineert het aangeroepen programma met de accounts die aan dat programma worden meegegeven. Dit is de basisvorm van een CPI: kies het programma om aan te roepen, verzamel de accounts die dat programma verwacht en geef beide door aan de aanroep. Hier is het aangeroepen programma het System Program.

Invoke Transfer

anchor_lang::system_program::transfer roept de System Program transfer-instructie aan. In dit sjabloon is de overdracht een klein voorbeeld van het aanroepen van een ander programma vanuit jouw programma. Als de transfer CPI mislukt, mislukt ook de initialize-instructie.

Log Message

msg! schrijft een bericht naar de programmalogboeken. Ok(()) geeft aan dat de instructie succesvol is teruggekeerd.

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 definieert de accounts die vereist zijn om een bestaand telleraccount bij te werken. De handler controleert of de ondertekenaar de opgeslagen autoriteit is, controleert of het aantal de opgegeven MAX_COUNT niet heeft bereikt, en verhoogt vervolgens de teller.

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(())
}

Accountcontext

De Increment struct definieert de accounts die moeten worden meegestuurd wanneer een gebruiker de increment instructie aanroept. Anchor controleert deze accounts voordat de handler wordt uitgevoerd.

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

Telleraccount

Het counter account slaat de Counter gegevens op. De mut constraint staat de handler toe het opgeslagen aantal bij te werken, en de seeds en bump constraints verifiëren het PDA-adres van de teller.

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

Autoriteitondertekenaar

Het authority account moet de transactie ondertekenen. De handler controleert later of deze ondertekenaar overeenkomt met de autoriteit die is opgeslagen in het telleraccount.

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

Handler Functie

De handle_increment functie wordt uitgevoerd nadat Anchor de accounts valideert in Increment. De ctx waarde geeft de handler toegang tot die gecontroleerde accounts.

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(())
}

Autoriteitscontrole

De eerste controle zorgt ervoor dat de ondertekenaar gemachtigd is om deze teller bij te werken. Als het adres van de ondertekenaar niet overeenkomt met counter.authority, stopt de instructie met ErrorCode::Unauthorized. Dit demonstreert autorisatie op applicatieniveau: het programma bezit de tellerdata, maar implementeert een regel voor welke ondertekenaar die data mag wijzigen.

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(())
}

Maximale Tellercontrole

De tweede controle voorkomt dat de teller voorbij MAX_COUNT gaat. Als de teller al op het maximum staat, stopt de instructie met ErrorCode::CounterOverflow. Dit maximum is een kunstmatige regel in de sjabloon zodat je kunt zien hoe aangepaste fouten een instructie stoppen voordat de accountdata wordt gewijzigd.

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(())
}

Teller Bijwerken

Pas nadat beide controles zijn geslaagd, werkt de handler de accountdata bij. Deze regel telt één op bij de opgeslagen tellerwaarde.

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(())
}

Logbericht

msg! schrijft de bijgewerkte telling naar de programmalogboeken. Ok(()) geeft aan dat de instructie succesvol is teruggekeerd.

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(())
}

Accountcontext

De Increment struct definieert de accounts die moeten worden meegestuurd wanneer een gebruiker de increment instructie aanroept. Anchor controleert deze accounts voordat de handler wordt uitgevoerd.

Telleraccount

Het counter account slaat de Counter gegevens op. De mut constraint staat de handler toe het opgeslagen aantal bij te werken, en de seeds en bump constraints verifiëren het PDA-adres van de teller.

Autoriteitondertekenaar

Het authority account moet de transactie ondertekenen. De handler controleert later of deze ondertekenaar overeenkomt met de autoriteit die is opgeslagen in het telleraccount.

Handler Functie

De handle_increment functie wordt uitgevoerd nadat Anchor de accounts valideert in Increment. De ctx waarde geeft de handler toegang tot die gecontroleerde accounts.

Autoriteitscontrole

De eerste controle zorgt ervoor dat de ondertekenaar gemachtigd is om deze teller bij te werken. Als het adres van de ondertekenaar niet overeenkomt met counter.authority, stopt de instructie met ErrorCode::Unauthorized. Dit demonstreert autorisatie op applicatieniveau: het programma bezit de tellerdata, maar implementeert een regel voor welke ondertekenaar die data mag wijzigen.

Maximale Tellercontrole

De tweede controle voorkomt dat de teller voorbij MAX_COUNT gaat. Als de teller al op het maximum staat, stopt de instructie met ErrorCode::CounterOverflow. Dit maximum is een kunstmatige regel in de sjabloon zodat je kunt zien hoe aangepaste fouten een instructie stoppen voordat de accountdata wordt gewijzigd.

Teller Bijwerken

Pas nadat beide controles zijn geslaagd, werkt de handler de accountdata bij. Deze regel telt één op bij de opgeslagen tellerwaarde.

Logbericht

msg! schrijft de bijgewerkte telling naar de programmalogboeken. Ok(()) geeft aan dat de instructie succesvol is teruggekeerd.

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

Testbestand

programs/my-program/tests/test_initialize.rs is een Rust-integratietest. Er wordt geen lokale validator gestart. In plaats daarvan laadt het het gecompileerde .so-bestand in LiteSVM, bouwt het transacties die het programma aanroepen en leest het het teller-account na elke transactie. De test bouwt instructies voor een Solana-transactie door het programma-ID van het aan te roepen programma op te geven, instruction data te verstrekken en de vereiste accounts door te geven.

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),
);

De Initialize accountcontext definieert de accounts die vereist zijn door de initialize instructie. De test geeft diezelfde accounts door aan de gegenereerde my_program::accounts::Initialize helper:

  • payer wordt doorgegeven als payer: payer.pubkey().
  • counter wordt doorgegeven als counter.
  • system_program wordt doorgegeven als system_program::ID.

my_program::instruction::Initialize {}.data() maakt de instruction data aan. Dit is waar instructieargumenten gecodeerd zouden worden, maar deze initialize instructie vereist geen argumenten.

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),
);

De Increment accountcontext definieert de accounts die vereist zijn door de increment instructie. De test geeft diezelfde accounts door aan de gegenereerde my_program::accounts::Increment helper:

  • counter wordt doorgegeven als counter.
  • authority wordt doorgegeven als authority: payer.pubkey().

my_program::instruction::Increment {}.data() maakt de instruction data aan. Dit is waar instructieargumenten gecodeerd zouden worden, maar deze increment instructie vereist geen argumenten.

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());
}

Projectconfiguratie

De hoofdprojectbestanden vertellen Anchor en Cargo hoe het programma gebouwd, getest en gedeployed moet worden. Voor een volledige referentie, zie de Anchor-documentatie voor Anchor.toml-configuratie en de 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?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.