Solana-dokumentaatioOhjelmien kehittäminenRust-ohjelmat

Rust-ohjelman rakenne

Rustilla kirjoitetuilla Solana-ohjelmilla on minimaaliset rakenteelliset vaatimukset, mikä mahdollistaa joustavuuden koodin organisoinnissa. Ainoa vaatimus on, että ohjelmalla on oltava entrypoint, joka määrittää, mistä ohjelman suoritus alkaa.

Ohjelman rakenne

Vaikka tiedostorakenteelle ei ole tiukkoja sääntöjä, Solana-ohjelmat noudattavat tyypillisesti yhteistä mallia:

  • entrypoint.rs: Määrittää sisääntulopisteen, joka ohjaa saapuvat käskyt.
  • state.rs: Määrittää ohjelman tilan (tilin tiedot).
  • instructions.rs: Määrittää käskyt, joita ohjelma voi suorittaa.
  • processor.rs: Määrittää käskykäsittelijät (funktiot), jotka toteuttavat liiketoimintalogiikan kullekin käskylle.
  • error.rs: Määrittää mukautetut virheet, joita ohjelma voi palauttaa.

Katso esimerkiksi Token Program.

Esimerkkiohjelma

Havainnollistaaksemme, miten rakennetaan natiivi Rust-ohjelma useilla käskyillä, käymme läpi yksinkertaisen laskuriohjelma, joka toteuttaa kaksi käskyä:

  1. InitializeCounter: Luo ja alustaa uuden tilin alkuarvolla.
  2. IncrementCounter: Kasvattaa olemassa olevaan tiliin tallennettua arvoa.

Yksinkertaisuuden vuoksi ohjelma toteutetaan yhdessä lib.rs-tiedostossa, vaikka käytännössä saatat haluta jakaa suuremmat ohjelmat useisiin tiedostoihin.

Osa 1: Ohjelman kirjoittaminen

Aloitetaan rakentamalla laskuriohjelma. Luomme ohjelman, joka voi alustaa laskurin alkuarvolla ja kasvattaa sitä.

Luo uusi ohjelma

Luodaan ensin uusi Rust-projekti Solana-ohjelmaamme varten.

Terminal
$
cargo new counter_program --lib
$
cd counter_program

Sinun pitäisi nähdä oletusmääräiset src/lib.rs- ja Cargo.toml-tiedostot.

Päivitä edition-kenttä tiedostossa Cargo.toml arvoon 2021. Muuten saatat kohdata virheen ohjelmaa käännettäessä.

Lisää riippuvuudet

Lisätään nyt tarvittavat riippuvuudet Solana-ohjelman rakentamiseen. Tarvitsemme solana-program:n ydin-SDK:ta varten ja borsh:n serialisointia varten.

Terminal
$
cargo add solana-program@2.2.0
$
cargo add borsh

Borshin käyttö ei ole pakollista. Se on kuitenkin yleisesti käytetty serialisointikirjasto Solana-ohjelmissa.

Määritä crate-type

Solana-ohjelmat on käännettävä dynaamisiksi kirjastoiksi. Lisää [lib]-osio määrittelemään, miten Cargo rakentaa ohjelman.

Cargo.toml
[lib]
crate-type = ["cdylib", "lib"]

Jos et sisällytä tätä konfiguraatiota, target/deploy-hakemistoa ei luoda, kun rakennat ohjelman.

Määritä ohjelman sisääntulopiste

Jokaisella Solana-ohjelmalla on sisääntulopiste, joka on funktio, jota kutsutaan, kun ohjelma suoritetaan. Aloitetaan lisäämällä tarvittavat importit ohjelmaan ja määrittelemällä sisääntulopiste.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::{rent::Rent, Sysvar},
};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}

Entrypoint-makro käsittelee input-datan deserialisoinnin process_instruction-funktion parametreiksi.

Solana-ohjelman entrypoint:lla on seuraava funktiosignatuuri. Kehittäjät voivat vapaasti luoda oman toteutuksensa entrypoint-funktiosta.

#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;

Määrittele ohjelman tila

Määritellään nyt datarakenne, joka tallennetaan laskuritileihimme. Tämä on data, joka tallennetaan tilin data -kenttään.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterAccount {
pub count: u64,
}

Määrittele käskyenum

Määritellään käskyt, joita ohjelmamme voi suorittaa. Käytämme enumia, jossa jokainen variantti edustaa eri käskyä.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum CounterInstruction {
InitializeCounter { initial_value: u64 },
IncrementCounter,
}

Toteuta käskyn deserialisointi

Nyt meidän täytyy deserialisoida instruction_data (raakatavut) yhdeksi CounterInstruction -enumin variantiksi. Borshin try_from_slice -metodi käsittelee tämän muunnoksen automaattisesti.

Päivitä process_instruction -funktio käyttämään Borsh-deserialisointia:

lib.rs
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = CounterInstruction::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;
Ok(())
}

Ohjaa käskyt käsittelijöille

Päivitetään nyt pää-process_instruction -funktio ohjaamaan käskyt niiden asianmukaisille käsittelijäfunktioille.

Tämä ohjausmalli on yleinen Solana-ohjelmissa. instruction_data deserialisoidaan käskyä edustavaksi enumin variantiksi, jonka jälkeen asianmukainen käsittelijäfunktio kutsutaan. Jokainen käsittelijäfunktio sisältää toteutuksen kyseiselle käskylle.

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_instruction -funktiota ja lisäten käsittelijät InitializeCounter- ja IncrementCounter -käskyille:

lib.rs
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = CounterInstruction::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;
match instruction {
CounterInstruction::InitializeCounter { initial_value } => {
process_initialize_counter(program_id, accounts, initial_value)?
}
CounterInstruction::IncrementCounter => {
process_increment_counter(program_id, accounts)?
}
};
Ok(())
}
fn process_initialize_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
initial_value: u64,
) -> ProgramResult {
Ok(())
}
fn process_increment_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
Ok(())
}

Toteuta alustuskäsittelijä

Toteutetaan käsittelijä uuden laskuritilin luomiseen ja alustamiseen. Koska vain System Program voi luoda tilejä Solanassa, käytämme Cross Program Invocationia (CPI), eli kutsumme toista ohjelmaa ohjelmastamme.

Ohjelmamme tekee CPI-kutsun System Programin create_account-ohjeelle. Uusi tili luodaan ohjelmamme omistukseen, mikä antaa ohjelmallemme mahdollisuuden kirjoittaa tilille ja alustaa data.

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_initialize_counter-funktiota:

lib.rs
fn process_initialize_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
initial_value: u64,
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let counter_account = next_account_info(accounts_iter)?;
let payer_account = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let account_space = 8;
let rent = Rent::get()?;
let required_lamports = rent.minimum_balance(account_space);
invoke(
&system_instruction::create_account(
payer_account.key,
counter_account.key,
required_lamports,
account_space as u64,
program_id,
),
&[
payer_account.clone(),
counter_account.clone(),
system_program.clone(),
],
)?;
let counter_data = CounterAccount {
count: initial_value,
};
let mut account_data = &mut counter_account.data.borrow_mut()[..];
counter_data.serialize(&mut account_data)?;
msg!("Counter initialized with value: {}", initial_value);
Ok(())
}

Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.

Toteuta kasvatuskäsittelijä

Toteutetaan nyt käsittelijä, joka kasvattaa olemassa olevaa laskuria. Tämä ohje:

  • Lukee tilin data-kentän counter_account-tilille
  • Deserialisoi sen CounterAccount-structiksi
  • Kasvattaa count-kenttää yhdellä
  • Serialisoi CounterAccount-structin takaisin tilin data-kenttään

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_increment_counter-funktiota:

lib.rs
fn process_increment_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let counter_account = next_account_info(accounts_iter)?;
if counter_account.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let mut data = counter_account.data.borrow_mut();
let mut counter_data: CounterAccount = CounterAccount::try_from_slice(&data)?;
counter_data.count = counter_data
.count
.checked_add(1)
.ok_or(ProgramError::InvalidAccountData)?;
counter_data.serialize(&mut &mut data[..])?;
msg!("Counter incremented to: {}", counter_data.count);
Ok(())
}

Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.

Valmis ohjelma

Onnittelut! Olet rakentanut täydellisen Solana-ohjelman, joka demonstroi perusrakenteen, joka on yhteinen kaikille Solana-ohjelmille:

  • Entrypoint: Määrittää, mistä ohjelman suoritus alkaa ja reitittää kaikki saapuvat pyynnöt asianmukaisille ohjekäsittelijöille
  • Ohjeiden käsittely: Määrittää ohjeet ja niiden käsittelijäfunktiot
  • Tilan hallinta: Määrittää tilidatarakenteet ja hallitsee niiden tilaa ohjelman omistamissa tileissä
  • Cross Program Invocation (CPI): Kutsuu System Programia luomaan uusia ohjelman omistamia tilejä

Seuraava vaihe on testata ohjelma varmistaaksesi, että kaikki toimii oikein.

Luo uusi ohjelma

Luodaan ensin uusi Rust-projekti Solana-ohjelmaamme varten.

Terminal
$
cargo new counter_program --lib
$
cd counter_program

Sinun pitäisi nähdä oletusmääräiset src/lib.rs- ja Cargo.toml-tiedostot.

Päivitä edition-kenttä tiedostossa Cargo.toml arvoon 2021. Muuten saatat kohdata virheen ohjelmaa käännettäessä.

Lisää riippuvuudet

Lisätään nyt tarvittavat riippuvuudet Solana-ohjelman rakentamiseen. Tarvitsemme solana-program:n ydin-SDK:ta varten ja borsh:n serialisointia varten.

Terminal
$
cargo add solana-program@2.2.0
$
cargo add borsh

Borshin käyttö ei ole pakollista. Se on kuitenkin yleisesti käytetty serialisointikirjasto Solana-ohjelmissa.

Määritä crate-type

Solana-ohjelmat on käännettävä dynaamisiksi kirjastoiksi. Lisää [lib]-osio määrittelemään, miten Cargo rakentaa ohjelman.

Cargo.toml
[lib]
crate-type = ["cdylib", "lib"]

Jos et sisällytä tätä konfiguraatiota, target/deploy-hakemistoa ei luoda, kun rakennat ohjelman.

Määritä ohjelman sisääntulopiste

Jokaisella Solana-ohjelmalla on sisääntulopiste, joka on funktio, jota kutsutaan, kun ohjelma suoritetaan. Aloitetaan lisäämällä tarvittavat importit ohjelmaan ja määrittelemällä sisääntulopiste.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::{rent::Rent, Sysvar},
};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}

Entrypoint-makro käsittelee input-datan deserialisoinnin process_instruction-funktion parametreiksi.

Solana-ohjelman entrypoint:lla on seuraava funktiosignatuuri. Kehittäjät voivat vapaasti luoda oman toteutuksensa entrypoint-funktiosta.

#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;

Määrittele ohjelman tila

Määritellään nyt datarakenne, joka tallennetaan laskuritileihimme. Tämä on data, joka tallennetaan tilin data -kenttään.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterAccount {
pub count: u64,
}

Määrittele käskyenum

Määritellään käskyt, joita ohjelmamme voi suorittaa. Käytämme enumia, jossa jokainen variantti edustaa eri käskyä.

Lisää seuraava koodi tiedostoon lib.rs:

lib.rs
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum CounterInstruction {
InitializeCounter { initial_value: u64 },
IncrementCounter,
}

Toteuta käskyn deserialisointi

Nyt meidän täytyy deserialisoida instruction_data (raakatavut) yhdeksi CounterInstruction -enumin variantiksi. Borshin try_from_slice -metodi käsittelee tämän muunnoksen automaattisesti.

Päivitä process_instruction -funktio käyttämään Borsh-deserialisointia:

lib.rs
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = CounterInstruction::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;
Ok(())
}

Ohjaa käskyt käsittelijöille

Päivitetään nyt pää-process_instruction -funktio ohjaamaan käskyt niiden asianmukaisille käsittelijäfunktioille.

Tämä ohjausmalli on yleinen Solana-ohjelmissa. instruction_data deserialisoidaan käskyä edustavaksi enumin variantiksi, jonka jälkeen asianmukainen käsittelijäfunktio kutsutaan. Jokainen käsittelijäfunktio sisältää toteutuksen kyseiselle käskylle.

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_instruction -funktiota ja lisäten käsittelijät InitializeCounter- ja IncrementCounter -käskyille:

lib.rs
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = CounterInstruction::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;
match instruction {
CounterInstruction::InitializeCounter { initial_value } => {
process_initialize_counter(program_id, accounts, initial_value)?
}
CounterInstruction::IncrementCounter => {
process_increment_counter(program_id, accounts)?
}
};
Ok(())
}
fn process_initialize_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
initial_value: u64,
) -> ProgramResult {
Ok(())
}
fn process_increment_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
Ok(())
}

Toteuta alustuskäsittelijä

Toteutetaan käsittelijä uuden laskuritilin luomiseen ja alustamiseen. Koska vain System Program voi luoda tilejä Solanassa, käytämme Cross Program Invocationia (CPI), eli kutsumme toista ohjelmaa ohjelmastamme.

Ohjelmamme tekee CPI-kutsun System Programin create_account-ohjeelle. Uusi tili luodaan ohjelmamme omistukseen, mikä antaa ohjelmallemme mahdollisuuden kirjoittaa tilille ja alustaa data.

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_initialize_counter-funktiota:

lib.rs
fn process_initialize_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
initial_value: u64,
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let counter_account = next_account_info(accounts_iter)?;
let payer_account = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let account_space = 8;
let rent = Rent::get()?;
let required_lamports = rent.minimum_balance(account_space);
invoke(
&system_instruction::create_account(
payer_account.key,
counter_account.key,
required_lamports,
account_space as u64,
program_id,
),
&[
payer_account.clone(),
counter_account.clone(),
system_program.clone(),
],
)?;
let counter_data = CounterAccount {
count: initial_value,
};
let mut account_data = &mut counter_account.data.borrow_mut()[..];
counter_data.serialize(&mut account_data)?;
msg!("Counter initialized with value: {}", initial_value);
Ok(())
}

Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.

Toteuta kasvatuskäsittelijä

Toteutetaan nyt käsittelijä, joka kasvattaa olemassa olevaa laskuria. Tämä ohje:

  • Lukee tilin data-kentän counter_account-tilille
  • Deserialisoi sen CounterAccount-structiksi
  • Kasvattaa count-kenttää yhdellä
  • Serialisoi CounterAccount-structin takaisin tilin data-kenttään

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_increment_counter-funktiota:

lib.rs
fn process_increment_counter(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let counter_account = next_account_info(accounts_iter)?;
if counter_account.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let mut data = counter_account.data.borrow_mut();
let mut counter_data: CounterAccount = CounterAccount::try_from_slice(&data)?;
counter_data.count = counter_data
.count
.checked_add(1)
.ok_or(ProgramError::InvalidAccountData)?;
counter_data.serialize(&mut &mut data[..])?;
msg!("Counter incremented to: {}", counter_data.count);
Ok(())
}

Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.

Valmis ohjelma

Onnittelut! Olet rakentanut täydellisen Solana-ohjelman, joka demonstroi perusrakenteen, joka on yhteinen kaikille Solana-ohjelmille:

  • Entrypoint: Määrittää, mistä ohjelman suoritus alkaa ja reitittää kaikki saapuvat pyynnöt asianmukaisille ohjekäsittelijöille
  • Ohjeiden käsittely: Määrittää ohjeet ja niiden käsittelijäfunktiot
  • Tilan hallinta: Määrittää tilidatarakenteet ja hallitsee niiden tilaa ohjelman omistamissa tileissä
  • Cross Program Invocation (CPI): Kutsuu System Programia luomaan uusia ohjelman omistamia tilejä

Seuraava vaihe on testata ohjelma varmistaaksesi, että kaikki toimii oikein.

Cargo.toml
lib.rs
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[dependencies]

Osa 2: Ohjelman testaaminen

Testataan nyt laskuriohjelmaamme. Käytämme LiteSVM-testikehystä, jonka avulla voimme testata ohjelmia ilman niiden julkaisemista klusteriin.

Lisää testiriippuvuudet

Lisätään ensin testaamiseen tarvittavat riippuvuudet. Käytämme litesvm-kirjastoa testaamiseen ja solana-sdk-kirjastoa.

Terminal
$
cargo add litesvm@0.6.1 --dev
$
cargo add solana-sdk@2.2.0 --dev

Luo testimoduuli

Lisätään nyt testimoduuli ohjelmaamme. Aloitetaan perusrakenteella ja importeilla.

Lisää seuraava koodi tiedostoon lib.rs, suoraan ohjelmakoodin alle:

lib.rs
#[cfg(test)]
mod test {
use super::*;
use litesvm::LiteSVM;
use solana_sdk::{
account::ReadableAccount,
instruction::{AccountMeta, Instruction},
message::Message,
signature::{Keypair, Signer},
system_program,
transaction::Transaction,
};
#[test]
fn test_counter_program() {
// Test implementation will go here
}
}

#[cfg(test)]-attribuutti varmistaa, että tämä koodi käännetään vain testejä ajettaessa.

Alusta testiympäristö

Konfiguroidaan testiympäristö LiteSVM:llä ja rahoitetaan maksajatili.

LiteSVM simuloi Solana-ajoympäristöä, jolloin voimme testata ohjelmaamme ilman julkaisemista oikeaan klusteriin.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let mut svm = LiteSVM::new();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000)
.expect("Failed to airdrop");

Lataa ohjelma

Nyt meidän täytyy rakentaa ja ladata ohjelmamme testiympäristöön. Suorita cargo build-sbf-komento rakentaaksesi ohjelman. Tämä luo counter_program.so-tiedoston target/deploy-hakemistoon.

Terminal
$
cargo build-sbf

Varmista, että edition tiedostossa Cargo.toml on asetettu arvoon 2021.

Rakentamisen jälkeen voimme ladata ohjelman.

Päivitä test_counter_program-funktio lataamaan ohjelma testiympäristöön.

lib.rs
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
svm.add_program_from_file(
program_id,
"target/deploy/counter_program.so"
).expect("Failed to load program");

Sinun täytyy suorittaa cargo build-sbf ennen testien ajamista luodaksesi .so-tiedoston. Testi lataa käännetyn ohjelman.

Testaa alustusinstruktio

Testataan alustusinstruktio luomalla uusi laskuritili alkuarvolla.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let counter_keypair = Keypair::new();
let initial_value: u64 = 42;
println!("Testing counter initialization...");
let init_instruction_data =
borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value })
.expect("Failed to serialize instruction");
let initialize_instruction = Instruction::new_with_bytes(
program_id,
&init_instruction_data,
vec![
AccountMeta::new(counter_keypair.pubkey(), true),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
],
);
let message = Message::new(&[initialize_instruction], Some(&payer.pubkey()));
let transaction = Transaction::new(
&[&payer, &counter_keypair],
message,
svm.latest_blockhash()
);
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "Initialize transaction should succeed");
let logs = result.unwrap().logs;
println!("Transaction logs:\n{:#?}", logs);

Varmista alustus

Alustuksen jälkeen varmistetaan, että laskuritili luotiin oikein odotetulla arvolla.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let account = svm
.get_account(&counter_keypair.pubkey())
.expect("Failed to get counter account");
let counter: CounterAccount = CounterAccount::try_from_slice(account.data())
.expect("Failed to deserialize counter data");
assert_eq!(counter.count, 42);
println!("Counter initialized successfully with value: {}", counter.count);

Testaa kasvattamisinstruktio

Testataan nyt kasvattamisinstruktio varmistaaksemme, että se päivittää laskuriarvon oikein.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
println!("Testing counter increment...");
let increment_instruction_data =
borsh::to_vec(&CounterInstruction::IncrementCounter)
.expect("Failed to serialize instruction");
let increment_instruction = Instruction::new_with_bytes(
program_id,
&increment_instruction_data,
vec![AccountMeta::new(counter_keypair.pubkey(), true)],
);
let message = Message::new(&[increment_instruction], Some(&payer.pubkey()));
let transaction = Transaction::new(
&[&payer, &counter_keypair],
message,
svm.latest_blockhash()
);
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "Increment transaction should succeed");
let logs = result.unwrap().logs;
println!("Transaction logs:\n{:#?}", logs);

Varmista lopputulokset

Lopuksi varmistetaan, että kasvattaminen toimi oikein tarkistamalla päivitetty laskuriarvo.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let account = svm
.get_account(&counter_keypair.pubkey())
.expect("Failed to get counter account");
let counter: CounterAccount = CounterAccount::try_from_slice(account.data())
.expect("Failed to deserialize counter data");
assert_eq!(counter.count, 43);
println!("Counter incremented successfully to: {}", counter.count);

Suorita testit seuraavalla komennolla. --nocapture -lippu tulostaa testin tulosteen.

Terminal
$
cargo test -- --nocapture

Odotettu tulos:

Testing counter initialization...
Transaction logs:
[
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]",
"Program 11111111111111111111111111111111 invoke [2]",
"Program 11111111111111111111111111111111 success",
"Program log: Counter initialized with value: 42",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 3803 of 200000 compute units",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",
]
Counter initialized successfully with value: 42
Testing counter increment...
Transaction logs:
[
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]",
"Program log: Counter incremented to: 43",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 762 of 200000 compute units",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",
]
Counter incremented successfully to: 43

Lisää testiriippuvuudet

Lisätään ensin testaamiseen tarvittavat riippuvuudet. Käytämme litesvm-kirjastoa testaamiseen ja solana-sdk-kirjastoa.

Terminal
$
cargo add litesvm@0.6.1 --dev
$
cargo add solana-sdk@2.2.0 --dev

Luo testimoduuli

Lisätään nyt testimoduuli ohjelmaamme. Aloitetaan perusrakenteella ja importeilla.

Lisää seuraava koodi tiedostoon lib.rs, suoraan ohjelmakoodin alle:

lib.rs
#[cfg(test)]
mod test {
use super::*;
use litesvm::LiteSVM;
use solana_sdk::{
account::ReadableAccount,
instruction::{AccountMeta, Instruction},
message::Message,
signature::{Keypair, Signer},
system_program,
transaction::Transaction,
};
#[test]
fn test_counter_program() {
// Test implementation will go here
}
}

#[cfg(test)]-attribuutti varmistaa, että tämä koodi käännetään vain testejä ajettaessa.

Alusta testiympäristö

Konfiguroidaan testiympäristö LiteSVM:llä ja rahoitetaan maksajatili.

LiteSVM simuloi Solana-ajoympäristöä, jolloin voimme testata ohjelmaamme ilman julkaisemista oikeaan klusteriin.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let mut svm = LiteSVM::new();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000)
.expect("Failed to airdrop");

Lataa ohjelma

Nyt meidän täytyy rakentaa ja ladata ohjelmamme testiympäristöön. Suorita cargo build-sbf-komento rakentaaksesi ohjelman. Tämä luo counter_program.so-tiedoston target/deploy-hakemistoon.

Terminal
$
cargo build-sbf

Varmista, että edition tiedostossa Cargo.toml on asetettu arvoon 2021.

Rakentamisen jälkeen voimme ladata ohjelman.

Päivitä test_counter_program-funktio lataamaan ohjelma testiympäristöön.

lib.rs
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
svm.add_program_from_file(
program_id,
"target/deploy/counter_program.so"
).expect("Failed to load program");

Sinun täytyy suorittaa cargo build-sbf ennen testien ajamista luodaksesi .so-tiedoston. Testi lataa käännetyn ohjelman.

Testaa alustusinstruktio

Testataan alustusinstruktio luomalla uusi laskuritili alkuarvolla.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let counter_keypair = Keypair::new();
let initial_value: u64 = 42;
println!("Testing counter initialization...");
let init_instruction_data =
borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value })
.expect("Failed to serialize instruction");
let initialize_instruction = Instruction::new_with_bytes(
program_id,
&init_instruction_data,
vec![
AccountMeta::new(counter_keypair.pubkey(), true),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
],
);
let message = Message::new(&[initialize_instruction], Some(&payer.pubkey()));
let transaction = Transaction::new(
&[&payer, &counter_keypair],
message,
svm.latest_blockhash()
);
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "Initialize transaction should succeed");
let logs = result.unwrap().logs;
println!("Transaction logs:\n{:#?}", logs);

Varmista alustus

Alustuksen jälkeen varmistetaan, että laskuritili luotiin oikein odotetulla arvolla.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let account = svm
.get_account(&counter_keypair.pubkey())
.expect("Failed to get counter account");
let counter: CounterAccount = CounterAccount::try_from_slice(account.data())
.expect("Failed to deserialize counter data");
assert_eq!(counter.count, 42);
println!("Counter initialized successfully with value: {}", counter.count);

Testaa kasvattamisinstruktio

Testataan nyt kasvattamisinstruktio varmistaaksemme, että se päivittää laskuriarvon oikein.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
println!("Testing counter increment...");
let increment_instruction_data =
borsh::to_vec(&CounterInstruction::IncrementCounter)
.expect("Failed to serialize instruction");
let increment_instruction = Instruction::new_with_bytes(
program_id,
&increment_instruction_data,
vec![AccountMeta::new(counter_keypair.pubkey(), true)],
);
let message = Message::new(&[increment_instruction], Some(&payer.pubkey()));
let transaction = Transaction::new(
&[&payer, &counter_keypair],
message,
svm.latest_blockhash()
);
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "Increment transaction should succeed");
let logs = result.unwrap().logs;
println!("Transaction logs:\n{:#?}", logs);

Varmista lopputulokset

Lopuksi varmistetaan, että kasvattaminen toimi oikein tarkistamalla päivitetty laskuriarvo.

Lisää seuraava koodi tiedostoon lib.rs päivittäen test_counter_program-funktiota:

lib.rs
let account = svm
.get_account(&counter_keypair.pubkey())
.expect("Failed to get counter account");
let counter: CounterAccount = CounterAccount::try_from_slice(account.data())
.expect("Failed to deserialize counter data");
assert_eq!(counter.count, 43);
println!("Counter incremented successfully to: {}", counter.count);

Suorita testit seuraavalla komennolla. --nocapture -lippu tulostaa testin tulosteen.

Terminal
$
cargo test -- --nocapture

Odotettu tulos:

Testing counter initialization...
Transaction logs:
[
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]",
"Program 11111111111111111111111111111111 invoke [2]",
"Program 11111111111111111111111111111111 success",
"Program log: Counter initialized with value: 42",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 3803 of 200000 compute units",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",
]
Counter initialized successfully with value: 42
Testing counter increment...
Transaction logs:
[
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]",
"Program log: Counter incremented to: 43",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 762 of 200000 compute units",
"Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",
]
Counter incremented successfully to: 43
Cargo.toml
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
borsh = "1.5.7"
solana-program = "2.2.0"
[dev-dependencies]
litesvm = "0.6.1"
solana-sdk = "2.2.0"

Osa 3: Ohjelman kutsuminen

Lisätään nyt asiakasohjelma ohjelman kutsumista varten.

Luo asiakasesimerkki

Luodaan Rust-asiakas vuorovaikutukseen käyttöönotetun ohjelman kanssa.

Terminal
$
mkdir examples
$
touch examples/client.rs

Lisää seuraava konfiguraatio tiedostoon Cargo.toml:

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

Asenna asiakkaan riippuvuudet:

Terminal
$
cargo add solana-client@2.2.0 --dev
$
cargo add tokio --dev

Toteuta asiakaskoodi

Toteutetaan nyt asiakas, joka kutsuu käyttöönotettua ohjelmaamme.

Suorita seuraava komento saadaksesi ohjelmatunnuksesi keypair-tiedostosta:

Terminal
$
solana address -k ./target/deploy/counter_program-keypair.json

Lisää asiakaskoodi tiedostoon examples/client.rs ja korvaa program_id edellisen komennon tulosteella:

examples/client.rs
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
examples/client.rs
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
signature::{Keypair, Signer},
system_program,
transaction::Transaction,
};
use std::str::FromStr;
use counter_program::CounterInstruction;
#[tokio::main]
async fn main() {
// Replace with your actual program ID from deployment
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
// Connect to local cluster
let rpc_url = String::from("http://localhost:8899");
let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
// Generate a new keypair for paying fees
let payer = Keypair::new();
// Request airdrop of 1 SOL for transaction fees
println!("Requesting airdrop...");
let airdrop_signature = client
.request_airdrop(&payer.pubkey(), 1_000_000_000)
.expect("Failed to request airdrop");
// Wait for airdrop confirmation
loop {
if client
.confirm_transaction(&airdrop_signature)
.unwrap_or(false)
{
break;
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
println!("Airdrop confirmed");
println!("\nInitializing counter...");
let counter_keypair = Keypair::new();
let initial_value = 100u64;
// Serialize the initialize instruction data
let instruction_data = borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value })
.expect("Failed to serialize instruction");
let initialize_instruction = Instruction::new_with_bytes(
program_id,
&instruction_data,
vec![
AccountMeta::new(counter_keypair.pubkey(), true),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
],
);
let mut transaction =
Transaction::new_with_payer(&[initialize_instruction], Some(&payer.pubkey()));
let blockhash = client
.get_latest_blockhash()
.expect("Failed to get blockhash");
transaction.sign(&[&payer, &counter_keypair], blockhash);
match client.send_and_confirm_transaction(&transaction) {
Ok(signature) => {
println!("Counter initialized!");
println!("Transaction: {}", signature);
println!("Counter address: {}", counter_keypair.pubkey());
}
Err(err) => {
eprintln!("Failed to initialize counter: {}", err);
return;
}
}
println!("\nIncrementing counter...");
// Serialize the increment instruction data
let increment_data = borsh::to_vec(&CounterInstruction::IncrementCounter)
.expect("Failed to serialize instruction");
let increment_instruction = Instruction::new_with_bytes(
program_id,
&increment_data,
vec![AccountMeta::new(counter_keypair.pubkey(), true)],
);
let mut transaction =
Transaction::new_with_payer(&[increment_instruction], Some(&payer.pubkey()));
transaction.sign(&[&payer, &counter_keypair], blockhash);
match client.send_and_confirm_transaction(&transaction) {
Ok(signature) => {
println!("Counter incremented!");
println!("Transaction: {}", signature);
}
Err(err) => {
eprintln!("Failed to increment counter: {}", err);
}
}
}

Luo asiakasesimerkki

Luodaan Rust-asiakas vuorovaikutukseen käyttöönotetun ohjelman kanssa.

Terminal
$
mkdir examples
$
touch examples/client.rs

Lisää seuraava konfiguraatio tiedostoon Cargo.toml:

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

Asenna asiakkaan riippuvuudet:

Terminal
$
cargo add solana-client@2.2.0 --dev
$
cargo add tokio --dev

Toteuta asiakaskoodi

Toteutetaan nyt asiakas, joka kutsuu käyttöönotettua ohjelmaamme.

Suorita seuraava komento saadaksesi ohjelmatunnuksesi keypair-tiedostosta:

Terminal
$
solana address -k ./target/deploy/counter_program-keypair.json

Lisää asiakaskoodi tiedostoon examples/client.rs ja korvaa program_id edellisen komennon tulosteella:

examples/client.rs
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
Cargo.toml
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
borsh = "1.5.7"
solana-program = "2.2.0"
[dev-dependencies]
litesvm = "0.6.1"
solana-sdk = "2.2.0"
solana-client = "2.2.0"
tokio = "1.47.1"
[[example]]
name = "client"
path = "examples/client.rs"

Osa 4: Ohjelman käyttöönotto

Nyt kun ohjelmamme ja asiakkasmme ovat valmiit, rakennetaan, otetaan käyttöön ja kutsutaan ohjelmaa.

Rakenna ohjelma

Ensin rakennetaan ohjelmamme.

Terminal
$
cargo build-sbf

Tämä komento kääntää ohjelmasi ja luo kaksi tärkeää tiedostoa hakemistoon target/deploy/:

counter_program.so # The compiled program
counter_program-keypair.json # Keypair for the program ID

Voit tarkastella ohjelmasi ID:tä suorittamalla seuraavan komennon:

Terminal
$
solana address -k ./target/deploy/counter_program-keypair.json

Esimerkki tulostuksesta:

HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF

Käynnistä paikallinen validaattori

Kehitystyössä käytämme paikallista testivalidaattoria.

Ensin määritetään Solana CLI käyttämään localhostia:

Terminal
$
solana config set -ul

Esimerkki tulostuksesta:

Config File: ~/.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: ~/.config/solana/id.json
Commitment: confirmed

Käynnistä nyt testivalidaattori erillisessä terminaalissa:

Terminal
$
solana-test-validator

Ohjelman käyttöönotto

Kun validaattori on käynnissä, ota ohjelma käyttöön paikallisessa klusterissa:

Terminal
$
solana program deploy ./target/deploy/counter_program.so

Esimerkki tulostuksesta:

Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Signature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h

Voit varmistaa käyttöönoton käyttämällä solana program show -komentoa ohjelman tunnuksella:

Terminal
$
solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF

Esimerkki tulostuksesta:

Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuM
Authority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1
Last Deployed In Slot: 16
Data Length: 82696 (0x14308) bytes
Balance: 0.57676824 SOL

Asiakkaan suorittaminen

Kun paikallinen validaattori on edelleen käynnissä, suorita asiakas:

Terminal
$
cargo run --example client

Odotettu tuloste:

Requesting airdrop...
Airdrop confirmed
Initializing counter...
Counter initialized!
Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14k
Counter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9Zfofcy
Incrementing counter...
Counter incremented!
Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS

Kun paikallinen validaattori on käynnissä, voit tarkastella transaktioita Solana Explorerissa käyttämällä tulosteen transaktioallekirjoituksia. Huomaa, että Solana Explorerin klusterin on oltava asetettu "Custom RPC URL" -tilaan, joka oletuksena on http://localhost:8899, jossa solana-test-validator on käynnissä.

Is this page helpful?

Sisällysluettelo

Muokkaa sivua

Hallinnoi

© 2026 Solana Foundation.
Kaikki oikeudet pidätetään.
Yhdistä