Rust Program Yapısı

Rust ile yazılan Solana programları, kodun nasıl düzenleneceği konusunda esneklik sağlayan minimum yapısal gereksinimlere sahiptir. Tek gereksinim, bir programın çalışmasının nereden başlayacağını tanımlayan bir entrypoint içermesidir.

Program Yapısı

Dosya yapısı için katı kurallar olmasa da, Solana programları genellikle yaygın bir modeli 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.

Örnek olarak Token Programı'na bakabilirsiniz.

Örnek Program

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

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

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

Bölüm 1: Programı Yazma

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ştur

Öncelikle, 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ı oluştururken bir hatayla karşılaşabilirsiniz.

Bağımlılıkları ekle

Ş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 ihtiyacımız var.

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

Borsh kullanmak zorunlu değildir. 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 derlenmelidir. Cargo'nun programı nasıl oluşturacağını yapılandırmak için [lib] bölümünü ekleyin.

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

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

Program giriş noktasını ayarla

Her Solana programının, program çağrıldığında çalıştırılan bir giriş noktası vardır. Program için ihtiyacımız olan 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 dönüştürülmesini sağlar.

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ımla

Ş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'unu 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 (ham baytlar) verisini CounterInstruction enum varyantlarımızdan birine dönüştürmemiz gerekiyor. Borsh'un try_from_slice metodu bu dönüşümü otomatik olarak gerçekleştirir.

Borsh deserializasyonunu kullanmak için process_instruction fonksiyonunu 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 talimatları uygun işleyici fonksiyonlara yönlendirmek için ana process_instruction fonksiyonunu güncelleyelim.

Bu yönlendirme modeli Solana programlarında yaygındır. instruction_data verisi, talimatı temsil eden bir enum varyantına dönüştürülür, ardından uygun işleyici fonksiyon çağrılır. Her işleyici fonksiyon, o talimat için uygulamayı içerir.

process_instruction fonksiyonunu güncelleyen ve InitializeCounter ve IncrementCounter talimatları için işleyiciler ekleyen aşağıdaki kodu lib.rs dosyasına 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şturup başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, programımızdan başka bir programı çağıran Cross Program Invocation (CPI) kullanacağız.

Programımız, System Program'ın create_account talimatını çağırmak için bir CPI yapar. Yeni hesap, programımız sahibi olarak oluşturulur ve bu da programımıza hesaba yazma ve veriyi 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 talimat 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 talimat:

  • Hesabın data alanından counter_account değerini okur
  • Bunu bir CounterAccount yapısına dönüştürür
  • count alanını 1 artırır
  • CounterAccount yapısını hesabın data alanına geri yazar

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 talimat 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:

  • Giriş noktası: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun talimat işleyicilerine yönlendirir
  • Talimat İşleme: Talimatları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
  • Durum Yönetimi: Hesap veri yapılarını tanımlar ve program tarafından sahip olunan hesaplardaki durumlarını 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ştur

Öncelikle, 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ı oluştururken bir hatayla karşılaşabilirsiniz.

Bağımlılıkları ekle

Ş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 ihtiyacımız var.

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

Borsh kullanmak zorunlu değildir. 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 derlenmelidir. Cargo'nun programı nasıl oluşturacağını yapılandırmak için [lib] bölümünü ekleyin.

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

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

Program giriş noktasını ayarla

Her Solana programının, program çağrıldığında çalıştırılan bir giriş noktası vardır. Program için ihtiyacımız olan 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 dönüştürülmesini sağlar.

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ımla

Ş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'unu 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 (ham baytlar) verisini CounterInstruction enum varyantlarımızdan birine dönüştürmemiz gerekiyor. Borsh'un try_from_slice metodu bu dönüşümü otomatik olarak gerçekleştirir.

Borsh deserializasyonunu kullanmak için process_instruction fonksiyonunu 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 talimatları uygun işleyici fonksiyonlara yönlendirmek için ana process_instruction fonksiyonunu güncelleyelim.

Bu yönlendirme modeli Solana programlarında yaygındır. instruction_data verisi, talimatı temsil eden bir enum varyantına dönüştürülür, ardından uygun işleyici fonksiyon çağrılır. Her işleyici fonksiyon, o talimat için uygulamayı içerir.

process_instruction fonksiyonunu güncelleyen ve InitializeCounter ve IncrementCounter talimatları için işleyiciler ekleyen aşağıdaki kodu lib.rs dosyasına 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şturup başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, programımızdan başka bir programı çağıran Cross Program Invocation (CPI) kullanacağız.

Programımız, System Program'ın create_account talimatını çağırmak için bir CPI yapar. Yeni hesap, programımız sahibi olarak oluşturulur ve bu da programımıza hesaba yazma ve veriyi 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 talimat 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 talimat:

  • Hesabın data alanından counter_account değerini okur
  • Bunu bir CounterAccount yapısına dönüştürür
  • count alanını 1 artırır
  • CounterAccount yapısını hesabın data alanına geri yazar

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 talimat 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:

  • Giriş noktası: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun talimat işleyicilerine yönlendirir
  • Talimat İşleme: Talimatları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
  • Durum Yönetimi: Hesap veri yapılarını tanımlar ve program tarafından sahip olunan hesaplardaki durumlarını 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 sayaç programımızı test edelim. Programları bir kümeye dağıtmadan test etmemizi sağlayan bir test çerçevesi 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ştur

Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve importlar ile 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şlat

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

LiteSVM, Solana çalışma zamanı 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ükle

Şimdi programımızı derleyip test ortamına yüklememiz gerekiyor. Programı derlemek 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 içindeki edition değerinin 2021 olarak ayarlandığından emin olun.

Derlemeden sonra, programı yükleyebiliriz.

Programı test ortamına 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");

Testleri çalıştırmadan önce .so dosyasını oluşturmak için cargo build-sbf komutunu çalıştırmalısınız. Test, derlenmiş programı yükler.

Başlatma talimatını test et

Başlangıç değeri olan 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ğrulama

Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulduğunu 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 etme

Şimdi artırma talimatını test edelim ve sayaç değerini düzgün şekilde güncellediğ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);

Son sonuçları doğrulama

Son olarak, güncellenmiş sayaç değerini kontrol ederek artırma işleminin 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ştur

Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve importlar ile 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şlat

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

LiteSVM, Solana çalışma zamanı 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ükle

Şimdi programımızı derleyip test ortamına yüklememiz gerekiyor. Programı derlemek 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 içindeki edition değerinin 2021 olarak ayarlandığından emin olun.

Derlemeden sonra, programı yükleyebiliriz.

Programı test ortamına 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");

Testleri çalıştırmadan önce .so dosyasını oluşturmak için cargo build-sbf komutunu çalıştırmalısınız. Test, derlenmiş programı yükler.

Başlatma talimatını test et

Başlangıç değeri olan 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ğrulama

Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulduğunu 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 etme

Şimdi artırma talimatını test edelim ve sayaç değerini düzgün şekilde güncellediğ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);

Son sonuçları doğrulama

Son olarak, güncellenmiş sayaç değerini kontrol ederek artırma işleminin 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şim kurmak 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.

Keypair dosyasından program kimliğinizi 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 kısmını ö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şim kurmak 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.

Keypair dosyasından program kimliğinizi 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 kısmını ö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ı derleme

Ö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.

Önce, 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 ID'niz ile 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ıdaki işlem imzalarını kullanarak işlemleri Solana Explorer üzerinden görüntüleyebilirsiniz. Solana Explorer'daki kümenin "Custom RPC URL" olarak ayarlanması gerektiğini unutmayın, bu varsayılan olarak validator'ın çalıştığı http://localhost:8899 adresine ayarlanır.

Is this page helpful?

İçindekiler

Sayfayı Düzenle