Ce démarrage rapide utilise le projet de démarrage du
framework Anchor généré par anchor init.
Vous allez créer le projet localement, exécuter ses tests, compiler le programme
et parcourir le code du programme qu'Anchor génère.
Prérequis
Avant de commencer, installez les outils de développement Solana. L'installation inclut Rust, la CLI Solana et la CLI Anchor.
Utilisez la version 1.1.2 ou supérieure de la CLI Anchor pour ce modèle. Vérifiez votre version installée :
$anchor --version
Créer le projet
Exécutez les commandes suivantes dans votre terminal :
$anchor init my-program$cd my-program
Le projet de démarrage comprend un programme Solana sous programs/my-program.
Le programme comprend deux instructions : une pour initialiser un compte
compteur et une pour incrémenter le compteur.
Certaines parties du modèle illustrent des patterns courants des programmes Solana : la dérivation d'adresses de compte PDA, l'exécution d'un Cross Program Invocation (CPI) pour transférer des SOL, et l'utilisation de vérifications d'erreurs personnalisées pour arrêter une instruction lorsqu'une condition échoue.
Compiler le programme
Exécutez anchor build pour compiler le programme de démarrage :
$anchor build
Le programme compilé est écrit dans target/deploy/my_program.so. Lorsque le
programme est déployé, le contenu de ce fichier .so est stocké dans un compte
on-chain.
Exécuter le test
Exécutez le test par défaut :
$anchor test
Le Anchor.toml de ce modèle utilise la commande de test Rust :
skip_local_validator = true[scripts]test = "cargo test"
Le test charge le programme compilé dans LiteSVM,
créé un payeur, envoie les instructions initialize et increment,
puis vérifie l'état du compte compteur.
L'exécution de anchor test compile également le programme, vous n'avez donc
pas besoin d'exécuter anchor build en premier lors des tests en local.
Déployer le programme
Les tests locaux offrent la boucle de rétroaction la plus rapide. Lorsque vous êtes prêt à déployer sur un réseau, par exemple devnet, compilez d'abord, puis déployez sur un cluster.
Le déploiement d'un programme Solana nécessite des SOL car le programme est stocké dans un program account, et celui-ci doit payer pour l'espace qu'il utilise. Sur devnet, demandez des SOL devnet gratuits depuis le Solana Faucet ou via le Solana CLI :
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
Fichiers sources
Le répertoire src contient le programme Solana. La documentation d'Anchor sur
la
Structure du programme
explique les macros principales utilisées ici, notamment declare_id!,
#[program], #[derive(Accounts)] et #[account]. Cette section
parcourt les fichiers du modèle.
lib.rs
lib.rs est le point d'entrée du programme. Il relie les fichiers sources,
définit l'adresse du programme et définit les instructions du programme que les
utilisateurs peuvent appeler.
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");
La même adresse de programme apparaît dans la configuration et dans le code.
Anchor.toml indique à Anchor l'adresse à déployer ou à appeler pour un
cluster. declare_id! définit l'adresse du programme dans le programme pour
les vérifications de sécurité.
constants.rs
constants.rs regroupe les valeurs partagées en un seul endroit. Dans ce
modèle, COUNTER_SEED dérive le PDA du compteur, HELLO_WORLD_LAMPORTS
est transféré lors de l'initialisation, et MAX_COUNT est vérifié avant
l'incrémentation.
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 utilise deux constantes :
COUNTER_SEEDdérive l'adresse PDA du compteur.HELLO_WORLD_LAMPORTSdéfinit le montant transféré du payeur vers le compte compteur.
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 utilise MAX_COUNT comme limite
supérieure du compteur. Si le compteur actuel a déjà atteint la valeur maximale,
require! retourne CounterOverflow et les données du compte ne sont
pas modifiées.
state.rs
state.rs définit des types de données personnalisés pour les comptes que le
programme crée et possède. Le programme définit des instructions pour créer,
initialiser et mettre à jour ces données, mais les données du compteur ne sont
pas stockées dans le programme lui-même. Elles sont stockées dans un compte
séparé possédant sa propre adresse.
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 définit les données du compte Counter. Les fichiers
d'instruction utilisent ce type lors de la création et de la mise à jour du
compte :
Counter::INIT_SPACEdimensionne le compte pour les champs définis dansstate.rs.countetauthoritysont les valeurs de champs écrites lors de l'initialisation du compte.count += 1met à jour la valeur du compteur stockée une fois la validation réussie.
error.rs
error.rs définit les erreurs personnalisées du programme. Dans ce modèle, les
erreurs montrent comment les gestionnaires d'instructions s'arrêtent lorsqu'un
appelant n'est pas autorisé à mettre à jour le compteur ou que celui-ci a déjà
atteint 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 nomme les erreurs que l'instruction peut retourner :
ErrorCode::Unauthorizedest retourné lorsque le signataire n'est pas l'autorité stockée dans le compte du compteur.ErrorCode::CounterOverflowest retourné lorsque le compteur a déjà atteintMAX_COUNT.
instructions.rs
instructions.rs connecte les fichiers d'instructions au crate du programme
afin que lib.rs puisse accéder au code des instructions initialize et
increment. Chaque fichier d'instructions définit les comptes requis par
cette instruction ainsi que la logique du gestionnaire qui s'exécute après
qu'Anchor a validé ces comptes.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs définit les comptes nécessaires à la création du compte
compteur, puis écrit les premières valeurs du compte. La struct
#[derive(Accounts)] utilise les
contraintes de compte
Anchor pour spécifier quels comptes sont requis et comment le nouveau compte
compteur est créé.
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
La struct Initialize définit les comptes qui doivent être inclus lorsqu'un
utilisateur appelle l'instruction initialize. Anchor vérifie ces comptes
avant l'exécution du gestionnaire.
#[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
Le compte payer paie la création du compte compteur. Le type
Signer<'info> signifie que le payeur doit signer la transaction, et
#[account(mut)] indique que le compte payeur peut être modifié car des
lamports seront déduits.
#[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
Le compte counter stocke les données Counter provenant de
state.rs. init indique à Anchor de créer ce compte avant l'exécution du
gestionnaire, et payer = payer indique à Anchor quel compte prend en
charge les frais de création.
#[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>,}
Taille du compte
La contrainte space indique à Anchor la quantité de données à allouer pour
le compte. Anchor stocke d'abord un discriminant de 8 octets, puis les octets
nécessaires pour les champs Counter. Le discriminant permet à Anchor de
reconnaître ce compte comme un compte Counter avant de désérialiser les
données du compte.
#[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>,}
Adresse du compteur
Les contraintes seeds et bump définissent l'adresse PDA attendue
pour le compte compteur. Anchor vérifie que le compte counter fourni
correspond à cette adresse. Le modèle utilise un PDA afin que les utilisateurs
puissent dériver l'adresse du compteur à partir de l'ID du programme et du seed,
rendant ainsi l'adresse du compteur déterministe.
#[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
Le compte system_program est requis car la création d'un nouveau compte
utilise le 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>,}
Fonction de gestionnaire
La fonction handle_initialize s'exécute après qu'Anchor a validé les
comptes dans Initialize. La valeur ctx donne au gestionnaire accès à
ces comptes vérifiés.
#[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(())}
Données initiales
Le gestionnaire écrit les premières valeurs dans le nouveau compte compteur. Le
comptage commence à 0, et le payeur devient l'autorité autorisée à
incrémenter le compteur ultérieurement.
#[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(())}
Comptes de transfert
Ce CPI de transfert est inclus uniquement pour illustrer comment un CPI transmet
des comptes à un autre programme. La structure Transfer liste les comptes
utilisés par le transfert du 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(())}
Contexte CPI
CpiContext::new combine le programme appelé avec les comptes transmis à ce
programme. C'est la structure de base d'un CPI : choisir le programme à
invoquer, collectionner les comptes attendus par ce programme, puis passer les
deux dans l'invocation. Ici, le programme appelé est le 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(())}
Invoke Transfer
anchor_lang::system_program::transfer invoque l'instruction de transfert
du System Program. Dans ce modèle, le transfert est un exemple simple d'appel
d'un autre programme depuis votre programme. Si le CPI de transfert échoue,
l'instruction initialize échoue également.
#[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! écrit un message dans les journaux du programme. Ok(()) indique
que l'instruction s'est terminée avec succès.
#[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 définit les comptes requis pour mettre à jour un compte compteur
existant. Le handler vérifie que le signataire est l'autorité enregistrée,
vérifie que le compteur n'a pas atteint la valeur MAX_COUNT spécifiée,
puis incrémente le compteur.
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 structure Increment définit les comptes qui doivent être inclus
lorsqu'un utilisateur appelle l'instruction increment. Anchor vérifie ces
comptes avant l'exécution du handler.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Counter Account
Le compte counter stocke les données Counter. La contrainte
mut permet au handler de mettre à jour le compteur enregistré, et les
contraintes seeds et bump vérifient l'adresse PDA du compteur.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Authority Signer
Le compte authority doit signer la transaction. Le handler vérifie ensuite
que ce signataire correspond à l'autorité enregistrée dans le compte compteur.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Fonction du gestionnaire
La fonction handle_increment s'exécute après qu'Anchor valide les comptes
dans Increment. La valeur ctx donne au gestionnaire accès à ces
comptes vérifiés.
#[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(())}
Vérification de l'autorité
La première vérification s'assure que le signataire est autorisé à mettre à jour
ce compteur. Si l'adresse du signataire ne correspond pas à
counter.authority, l'instruction s'arrête avec
ErrorCode::Unauthorized. Cela illustre une autorisation au niveau de
l'application : le programme détient les données du compteur, mais il implémente
une règle définissant quel signataire peut modifier ces données.
#[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(())}
Vérification du compteur maximum
La deuxième vérification empêche le compteur de dépasser MAX_COUNT. Si le
compteur a déjà atteint la limite, l'instruction s'arrête avec
ErrorCode::CounterOverflow. Cette limite est une règle artificielle dans
le modèle afin que vous puissiez voir comment les erreurs personnalisées
arrêtent une instruction avant que les données du compte ne soient modifiées.
#[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(())}
Mise à jour du compteur
Ce n'est qu'une fois les deux vérifications passées que le gestionnaire met à jour les données du compte. Cette ligne ajoute un à la valeur du compteur stockée.
#[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(())}
Message de journal
msg! écrit le compteur mis à jour dans les journaux du programme.
Ok(()) indique que l'instruction s'est terminée avec succès.
#[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(())}
Fichier de test
programs/my-program/tests/test_initialize.rs est un test d'intégration Rust.
Il ne démarre pas de validator local. À la place, il charge le fichier .so
compilé dans LiteSVM, construit des transactions qui appellent le programme et
lit le compte compteur après chaque transaction. Le test construit des
instructions pour une transaction Solana en spécifiant l'ID du programme à
invoquer, en fournissant des instruction data et en passant les comptes requis.
#[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),);
Le contexte de compte Initialize définit les comptes requis par
l'instruction initialize. Le test passe ces mêmes comptes au helper
my_program::accounts::Initialize généré :
payerest passé en tant quepayer: payer.pubkey().counterest passé en tant quecounter.system_programest passé en tant quesystem_program::ID.
my_program::instruction::Initialize {}.data()
crée l'instruction data. C'est ici que les arguments d'instruction seraient
encodés, mais cette instruction initialize ne nécessite aucun argument.
#[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),);
Le contexte de compte Increment définit les comptes requis par
l'instruction increment. Le test passe ces mêmes comptes au helper
my_program::accounts::Increment généré :
counterest passé en tant quecounter.authorityest passé en tant queauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
crée l'instruction data. C'est ici que les arguments d'instruction seraient
encodés, mais cette instruction increment ne nécessite aucun argument.
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());}
Configuration du projet
Les fichiers racine du projet indiquent à Anchor et à Cargo comment compiler, tester et déployer le programme. Pour une référence complète, consultez la documentation Anchor pour la configuration Anchor.toml et 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?