Rust program yapısı

Rust ile yazılan Solana programları minimum yapısal gereksinimlere sahiptir ve kodun nasıl organize edileceği konusunda esneklik sağlar. Tek gereksinim, programın bir entrypoint içermesi ve bu noktanın programın yürütülmesinin nerede başladığını tanımlamasıdır.

Program yapısı

Dosya yapısı için katı kurallar olmasa da, Solana programları genellikle ortak bir deseni takip eder:

  • entrypoint.rs: Gelen talimatları yönlendiren giriş noktasını tanımlar.
  • state.rs: Program durumunu (hesap verisi) tanımlar.
  • instructions.rs: Programın yürütebileceği talimatları tanımlar.
  • processor.rs: Her talimat için iş mantığını uygulayan talimat işleyicilerini (fonksiyonlar) tanımlar.
  • error.rs: Programın döndürebileceği özel hataları tanımlar.

Örneğin, Token Program sayfasına bakın.

Örnek program

Birden fazla talimata sahip yerel bir Rust programının nasıl oluşturulacağını göstermek için, iki talimat uygulayan basit bir sayaç programını adım adım inceleyeceğiz:

  1. InitializeCounter: Başlangıç değeriyle yeni bir hesap oluşturur ve başlatır.
  2. IncrementCounter: Mevcut bir hesapta saklanan değeri artırır.

Basitlik açısından, program tek bir lib.rs dosyasında uygulanacaktır, ancak pratikte daha büyük programları birden fazla dosyaya bölmek isteyebilirsiniz.

Bölüm 1: Programı yazmak

Sayaç programını oluşturmaya başlayalım. Bir sayacı başlangıç değeriyle başlatabilen ve artırabilen bir program oluşturacağız.

Yeni bir program oluşturma

İlk olarak, Solana programımız için yeni bir Rust projesi oluşturalım.

Terminal
$
cargo new counter_program --lib
$
cd counter_program

Varsayılan src/lib.rs ve Cargo.toml dosyalarını görmelisiniz.

Cargo.toml dosyasındaki edition alanını 2021 olarak güncelleyin. Aksi takdirde, programı derlerken bir hatayla karşılaşabilirsiniz.

Bağımlılıkları ekleme

Şimdi bir Solana programı oluşturmak için gerekli bağımlılıkları ekleyelim. Temel SDK için solana-program ve serileştirme için borsh kütüphanelerine ihtiyacımız var.

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

Borsh kullanma zorunluluğu yoktur. Ancak, Solana programları için yaygın olarak kullanılan bir serileştirme kütüphanesidir.

Crate-type yapılandırması

Solana programları dinamik kütüphaneler olarak derlenmeli. Cargo'nun programı nasıl derleyeceğini yapılandırmak için [lib] bölümünü ekleyin.

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

Bu yapılandırmayı eklemezseniz, programı derlediğinizde target/deploy dizini oluşturulmayacaktır.

Program giriş noktasını ayarlama

Her Solana programının bir giriş noktası vardır; bu, program çağrıldığında çalıştırılan fonksiyondur. Program için ihtiyaç duyacağımız içe aktarmaları ekleyerek ve giriş noktasını ayarlayarak başlayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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 makrosu, input verisinin process_instruction fonksiyonunun parametrelerine deserileştirilmesini gerçekleştirir.

Bir Solana programı entrypoint fonksiyonu aşağıdaki imzaya sahiptir. Geliştiriciler entrypoint fonksiyonunun kendi uygulamalarını oluşturmakta özgürdür.

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

Program durumunu tanımlama

Şimdi sayaç hesaplarımızda saklanacak veri yapısını tanımlayalım. Bu, hesabın data alanında saklanacak veridir.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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

Talimat enum'ını tanımlama

Programımızın yürütebileceği talimatları tanımlayalım. Her varyantın farklı bir talimatı temsil ettiği bir enum kullanacağız.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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

Talimat deserializasyonunu uygulama

Şimdi instruction_data verisini (ham baytlar) CounterInstruction enum varyantlarımızdan birine deserialize etmemiz gerekiyor. Borsh try_from_slice metodu bu dönüşümü otomatik olarak gerçekleştirir.

process_instruction fonksiyonunu Borsh deserializasyonunu kullanacak şekilde güncelleyin:

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

Talimatları işleyicilere yönlendirme

Şimdi ana process_instruction fonksiyonunu, talimatları uygun işleyici fonksiyonlarına yönlendirecek şekilde güncelleyelim.

Bu yönlendirme deseni Solana programlarında yaygındır. instruction_data, talimatı temsil eden bir enum varyantına deserialize edilir, ardından uygun işleyici fonksiyonu çağrılır. Her işleyici fonksiyonu, o talimat için uygulamayı içerir.

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_instruction fonksiyonunu güncelleyin ve InitializeCounter ile IncrementCounter talimatları için işleyicileri ekleyin:

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

Başlatma işleyicisini uygulama

Yeni bir sayaç hesabı oluşturmak ve başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, bir Cross Program Invocation (CPI) kullanacağız, yani programımızdan başka bir programı çağıracağız.

Programımız, System Program'ın create_account instructionını çağırmak için bir CPI yapar. Yeni hesap, programımızın sahibi olarak oluşturulur ve programımıza hesaba yazma ve verileri başlatma yeteneği verir.

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_initialize_counter fonksiyonunu güncelleyin:

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

Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.

Artırma işleyicisini uygulama

Şimdi mevcut bir sayacı artıran işleyiciyi uygulayalım. Bu instruction:

  • counter_account için hesap data alanını okur
  • Bunu bir CounterAccount struct'ına deserialize eder
  • count alanını 1 artırır
  • CounterAccount struct'ını hesabın data alanına geri serialize eder

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_increment_counter fonksiyonunu güncelleyin:

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

Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.

Tamamlanmış program

Tebrikler! Tüm Solana programlarının paylaştığı temel yapıyı gösteren eksiksiz bir Solana programı oluşturdunuz:

  • Entrypoint: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun instruction işleyicilerine yönlendirir
  • Instruction işleme: Instructionları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
  • Durum yönetimi: Hesap veri yapılarını tanımlar ve bunların durumunu program sahipli hesaplarda yönetir
  • Cross program invocation (CPI): Yeni program sahipli hesaplar oluşturmak için System Program'ı çağırır

Bir sonraki adım, her şeyin doğru çalıştığından emin olmak için programı test etmektir.

Yeni bir program oluşturma

İlk olarak, Solana programımız için yeni bir Rust projesi oluşturalım.

Terminal
$
cargo new counter_program --lib
$
cd counter_program

Varsayılan src/lib.rs ve Cargo.toml dosyalarını görmelisiniz.

Cargo.toml dosyasındaki edition alanını 2021 olarak güncelleyin. Aksi takdirde, programı derlerken bir hatayla karşılaşabilirsiniz.

Bağımlılıkları ekleme

Şimdi bir Solana programı oluşturmak için gerekli bağımlılıkları ekleyelim. Temel SDK için solana-program ve serileştirme için borsh kütüphanelerine ihtiyacımız var.

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

Borsh kullanma zorunluluğu yoktur. Ancak, Solana programları için yaygın olarak kullanılan bir serileştirme kütüphanesidir.

Crate-type yapılandırması

Solana programları dinamik kütüphaneler olarak derlenmeli. Cargo'nun programı nasıl derleyeceğini yapılandırmak için [lib] bölümünü ekleyin.

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

Bu yapılandırmayı eklemezseniz, programı derlediğinizde target/deploy dizini oluşturulmayacaktır.

Program giriş noktasını ayarlama

Her Solana programının bir giriş noktası vardır; bu, program çağrıldığında çalıştırılan fonksiyondur. Program için ihtiyaç duyacağımız içe aktarmaları ekleyerek ve giriş noktasını ayarlayarak başlayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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 makrosu, input verisinin process_instruction fonksiyonunun parametrelerine deserileştirilmesini gerçekleştirir.

Bir Solana programı entrypoint fonksiyonu aşağıdaki imzaya sahiptir. Geliştiriciler entrypoint fonksiyonunun kendi uygulamalarını oluşturmakta özgürdür.

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

Program durumunu tanımlama

Şimdi sayaç hesaplarımızda saklanacak veri yapısını tanımlayalım. Bu, hesabın data alanında saklanacak veridir.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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

Talimat enum'ını tanımlama

Programımızın yürütebileceği talimatları tanımlayalım. Her varyantın farklı bir talimatı temsil ettiği bir enum kullanacağız.

Aşağıdaki kodu lib.rs dosyasına ekleyin:

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

Talimat deserializasyonunu uygulama

Şimdi instruction_data verisini (ham baytlar) CounterInstruction enum varyantlarımızdan birine deserialize etmemiz gerekiyor. Borsh try_from_slice metodu bu dönüşümü otomatik olarak gerçekleştirir.

process_instruction fonksiyonunu Borsh deserializasyonunu kullanacak şekilde güncelleyin:

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

Talimatları işleyicilere yönlendirme

Şimdi ana process_instruction fonksiyonunu, talimatları uygun işleyici fonksiyonlarına yönlendirecek şekilde güncelleyelim.

Bu yönlendirme deseni Solana programlarında yaygındır. instruction_data, talimatı temsil eden bir enum varyantına deserialize edilir, ardından uygun işleyici fonksiyonu çağrılır. Her işleyici fonksiyonu, o talimat için uygulamayı içerir.

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_instruction fonksiyonunu güncelleyin ve InitializeCounter ile IncrementCounter talimatları için işleyicileri ekleyin:

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

Başlatma işleyicisini uygulama

Yeni bir sayaç hesabı oluşturmak ve başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, bir Cross Program Invocation (CPI) kullanacağız, yani programımızdan başka bir programı çağıracağız.

Programımız, System Program'ın create_account instructionını çağırmak için bir CPI yapar. Yeni hesap, programımızın sahibi olarak oluşturulur ve programımıza hesaba yazma ve verileri başlatma yeteneği verir.

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_initialize_counter fonksiyonunu güncelleyin:

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

Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.

Artırma işleyicisini uygulama

Şimdi mevcut bir sayacı artıran işleyiciyi uygulayalım. Bu instruction:

  • counter_account için hesap data alanını okur
  • Bunu bir CounterAccount struct'ına deserialize eder
  • count alanını 1 artırır
  • CounterAccount struct'ını hesabın data alanına geri serialize eder

Aşağıdaki kodu lib.rs dosyasına ekleyerek process_increment_counter fonksiyonunu güncelleyin:

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

Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.

Tamamlanmış program

Tebrikler! Tüm Solana programlarının paylaştığı temel yapıyı gösteren eksiksiz bir Solana programı oluşturdunuz:

  • Entrypoint: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun instruction işleyicilerine yönlendirir
  • Instruction işleme: Instructionları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
  • Durum yönetimi: Hesap veri yapılarını tanımlar ve bunların durumunu program sahipli hesaplarda yönetir
  • Cross program invocation (CPI): Yeni program sahipli hesaplar oluşturmak için System Program'ı çağırır

Bir sonraki adım, her şeyin doğru çalıştığından emin olmak için programı test etmektir.

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

Bölüm 2: Programı test etme

Şimdi counter programımızı test edelim. Programları bir kümeye dağıtmadan test etmemizi sağlayan bir test framework'ü olan LiteSVM kullanacağız.

Test bağımlılıklarını ekleme

Öncelikle, test için gereken bağımlılıkları ekleyelim. Test için litesvm ve solana-sdk kullanacağız.

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

Test modülü oluşturma

Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve import'larla başlayacağız.

Aşağıdaki kodu lib.rs dosyasına, doğrudan program kodunun altına ekleyin:

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)] özniteliği, bu kodun yalnızca testler çalıştırılırken derlenmesini sağlar.

Test ortamını başlatma

LiteSVM ile test ortamını kuralım ve bir payer hesabını fonlayalım.

LiteSVM, Solana runtime ortamını simüle ederek programımızı gerçek bir kümeye dağıtmadan test etmemizi sağlar.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Programı yükleme

Şimdi programımızı oluşturup test ortamına yüklememiz gerekiyor. Programı oluşturmak için cargo build-sbf komutunu çalıştırın. Bu, target/deploy dizininde counter_program.so dosyasını oluşturacaktır.

Terminal
$
cargo build-sbf

Cargo.toml dosyasındaki edition değerinin 2021 olarak ayarlandığından emin olun.

Derleme işleminden sonra, programı yükleyebiliriz.

Test ortamına programı yüklemek için test_counter_program fonksiyonunu güncelleyin.

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

cargo build-sbf komutunu çalıştırarak .so dosyasını oluşturmanız gerekir. Test, derlenmiş programı yükler.

Test başlatma talimatı

Başlangıç değeriyle yeni bir sayaç hesabı oluşturarak başlatma talimatını test edelim.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Başlatmayı doğrula

Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulup oluşturulmadığını doğrulayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Artırma talimatını test et

Şimdi artırma talimatını test ederek sayaç değerinin düzgün şekilde güncellendiğinden emin olalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Sonuçları doğrula

Son olarak, güncellenen sayaç değerini kontrol ederek artırmanın doğru çalıştığını doğrulayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Testleri aşağıdaki komutla çalıştırın. --nocapture bayrağı testin çıktısını yazdırır.

Terminal
$
cargo test -- --nocapture

Beklenen çıktı:

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

Test bağımlılıklarını ekleme

Öncelikle, test için gereken bağımlılıkları ekleyelim. Test için litesvm ve solana-sdk kullanacağız.

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

Test modülü oluşturma

Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve import'larla başlayacağız.

Aşağıdaki kodu lib.rs dosyasına, doğrudan program kodunun altına ekleyin:

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)] özniteliği, bu kodun yalnızca testler çalıştırılırken derlenmesini sağlar.

Test ortamını başlatma

LiteSVM ile test ortamını kuralım ve bir payer hesabını fonlayalım.

LiteSVM, Solana runtime ortamını simüle ederek programımızı gerçek bir kümeye dağıtmadan test etmemizi sağlar.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Programı yükleme

Şimdi programımızı oluşturup test ortamına yüklememiz gerekiyor. Programı oluşturmak için cargo build-sbf komutunu çalıştırın. Bu, target/deploy dizininde counter_program.so dosyasını oluşturacaktır.

Terminal
$
cargo build-sbf

Cargo.toml dosyasındaki edition değerinin 2021 olarak ayarlandığından emin olun.

Derleme işleminden sonra, programı yükleyebiliriz.

Test ortamına programı yüklemek için test_counter_program fonksiyonunu güncelleyin.

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

cargo build-sbf komutunu çalıştırarak .so dosyasını oluşturmanız gerekir. Test, derlenmiş programı yükler.

Test başlatma talimatı

Başlangıç değeriyle yeni bir sayaç hesabı oluşturarak başlatma talimatını test edelim.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Başlatmayı doğrula

Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulup oluşturulmadığını doğrulayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Artırma talimatını test et

Şimdi artırma talimatını test ederek sayaç değerinin düzgün şekilde güncellendiğinden emin olalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Sonuçları doğrula

Son olarak, güncellenen sayaç değerini kontrol ederek artırmanın doğru çalıştığını doğrulayalım.

Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu güncelleyin:

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

Testleri aşağıdaki komutla çalıştırın. --nocapture bayrağı testin çıktısını yazdırır.

Terminal
$
cargo test -- --nocapture

Beklenen çıktı:

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"

Bölüm 3: Programı Çağırma

Şimdi programı çağırmak için bir istemci betiği ekleyelim.

İstemci örneği oluşturma

Dağıtılmış programımızla etkileşime geçmek için bir Rust istemcisi oluşturalım.

Terminal
$
mkdir examples
$
touch examples/client.rs

Aşağıdaki yapılandırmayı Cargo.toml dosyasına ekleyin:

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

İstemci bağımlılıklarını yükleyin:

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

İstemci kodunu uygulama

Şimdi dağıtılmış programımızı çağıracak istemciyi uygulayalım.

Program kimliğinizi keypair dosyasından almak için aşağıdaki komutu çalıştırın:

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

İstemci kodunu examples/client.rs dosyasına ekleyin ve program_id değerini önceki komutun çıktısıyla değiştirin:

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

İstemci örneği oluşturma

Dağıtılmış programımızla etkileşime geçmek için bir Rust istemcisi oluşturalım.

Terminal
$
mkdir examples
$
touch examples/client.rs

Aşağıdaki yapılandırmayı Cargo.toml dosyasına ekleyin:

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

İstemci bağımlılıklarını yükleyin:

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

İstemci kodunu uygulama

Şimdi dağıtılmış programımızı çağıracak istemciyi uygulayalım.

Program kimliğinizi keypair dosyasından almak için aşağıdaki komutu çalıştırın:

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

İstemci kodunu examples/client.rs dosyasına ekleyin ve program_id değerini önceki komutun çıktısıyla değiştirin:

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"

Bölüm 4: Programı Dağıtma

Artık programımız ve istemcimiz hazır olduğuna göre, programı derleyelim, dağıtalım ve çağıralım.

Programı derleyin

Öncelikle programımızı derleyelim.

Terminal
$
cargo build-sbf

Bu komut programınızı derler ve target/deploy/ içinde iki önemli dosya oluşturur:

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

Aşağıdaki komutu çalıştırarak programınızın ID'sini görüntüleyebilirsiniz:

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

Örnek çıktı:

HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF

Yerel validator'ı başlatın

Geliştirme için yerel bir test validator'ı kullanacağız.

Öncelikle Solana CLI'yi localhost kullanacak şekilde yapılandırın:

Terminal
$
solana config set -ul

Örnek çıktı:

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

Şimdi ayrı bir terminalde test validator'ı başlatın:

Terminal
$
solana-test-validator

Programı dağıtın

Validator çalışırken, programınızı yerel kümeye dağıtın:

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

Örnek çıktı:

Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Signature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h

Program kimliğinizle solana program show komutunu kullanarak dağıtımı doğrulayabilirsiniz:

Terminal
$
solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF

Örnek çıktı:

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

İstemciyi çalıştırın

Yerel validator hala çalışırken, istemciyi çalıştırın:

Terminal
$
cargo run --example client

Beklenen çıktı:

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

Yerel validator çalışırken, çıktı işlem imzalarını kullanarak Solana Explorer üzerinde işlemleri görüntüleyebilirsiniz. Solana Explorer'daki kümenin "Custom RPC URL" olarak ayarlanması gerektiğini unutmayın; bu, solana-test-validator'nin çalıştığı http://localhost:8899 varsayılan değerine ayarlanır.

Is this page helpful?

İçindekiler

Sayfayı Düzenle

Yönetici

© 2026 Solana Vakfı.
Tüm hakları saklıdır.
Bağlanın