Δημιουργήστε το Πρώτο σας Πρόγραμμα Solana

Αυτή η γρήγορη εκκίνηση χρησιμοποιεί το αρχικό έργο του πλαισίου Anchor που δημιουργείται από το anchor init. Θα δημιουργήσετε το έργο τοπικά, θα εκτελέσετε τις δοκιμές του, θα κατασκευάσετε το πρόγραμμα και θα εξερευνήσετε τον κώδικα που δημιουργεί το Anchor.

Προαπαιτούμενα

Πριν ξεκινήσετε, εγκαταστήστε τα εργαλεία ανάπτυξης Solana. Η εγκατάσταση περιλαμβάνει Rust, το Solana CLI και το Anchor CLI.

Χρησιμοποιήστε την έκδοση 1.1.2 ή νεότερη του Anchor CLI για αυτό το πρότυπο. Ελέγξτε την εγκατεστημένη έκδοσή σας:

Terminal
$
anchor --version

Δημιουργία του έργου

Εκτελέστε τις παρακάτω εντολές στο τερματικό σας:

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

Το αρχικό έργο περιλαμβάνει ένα πρόγραμμα Solana στο programs/my-program. Το πρόγραμμα περιλαμβάνει δύο εντολές: μία για την αρχικοποίηση ενός λογαριασμού μετρητή και μία για την αύξηση του μετρητή.

Ορισμένα μέρη του προτύπου επιδεικνύουν συνήθη μοτίβα προγραμμάτων Solana: παραγωγή διευθύνσεων λογαριασμών PDA, εκτέλεση Cross Program Invocation (CPI) για μεταφορά SOL, και χρήση προσαρμοσμένων ελέγχων σφαλμάτων για τη διακοπή μιας εντολής όταν μια συνθήκη αποτυγχάνει.

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

Κατασκευή Προγράμματος

Εκτελέστε anchor build για να μεταγλωττίσετε το αρχικό πρόγραμμα:

Terminal
$
anchor build

Το μεταγλωττισμένο πρόγραμμα αποθηκεύεται στο target/deploy/my_program.so. Κατά την ανάπτυξη του προγράμματος, τα περιεχόμενα αυτού του αρχείου .so αποθηκεύονται σε έναν λογαριασμό onchain.

Εκτέλεση Δοκιμής

Εκτελέστε την προεπιλεγμένη δοκιμή:

Terminal
$
anchor test

Το Anchor.toml αυτού του προτύπου χρησιμοποιεί την εντολή δοκιμής Rust:

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

Η δοκιμή φορτώνει το μεταγλωττισμένο πρόγραμμα στο LiteSVM, δημιουργεί έναν πληρωτή, αποστέλλει τις εντολές initialize και increment, και στη συνέχεια ελέγχει την κατάσταση του program account.

Η εκτέλεση του anchor test μεταγλωττίζει επίσης το πρόγραμμα, επομένως δεν χρειάζεται να εκτελέσετε το anchor build πρώτα κατά την τοπική δοκιμή.

Ανάπτυξη Προγράμματος

Οι τοπικές δοκιμές είναι ο ταχύτερος κύκλος ανατροφοδότησης. Όταν είστε έτοιμοι να αναπτύξετε σε ένα δίκτυο, για παράδειγμα στο devnet, κάντε πρώτα build και στη συνέχεια αναπτύξτε σε ένα cluster.

Η ανάπτυξη ενός προγράμματος Solana απαιτεί SOL επειδή το πρόγραμμα αποθηκεύεται σε ένα λογαριασμό, και ο λογαριασμός πρέπει να πληρώσει για τον χώρο που χρησιμοποιεί. Στο devnet, ζητήστε δωρεάν devnet SOL από το Solana Faucet ή με το Solana CLI:

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

Αρχεία πηγής

Ο κατάλογος src περιέχει το πρόγραμμα Solana. Τα έγγραφα Program Structure του Anchor εξηγούν τις βασικές μακροεντολές που χρησιμοποιούνται εδώ, συμπεριλαμβανομένων των declare_id!, #[program], #[derive(Accounts)] και #[account]. Αυτή η ενότητα αναλύει τα αρχεία προτύπου.

lib.rs

Το lib.rs είναι το σημείο εισόδου του προγράμματος. Συνδέει τα αρχεία πηγής, ορίζει τη διεύθυνση του προγράμματος και ορίζει τις εντολές του προγράμματος που μπορούν να καλέσουν οι χρήστες.

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

Η ίδια διεύθυνση προγράμματος εμφανίζεται στη διαμόρφωση και στον κώδικα. Το Anchor.toml λέει στο Anchor ποια διεύθυνση να αναπτύξει ή να καλέσει για ένα cluster. Το declare_id! ορίζει τη διεύθυνση του προγράμματος μέσα στο πρόγραμμα για ελέγχους ασφαλείας.

constants.rs

Το constants.rs διατηρεί τις κοινές τιμές σε ένα μέρος. Σε αυτό το πρότυπο, το COUNTER_SEED εξάγει το PDA του μετρητή, το HELLO_WORLD_LAMPORTS μεταφέρεται κατά την αρχικοποίηση, και το MAX_COUNT ελέγχεται πριν την αύξηση.

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 χρησιμοποιεί δύο σταθερές:

  • COUNTER_SEED παράγει τη διεύθυνση PDA του μετρητή.
  • HELLO_WORLD_LAMPORTS ορίζει το ποσό που μεταφέρεται από τον πληρωτή στον λογαριασμό μετρητή.
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 χρησιμοποιεί το MAX_COUNT ως το ανώτατο όριο του μετρητή. Εάν η τρέχουσα τιμή είναι ήδη στο μέγιστο, το require! επιστρέφει CounterOverflow και τα δεδομένα του λογαριασμού δεν αλλάζουν.

state.rs

Το state.rs ορίζει προσαρμοσμένους τύπους δεδομένων για τους λογαριασμούς που δημιουργεί και διαχειρίζεται το πρόγραμμα. Το πρόγραμμα ορίζει οδηγίες για τη δημιουργία, αρχικοποίηση και ενημέρωση αυτών των δεδομένων, αλλά τα δεδομένα του μετρητή δεν αποθηκεύονται μέσα στο ίδιο το πρόγραμμα. Αποθηκεύονται σε έναν ξεχωριστό λογαριασμό με τη δική του διεύθυνση.

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 ορίζει τα δεδομένα λογαριασμού Counter. Τα αρχεία εντολών χρησιμοποιούν αυτόν τον τύπο όταν δημιουργούν και ενημερώνουν τον λογαριασμό:

  • Counter::INIT_SPACE καθορίζει το μέγεθος του λογαριασμού για τα πεδία που ορίζονται στο state.rs.
  • count και authority είναι οι τιμές των πεδίων που εγγράφονται κατά την αρχικοποίηση του λογαριασμού.
  • count += 1 ενημερώνει την αποθηκευμένη τιμή μετρητή μετά την επιτυχή επικύρωση.

error.rs

error.rs ορίζει τα προσαρμοσμένα σφάλματα του προγράμματος. Σε αυτό το πρότυπο, τα σφάλματα δείχνουν πώς οι χειριστές εντολών σταματούν όταν ένας καλών δεν επιτρέπεται να ενημερώσει τον μετρητή ή ο μετρητής έχει ήδη φτάσει στο 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 ονομάζει τα σφάλματα που μπορεί να επιστρέψει η εντολή:

  • ErrorCode::Unauthorized επιστρέφεται όταν ο υπογράφων δεν είναι η αρχή που είναι αποθηκευμένη στον λογαριασμό μετρητή.
  • ErrorCode::CounterOverflow επιστρέφεται όταν ο μετρητής έχει ήδη φτάσει στο MAX_COUNT.

instructions.rs

instructions.rs συνδέει τα αρχεία εντολών με το crate του προγράμματος, ώστε lib.rs να μπορεί να έχει πρόσβαση στον κώδικα εντολών initialize και increment. Κάθε αρχείο εντολής ορίζει τους λογαριασμούς που απαιτεί η εντολή αυτή και τη λογική του χειριστή που εκτελείται αφού το Anchor επικυρώσει αυτούς τους λογαριασμούς.

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

initialize.rs

initialize.rs ορίζει τους λογαριασμούς που απαιτούνται για τη δημιουργία του λογαριασμού μετρητή και στη συνέχεια γράφει τις αρχικές τιμές του λογαριασμού. Η δομή #[derive(Accounts)] χρησιμοποιεί τους περιορισμούς λογαριασμού του Anchor για να δηλώσει ποιοι λογαριασμοί απαιτούνται και πώς δημιουργείται ο νέος λογαριασμός μετρητή.

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

Η δομή Initialize ορίζει τους λογαριασμούς που πρέπει να συμπεριληφθούν όταν ένας χρήστης καλεί την εντολή initialize. Το Anchor ελέγχει αυτούς τους λογαριασμούς πριν εκτελεστεί ο χειριστής.

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

Ο λογαριασμός payer πληρώνει για τη δημιουργία του λογαριασμού μετρητή. Ο τύπος Signer<'info> σημαίνει ότι ο πληρωτής πρέπει να υπογράψει τη συναλλαγή, και το #[account(mut)] σημαίνει ότι ο λογαριασμός του πληρωτή μπορεί να τροποποιηθεί επειδή θα αφαιρεθούν lamport.

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

Ο λογαριασμός counter αποθηκεύει τα δεδομένα Counter από το state.rs. Το init ενημερώνει το Anchor να δημιουργήσει αυτόν τον λογαριασμό πριν εκτελεστεί ο χειριστής, και το payer = payer ενημερώνει το Anchor ποιος λογαριασμός πληρώνει για τη δημιουργία.

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

Μέγεθος Λογαριασμού

Ο περιορισμός space λέει στο Anchor πόσα δεδομένα λογαριασμού να δεσμεύσει. Το Anchor αποθηκεύει πρώτα έναν διακριτή 8 byte, και στη συνέχεια τα bytes που απαιτούνται για τα πεδία Counter. Ο διακριτής επιτρέπει στο Anchor να αναγνωρίζει αυτόν τον λογαριασμό ως λογαριασμό Counter πριν αποσειριοποιήσει τα δεδομένα του λογαριασμού.

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

Διεύθυνση Μετρητή

Οι περιορισμοί seeds και bump ορίζουν την αναμενόμενη διεύθυνση PDA για τον λογαριασμό μετρητή. Το Anchor επαληθεύει ότι ο παρεχόμενος λογαριασμός counter αντιστοιχεί σε αυτή τη διεύθυνση. Το πρότυπο χρησιμοποιεί ένα PDA ώστε οι χρήστες να μπορούν να εξάγουν τη διεύθυνση του μετρητή από το αναγνωριστικό προγράμματος και το seed, καθιστώντας τη διεύθυνση του μετρητή ντετερμινιστική.

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

Ο λογαριασμός system_program είναι απαραίτητος επειδή η δημιουργία νέου λογαριασμού χρησιμοποιεί το 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>,
}

Συνάρτηση Χειριστή

Η συνάρτηση handle_initialize εκτελείται αφού το Anchor επικυρώσει τους λογαριασμούς στο Initialize. Η τιμή ctx παρέχει στον χειριστή πρόσβαση σε αυτούς τους ελεγμένους λογαριασμούς.

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

Αρχικά Δεδομένα

Ο χειριστής γράφει τις πρώτες τιμές στον νέο λογαριασμό μετρητή. Η μέτρηση ξεκινά από 0, και ο πληρωτής γίνεται η αρχή που έχει δικαίωμα να αυξάνει τον μετρητή αργότερα.

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 περιλαμβάνεται μόνο για να δείξει πώς ένα CPI περνά λογαριασμούς σε ένα άλλο πρόγραμμα. Η δομή Transfer παραθέτει τους λογαριασμούς που χρησιμοποιεί η μεταφορά του 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(())
}

Περιβάλλον CPI

CpiContext::new συνδυάζει το πρόγραμμα που καλείται με τους λογαριασμούς που περνούν σε αυτό το πρόγραμμα. Αυτή είναι η βασική δομή ενός CPI: επιλέξτε το πρόγραμμα προς κλήση, συγκεντρώστε τους λογαριασμούς που αναμένει το πρόγραμμα, και στη συνέχεια περάστε και τα δύο στην κλήση. Εδώ, το πρόγραμμα που καλείται είναι το 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 καλεί την εντολή μεταφοράς του System Program. Σε αυτό το πρότυπο, η μεταφορά είναι ένα μικρό παράδειγμα κλήσης ενός άλλου προγράμματος από το πρόγραμμά σας. Εάν το CPI μεταφοράς αποτύχει, η εντολή initialize αποτυγχάνει επίσης.

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! γράφει ένα μήνυμα στα αρχεία καταγραφής του προγράμματος. Ok(()) υποδηλώνει ότι η εντολή εκτελέστηκε με επιτυχία.

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

Η δομή Initialize ορίζει τους λογαριασμούς που πρέπει να συμπεριληφθούν όταν ένας χρήστης καλεί την εντολή initialize. Το Anchor ελέγχει αυτούς τους λογαριασμούς πριν εκτελεστεί ο χειριστής.

Payer Account

Ο λογαριασμός payer πληρώνει για τη δημιουργία του λογαριασμού μετρητή. Ο τύπος Signer<'info> σημαίνει ότι ο πληρωτής πρέπει να υπογράψει τη συναλλαγή, και το #[account(mut)] σημαίνει ότι ο λογαριασμός του πληρωτή μπορεί να τροποποιηθεί επειδή θα αφαιρεθούν lamport.

Counter Account

Ο λογαριασμός counter αποθηκεύει τα δεδομένα Counter από το state.rs. Το init ενημερώνει το Anchor να δημιουργήσει αυτόν τον λογαριασμό πριν εκτελεστεί ο χειριστής, και το payer = payer ενημερώνει το Anchor ποιος λογαριασμός πληρώνει για τη δημιουργία.

Μέγεθος Λογαριασμού

Ο περιορισμός space λέει στο Anchor πόσα δεδομένα λογαριασμού να δεσμεύσει. Το Anchor αποθηκεύει πρώτα έναν διακριτή 8 byte, και στη συνέχεια τα bytes που απαιτούνται για τα πεδία Counter. Ο διακριτής επιτρέπει στο Anchor να αναγνωρίζει αυτόν τον λογαριασμό ως λογαριασμό Counter πριν αποσειριοποιήσει τα δεδομένα του λογαριασμού.

Διεύθυνση Μετρητή

Οι περιορισμοί seeds και bump ορίζουν την αναμενόμενη διεύθυνση PDA για τον λογαριασμό μετρητή. Το Anchor επαληθεύει ότι ο παρεχόμενος λογαριασμός counter αντιστοιχεί σε αυτή τη διεύθυνση. Το πρότυπο χρησιμοποιεί ένα PDA ώστε οι χρήστες να μπορούν να εξάγουν τη διεύθυνση του μετρητή από το αναγνωριστικό προγράμματος και το seed, καθιστώντας τη διεύθυνση του μετρητή ντετερμινιστική.

System Program

Ο λογαριασμός system_program είναι απαραίτητος επειδή η δημιουργία νέου λογαριασμού χρησιμοποιεί το System Program.

Συνάρτηση Χειριστή

Η συνάρτηση handle_initialize εκτελείται αφού το Anchor επικυρώσει τους λογαριασμούς στο Initialize. Η τιμή ctx παρέχει στον χειριστή πρόσβαση σε αυτούς τους ελεγμένους λογαριασμούς.

Αρχικά Δεδομένα

Ο χειριστής γράφει τις πρώτες τιμές στον νέο λογαριασμό μετρητή. Η μέτρηση ξεκινά από 0, και ο πληρωτής γίνεται η αρχή που έχει δικαίωμα να αυξάνει τον μετρητή αργότερα.

Λογαριασμοί Μεταφοράς

Αυτή η μεταφορά CPI περιλαμβάνεται μόνο για να δείξει πώς ένα CPI περνά λογαριασμούς σε ένα άλλο πρόγραμμα. Η δομή Transfer παραθέτει τους λογαριασμούς που χρησιμοποιεί η μεταφορά του System Program.

Περιβάλλον CPI

CpiContext::new συνδυάζει το πρόγραμμα που καλείται με τους λογαριασμούς που περνούν σε αυτό το πρόγραμμα. Αυτή είναι η βασική δομή ενός CPI: επιλέξτε το πρόγραμμα προς κλήση, συγκεντρώστε τους λογαριασμούς που αναμένει το πρόγραμμα, και στη συνέχεια περάστε και τα δύο στην κλήση. Εδώ, το πρόγραμμα που καλείται είναι το System Program.

Invoke Transfer

anchor_lang::system_program::transfer καλεί την εντολή μεταφοράς του System Program. Σε αυτό το πρότυπο, η μεταφορά είναι ένα μικρό παράδειγμα κλήσης ενός άλλου προγράμματος από το πρόγραμμά σας. Εάν το CPI μεταφοράς αποτύχει, η εντολή initialize αποτυγχάνει επίσης.

Log Message

msg! γράφει ένα μήνυμα στα αρχεία καταγραφής του προγράμματος. Ok(()) υποδηλώνει ότι η εντολή εκτελέστηκε με επιτυχία.

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 ορίζει τους λογαριασμούς που απαιτούνται για την ενημέρωση ενός υπάρχοντος λογαριασμού μετρητή. Ο χειριστής ελέγχει ότι ο υπογράφων είναι η αποθηκευμένη αρχή, ελέγχει ότι η μέτρηση δεν έχει φτάσει το καθορισμένο MAX_COUNT, και στη συνέχεια αυξάνει τη μέτρηση.

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

Η δομή Increment ορίζει τους λογαριασμούς που πρέπει να συμπεριληφθούν όταν ένας χρήστης καλεί την εντολή increment. Το Anchor ελέγχει αυτούς τους λογαριασμούς πριν εκτελεστεί ο χειριστής.

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

Ο λογαριασμός counter αποθηκεύει τα δεδομένα Counter. Ο περιορισμός mut επιτρέπει στον χειριστή να ενημερώσει την αποθηκευμένη μέτρηση, και οι περιορισμοί seeds και bump επαληθεύουν τη διεύθυνση PDA του μετρητή.

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

Ο λογαριασμός authority πρέπει να υπογράψει τη συναλλαγή. Ο χειριστής στη συνέχεια ελέγχει ότι αυτός ο υπογράφων αντιστοιχεί στην αρχή που είναι αποθηκευμένη στον λογαριασμό μετρητή.

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

Συνάρτηση Handler

Η συνάρτηση handle_increment εκτελείται αφού το Anchor επικυρώσει τους λογαριασμούς στο Increment. Η τιμή ctx δίνει στον handler πρόσβαση σε αυτούς τους ελεγμένους λογαριασμούς.

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

Έλεγχος Εξουσιοδότησης

Ο πρώτος έλεγχος διασφαλίζει ότι ο υπογράφων επιτρέπεται να ενημερώσει αυτόν τον μετρητή. Εάν η διεύθυνση του υπογράφοντα δεν ταιριάζει με counter.authority, η εντολή σταματά με ErrorCode::Unauthorized. Αυτό δείχνει εξουσιοδότηση σε επίπεδο εφαρμογής: το πρόγραμμα κατέχει τα δεδομένα του μετρητή, αλλά εφαρμόζει έναν κανόνα για το ποιος υπογράφων μπορεί να αλλάξει αυτά τα δεδομένα.

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

Έλεγχος Μέγιστης Τιμής Μετρητή

Ο δεύτερος έλεγχος εμποδίζει τον μετρητή να υπερβεί το MAX_COUNT. Εάν ο μετρητής βρίσκεται ήδη στο όριο, η εντολή σταματά με ErrorCode::CounterOverflow. Αυτό το όριο είναι ένας τεχνητός κανόνας στο πρότυπο, ώστε να μπορείτε να δείτε πώς τα προσαρμοσμένα σφάλματα σταματούν μια εντολή πριν αλλάξουν τα δεδομένα λογαριασμού.

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

Ενημέρωση Μετρητή

Μόνο αφού περάσουν και οι δύο έλεγχοι ο handler ενημερώνει τα δεδομένα του λογαριασμού. Αυτή η γραμμή προσθέτει ένα στην αποθηκευμένη τιμή του μετρητή.

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

Μήνυμα Καταγραφής

Το msg! γράφει τον ενημερωμένο αριθμό στα αρχεία καταγραφής του προγράμματος. Το Ok(()) υποδεικνύει ότι η εντολή επιστράφηκε επιτυχώς.

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

Η δομή Increment ορίζει τους λογαριασμούς που πρέπει να συμπεριληφθούν όταν ένας χρήστης καλεί την εντολή increment. Το Anchor ελέγχει αυτούς τους λογαριασμούς πριν εκτελεστεί ο χειριστής.

Counter Account

Ο λογαριασμός counter αποθηκεύει τα δεδομένα Counter. Ο περιορισμός mut επιτρέπει στον χειριστή να ενημερώσει την αποθηκευμένη μέτρηση, και οι περιορισμοί seeds και bump επαληθεύουν τη διεύθυνση PDA του μετρητή.

Authority Signer

Ο λογαριασμός authority πρέπει να υπογράψει τη συναλλαγή. Ο χειριστής στη συνέχεια ελέγχει ότι αυτός ο υπογράφων αντιστοιχεί στην αρχή που είναι αποθηκευμένη στον λογαριασμό μετρητή.

Συνάρτηση Handler

Η συνάρτηση handle_increment εκτελείται αφού το Anchor επικυρώσει τους λογαριασμούς στο Increment. Η τιμή ctx δίνει στον handler πρόσβαση σε αυτούς τους ελεγμένους λογαριασμούς.

Έλεγχος Εξουσιοδότησης

Ο πρώτος έλεγχος διασφαλίζει ότι ο υπογράφων επιτρέπεται να ενημερώσει αυτόν τον μετρητή. Εάν η διεύθυνση του υπογράφοντα δεν ταιριάζει με counter.authority, η εντολή σταματά με ErrorCode::Unauthorized. Αυτό δείχνει εξουσιοδότηση σε επίπεδο εφαρμογής: το πρόγραμμα κατέχει τα δεδομένα του μετρητή, αλλά εφαρμόζει έναν κανόνα για το ποιος υπογράφων μπορεί να αλλάξει αυτά τα δεδομένα.

Έλεγχος Μέγιστης Τιμής Μετρητή

Ο δεύτερος έλεγχος εμποδίζει τον μετρητή να υπερβεί το MAX_COUNT. Εάν ο μετρητής βρίσκεται ήδη στο όριο, η εντολή σταματά με ErrorCode::CounterOverflow. Αυτό το όριο είναι ένας τεχνητός κανόνας στο πρότυπο, ώστε να μπορείτε να δείτε πώς τα προσαρμοσμένα σφάλματα σταματούν μια εντολή πριν αλλάξουν τα δεδομένα λογαριασμού.

Ενημέρωση Μετρητή

Μόνο αφού περάσουν και οι δύο έλεγχοι ο handler ενημερώνει τα δεδομένα του λογαριασμού. Αυτή η γραμμή προσθέτει ένα στην αποθηκευμένη τιμή του μετρητή.

Μήνυμα Καταγραφής

Το msg! γράφει τον ενημερωμένο αριθμό στα αρχεία καταγραφής του προγράμματος. Το Ok(()) υποδεικνύει ότι η εντολή επιστράφηκε επιτυχώς.

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

Αρχείο δοκιμών

Το programs/my-program/tests/test_initialize.rs είναι ένα τεστ ενσωμάτωσης Rust. Δεν εκκινεί τοπικό validator. Αντίθετα, φορτώνει το μεταγλωττισμένο αρχείο .so στο LiteSVM, δημιουργεί συναλλαγές που καλούν το πρόγραμμα και διαβάζει τον λογαριασμό μετρητή μετά από κάθε συναλλαγή. Το τεστ δημιουργεί εντολές για μια συναλλαγή Solana καθορίζοντας το αναγνωριστικό προγράμματος που θα κληθεί, παρέχοντας instruction data και μεταβιβάζοντας τους απαιτούμενους λογαριασμούς.

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

Το περιβάλλον λογαριασμού Initialize ορίζει τους λογαριασμούς που απαιτούνται από την εντολή initialize. Το τεστ περνά τους ίδιους λογαριασμούς στο δημιουργημένο βοηθητικό πρόγραμμα my_program::accounts::Initialize:

  • payer μεταβιβάζεται ως payer: payer.pubkey().
  • counter μεταβιβάζεται ως counter.
  • system_program μεταβιβάζεται ως system_program::ID.

my_program::instruction::Initialize {}.data() δημιουργεί το instruction data. Εδώ θα κωδικοποιούνταν τα ορίσματα της εντολής, αλλά αυτή η εντολή initialize δεν απαιτεί κανένα όρισμα.

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

Το περιβάλλον λογαριασμού Increment ορίζει τους λογαριασμούς που απαιτούνται από την εντολή increment. Το τεστ περνά τους ίδιους λογαριασμούς στο δημιουργημένο βοηθητικό πρόγραμμα my_program::accounts::Increment:

  • counter μεταβιβάζεται ως counter.
  • authority μεταβιβάζεται ως authority: payer.pubkey().

my_program::instruction::Increment {}.data() δημιουργεί το instruction data. Εδώ θα κωδικοποιούνταν τα ορίσματα της εντολής, αλλά αυτή η εντολή increment δεν απαιτεί κανένα όρισμα.

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

Διαμόρφωση έργου

Τα αρχεία ρίζας του έργου ενημερώνουν το Anchor και το Cargo για το πώς να κατασκευάσουν, να δοκιμάσουν και να αναπτύξουν το πρόγραμμα. Για πλήρη αναφορά, δείτε την τεκμηρίωση του Anchor για τη διαμόρφωση Anchor.toml και το 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?

© 2026 Ίδρυμα Solana. Με επιφύλαξη παντός δικαιώματος.