Mollusk
Mollusk, Solana programlarını test etmek için hafif bir test ortamıdır. Solana program talimatlarını küçültülmüş bir Solana Sanal Makinesi (SVM) ortamında test etmek için basit bir arayüz sağlar. Tüm test hesapları açıkça tanımlanmalıdır, bu da belirleyici ve tekrarlanabilir testler sağlar.
Kurulum
mollusk-svm
bağımlılığını Cargo.toml
dosyasına ekleyin:
$cargo add mollusk-svm --dev
[dev-dependencies]mollusk-svm = "0.5"
İşlem birimi kullanımını karşılaştırmak için, mollusk-svm-bencher
bağımlılığını Cargo.toml
dosyasına ekleyin:
$cargo add mollusk-svm-bencher --dev
[dev-dependencies]mollusk-svm-bencher = "0.5"
Mollusk ile test yapmak için Token Program, token2022 programı (Token
Extensions) ve Associated Token Program kullanmak için,
mollusk-svm-programs-token
bağımlılığını Cargo.toml
dosyasına ekleyin:
$cargo add mollusk-svm-programs-token --dev
[dev-dependencies]mollusk-svm-programs-token = "0.5"
Mollusk SVM
Aşağıdaki örnek, Mollusk kullanarak temel bir Solana programını test etmek için minimal bir kurulum göstermektedir.
Merhaba Dünya Programı
Bu örnek, Mollusk kullanarak temel bir Solana programını nasıl test edeceğinizi göstermektedir. Program çağrıldığında program günlüklerine basitçe "Hello, world!" yazdırır.
cargo build-sbf
çalıştırmak, derlenmiş programı
/target/deploy/<program_name>.so
konumunda oluşturur.
use solana_program::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,};entrypoint!(process_instruction);pub fn process_instruction(_program_id: &Pubkey,_accounts: &[AccountInfo],_instruction_data: &[u8],) -> ProgramResult {msg!("Hello, world!");Ok(())}#[cfg(test)]mod tests {use mollusk_svm::{result::Check, Mollusk};use solana_sdk::{instruction::Instruction, pubkey::Pubkey};#[test]fn test_hello_world() {let program_id = Pubkey::new_unique();let mollusk = Mollusk::new(&program_id, "target/deploy/hello_world");let instruction = Instruction::new_with_bytes(program_id, &[], vec![]);mollusk.process_and_validate_instruction(&instruction, &[], &[Check::success()]);}}
Mollusk ile bir Solana programını test etmek için:
- Bir
Mollusk
örneği oluşturun - Mollusk'u bir program kimliği ve derlenmiş programın yolu (.so
dosyası) ile başlatın - Bir talimat oluşturun - Programı çağırmak için bir talimat oluşturun
- İşleyin ve doğrulayın - Talimatı Mollusk kullanarak işleyin ve sonucu doğrulayın
#[cfg(test)]mod tests {use mollusk_svm::{result::Check, Mollusk};use solana_sdk::{instruction::Instruction, pubkey::Pubkey};#[test]fn test_hello_world() {let program_id = Pubkey::new_unique();let mollusk = Mollusk::new(&program_id, "target/deploy/hello_world");let instruction = Instruction::new_with_bytes(program_id, &[], vec![]);mollusk.process_and_validate_instruction(&instruction, &[], &[Check::success()]);}}
Testi çalıştırmak için cargo test
komutunu çalıştırın.
Test başarıyla çalıştığında, aşağıdakine benzer bir çıktı göreceksiniz:
running 1 test[2025-09-22T19:25:50.427685000Z DEBUG solana_runtime::message_processor::stable_log] Program 11157t3sqMV725NVRLrVQbAu98Jjfk1uCKehJnXXQs invoke [1][2025-09-22T19:25:50.429669000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Hello, world![2025-09-22T19:25:50.429690000Z DEBUG solana_runtime::message_processor::stable_log] Program 11157t3sqMV725NVRLrVQbAu98Jjfk1uCKehJnXXQs consumed 211 of 1400000 compute units[2025-09-22T19:25:50.429726000Z DEBUG solana_runtime::message_processor::stable_log] Program 11157t3sqMV725NVRLrVQbAu98Jjfk1uCKehJnXXQs successtest tests::test_hello_world ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02sDoc-tests hello_world
Mollusk
yapısı, Solana programlarını test etmek için basit bir arayüz sağlar. Tüm
alanlar bir avuç yardımcı metot aracılığıyla değiştirilebilir, ancak
kullanıcılar daha fazla kontrol isterlerse bunlara doğrudan erişebilir ve
değiştirebilirler.
Mollusk'u varsayılan bir örnek ile başlatmak için Mollusk::default
metodunu
kullanın.
// Default instance with no custom programslet mollusk = Mollusk::default();
Mollusk'u belirli bir programla başlatmak için Mollusk::new
metodunu kullanın.
// Initialize Mollusk with a specific program from a file pathlet program_id = Pubkey::new_unique();let mollusk = Mollusk::new(&program_id, "target/deploy/my_program");
Mollusk'a bir program eklemek için Mollusk::add_program
metodunu kullanın.
let mollusk = Mollusk::default();let program_id = Pubkey::new_unique();// Add a program to Molluskmollusk.add_program(&program_id,"target/deploy/my_program",&bpf_loader_upgradeable::id(),);
Dosya yolunu belirtirken, .so
uzantısını dahil etmeyin. Örneğin,
"path/to/my_program"
doğrudur, ancak "path/to/my_program.so"
değildir.
Talimatları işleme
Mollusk, talimatları işlemek için dört ana metot sağlar:
Metot | Açıklama |
---|---|
process_instruction | Bir talimatı işler ve sonucu döndürür. |
process_and_validate_instruction | Bir talimatı işler ve sonuç üzerinde bir dizi kontrol gerçekleştirir, herhangi bir kontrol başarısız olursa panik yapar. |
process_instruction_chain | Birden fazla talimatı işler ve sonucu döndürür. |
process_and_validate_instruction_chain | Birden fazla talimatı işler ve her sonuç üzerinde bir dizi kontrol gerçekleştirir, herhangi bir kontrol başarısız olursa panik yapar. |
InstructionResult
işlenen bir talimatın ayrıntılarını içerir.
Tek talimat
Sonuç üzerinde kontrol yapmadan tek bir talimatı işlemek için
process_instruction
metodunu kullanın. İşlemden sonra sonuçları manuel olarak
doğrulayabilirsiniz.
pub fn process_instruction(&self,instruction: &Instruction,accounts: &[(Pubkey, Account)],) -> InstructionResult
Aşağıdaki örnek, doğrulama kontrolleri olmadan bir SOL transfer talimatını işler.
Aşağıdaki örnekler, gösterim amacıyla Mollusk'u main
fonksiyonunda çalıştırır.
Uygulamada, Mollusk'u genellikle #[test]
özniteliği ile işaretlenmiş bir test
modülünde kullanacaksınız.
use {mollusk_svm::Mollusk,solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},};fn main() {// Initialize Mollusklet mollusk = Mollusk::default();// Set up accountslet sender = Pubkey::new_unique();let recipient = Pubkey::new_unique();let initial_lamports = 1_000_000;let transfer_amount = 250_000;// Create transfer instructionlet instruction = system_instruction::transfer(&sender, &recipient, transfer_amount);// Define initial account stateslet accounts = vec![(sender,Account {lamports: initial_lamports,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(recipient,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),];// Process the instructionlet result = mollusk.process_instruction(&instruction, &accounts);println!("{:#?}", result);// Check the resultassert!(result.program_result.is_ok());assert_eq!(result.get_account(&sender).unwrap().lamports, 750_000);assert_eq!(result.get_account(&recipient).unwrap().lamports, 250_000);}
Kontrollü Tek Talimat
Doğrulama kontrolleriyle tek bir talimatı işlemek için
process_and_validate_instruction
yöntemini kullanın. Herhangi bir kontrol
başarısız olursa bu yöntem panic verecektir.
pub fn process_and_validate_instruction(&self,instruction: &Instruction,accounts: &[(Pubkey, Account)],checks: &[Check],) -> InstructionResult
Aşağıdaki örnek, doğrulama kontrolleriyle bir SOL transfer talimatını işler.
use {mollusk_svm::{Mollusk, result::Check},solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},};fn main() {let mollusk = Mollusk::default();let sender = Pubkey::new_unique();let recipient = Pubkey::new_unique();let initial_lamports = 1_000_000;let transfer_amount = 250_000;let instruction = system_instruction::transfer(&sender, &recipient, transfer_amount);let accounts = vec![(sender,Account {lamports: initial_lamports,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(recipient,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),];// Define validation checkslet checks = vec![Check::success(),Check::account(&sender).lamports(750_000).build(),Check::account(&recipient).lamports(250_000).build(),];// Process and validate (will panic if any check fails)let result = mollusk.process_and_validate_instruction(&instruction, &accounts, &checks);println!("{:#?}", result);}
Çoklu Talimatlar
Doğrulama kontrolleri olmadan birden fazla talimatı sırayla işlemek için
process_instruction_chain
yöntemini kullanın.
pub fn process_instruction_chain(&self,instructions: &[Instruction],accounts: &[(Pubkey, Account)],) -> InstructionResult
Aşağıdaki örnek, doğrulama kontrolleri olmadan iki SOL transfer talimatını işler.
use {mollusk_svm::Mollusk,solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},};fn main() {let mollusk = Mollusk::default();// Set up accountslet alice = Pubkey::new_unique();let bob = Pubkey::new_unique();let charlie = Pubkey::new_unique();let initial_lamports = 1_000_000;// Create chain of transferslet instructions = vec![system_instruction::transfer(&alice, &bob, 300_000), // Alice -> Bobsystem_instruction::transfer(&bob, &charlie, 100_000), // Bob -> Charlie];let accounts = vec![(alice,Account {lamports: initial_lamports,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(bob,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(charlie,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),];// Process the instruction chainlet result = mollusk.process_instruction_chain(&instructions, &accounts);println!("{:#?}", result);// Final balances: Alice=700K, Bob=200K, Charlie=100Kassert_eq!(result.get_account(&alice).unwrap().lamports, 700_000);assert_eq!(result.get_account(&bob).unwrap().lamports, 200_000);assert_eq!(result.get_account(&charlie).unwrap().lamports, 100_000);}
Kontrollü Çoklu Talimatlar
Her talimatın ardından doğrulama kontrolleriyle birden fazla talimatı işlemek
için process_and_validate_instruction_chain
yöntemini kullanın. Her talimatın
geçmesi gereken kendi kontrol seti vardır.
pub fn process_and_validate_instruction_chain(&self,instructions: &[(&Instruction, &[Check])],accounts: &[(Pubkey, Account)],) -> InstructionResult
Aşağıdaki örnek, her talimatın ardından doğrulama kontrolleri yapılan iki SOL transfer talimatından oluşan bir zinciri işler.
use {mollusk_svm::{result::Check, Mollusk},solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},};fn main() {let mollusk = Mollusk::default();// Create accountslet alice = Pubkey::new_unique();let bob = Pubkey::new_unique();let charlie = Pubkey::new_unique();let initial_lamports = 1_000_000;// Create transfer instructionslet transfer1 = system_instruction::transfer(&alice, &bob, 300_000);let transfer2 = system_instruction::transfer(&bob, &charlie, 100_000);// Initial accountslet accounts = vec![(alice,Account {lamports: initial_lamports,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(bob,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(charlie,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),];// Define checks for each instructionlet checks_after_transfer1 = vec![Check::success(),Check::account(&alice).lamports(700_000) // 1M - 300K.build(),Check::account(&bob).lamports(300_000) // 0 + 300K.build(),Check::account(&charlie).lamports(0) // Unchanged.build(),];let checks_after_transfer2 = vec![Check::success(),Check::account(&alice).lamports(700_000) // Unchanged from previous.build(),Check::account(&bob).lamports(200_000) // 300K - 100K.build(),Check::account(&charlie).lamports(100_000) // 0 + 100K.build(),];// Process with validation at each steplet instruction_and_checks = [(&transfer1, checks_after_transfer1.as_slice()),(&transfer2, checks_after_transfer2.as_slice()),];// Execute chain (panics if any check fails)let result = mollusk.process_and_validate_instruction_chain(&instruction_and_checks, &accounts);println!("{:#?}", result);}
Doğrulama Kontrolleri
Mollusk, bir işlemin sonuçlarını kontrol etmek için bir dizi yardımcı metot sağlar.
use mollusk_svm::result::Check;
İşlem sonuçlarını doğrulamak için aşağıdaki metotları kullanın:
// Program execution succeededCheck::success()// Program returned specific errorCheck::err(ProgramError::InvalidArgument)// Instruction level errorCheck::instruction_err(InstructionError::InsufficientFunds)// Check with specific program resultCheck::program_result(ProgramResult::Success)// Compute units consumedCheck::compute_units(1000)// Execution timeCheck::time(100)// Return data from instruction executionCheck::return_data(&[1, 2, 3, 4])
Hesap durumlarını doğrulamak için aşağıdakileri kullanın:
// Single account validationCheck::account(&pubkey).lamports(1_000_000) // Exact lamports.owner(&program_id) // Account owner.data(&expected_data) // Exact data match.data_slice(8, &[1, 2, 3]) // Partial data match at offset.executable(false) // Executable flag.space(100) // Account data size.closed() // Account is closed (0 lamports).rent_exempt() // Account is rent-exempt.build()// Check all accounts are rent exemptCheck::all_rent_exempt()
Kalıcı Hesap Durumu
MolluskContext
,
Mollusk
etrafında bir sarmalayıcıdır ve account_store
aracılığıyla birden
fazla işlem çağrısı boyunca hesap durumunu korur. İşlemleri işleme metotları
Mollusk
ile aynıdır.
Her metoda accounts
geçirilmesini gerektiren Mollusk
aksine (örn.
process_instruction
), MolluskContext
hesapları account_store
aracılığıyla
dahili olarak yönetir. Bu, işlemleri işlerken accounts
parametresine olan
ihtiyacı ortadan kaldırır.
with_context
metodunu kullanarak bir account_store
oluşturun:
use std::collections::HashMap;use solana_sdk::{account::Account, pubkey::Pubkey, system_program};use mollusk_svm::Mollusk;let mollusk = Mollusk::default();let account_address = Pubkey::new_unique();let mut account_store = HashMap::new();account_store.insert(account_address,Account {lamports: 1_000_000,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},);let context = mollusk.with_context(account_store);
Aşağıdaki örnek, account_store
aracılığıyla işlemler arasında kalıcı hesap
durumu ile iki ayrı SOL transfer işlemini işler.
use {mollusk_svm::Mollusk,solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},std::collections::HashMap,};fn main() {// Initialize Mollusklet mollusk = Mollusk::default();// Create accountslet sender = Pubkey::new_unique();let recipient = Pubkey::new_unique();// Create account store with initial balanceslet mut account_store = HashMap::new();account_store.insert(sender,Account {lamports: 1_000_000,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},);account_store.insert(recipient,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},);// Create a stateful contextlet context = mollusk.with_context(account_store);// First transfer: 200,000 lamportslet instruction1 = system_instruction::transfer(&sender, &recipient, 200_000);context.process_instruction(&instruction1);// Second transfer: 100,000 lamports (state persists from first transfer)let instruction2 = system_instruction::transfer(&sender, &recipient, 100_000);context.process_instruction(&instruction2);// Check final balanceslet store = context.account_store.borrow();let sender_account = store.get(&sender).unwrap();let recipient_account = store.get(&recipient).unwrap();println!("Sender: {:#?}", sender_account);println!("Recipient: {:#?}", recipient_account);}
Mollusk Sysvars
Mollusk, test için değerlerini değiştirmek üzere özel bir
Sysvars
yapısı sağlar.
Belirli bir slot'a ileri veya geri giderek zaman simülasyonu yapmak için sysvar
saatini güncellemek üzere warp_to_slot
metodunu kullanın.
use mollusk_svm::Mollusk;fn main() {// Initialize Mollusklet mut mollusk = Mollusk::default();// Show initial slotprintln!("Initial slot: {}", mollusk.sysvars.clock.slot);// Warp to slot 1000mollusk.warp_to_slot(100);println!("After warp: {}", mollusk.sysvars.clock.slot);// Warp to slot 10mollusk.warp_to_slot(10);println!("After second warp: {}", mollusk.sysvars.clock.slot);}
Aşağıdaki örnek, rent parametrelerini değiştirmek için sysvars
alanına
erişerek Mollusk sysvar'ı doğrudan nasıl değiştireceğinizi gösterir. Diğer
sysvar değerlerini de aynı şekilde değiştirebilirsiniz.
use {mollusk_svm::Mollusk, solana_sdk::rent::Rent};fn main() {let mut mollusk = Mollusk::default();// Show default rentprintln!("Default rent exemption for 1000 bytes: {} lamports",mollusk.sysvars.rent.minimum_balance(1000));// Customize rent parametersmollusk.sysvars.rent = Rent {lamports_per_byte_year: 1,exemption_threshold: 1.0,burn_percent: 0,};// Show custom rentprintln!("Custom rent exemption for 1000 bytes: {} lamports",mollusk.sysvars.rent.minimum_balance(1000));}
İşlem Birimi Karşılaştırması
MolluskComputeUnitBencher
bir programın talimatlarının işlem birimi kullanımını takip eder. Sonuçlar bir
markdown dosyasına yazılır.
Bağımlılık olarak
mollusk-svm-bencher
gerektirir.
Aşağıdaki örnek, bir SOL transfer talimatının işlem birimi kullanımını karşılaştırır.
use {mollusk_svm::Mollusk,mollusk_svm_bencher::MolluskComputeUnitBencher,solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},};fn main() {// Initialize Mollusklet mollusk = Mollusk::default();// Create test accountslet sender = Pubkey::new_unique();let receiver = Pubkey::new_unique();// Transfer instructionlet transfer = system_instruction::transfer(&sender, &receiver, 100_000);let accounts = vec![(sender,Account {lamports: 1_000_000,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),(receiver,Account {lamports: 0,data: vec![],owner: system_program::id(),executable: false,rent_epoch: 0,},),];// Run benchmarkMolluskComputeUnitBencher::new(mollusk).bench(("transfer", &transfer, &accounts)).must_pass(true).out_dir("./target/benches").execute();}
Karşılaştırma sonuçları, belirtilen out_dir
dizinine compute_units.md
adlı
bir markdown dosyası olarak yazılır.
#### 2025-09-19 22:28:53.691839 UTCSolana CLI Version: solana-cli 2.2.20 (src:dabc99a5; feat:3073396398,client:Agave)| Name | CUs | Delta || -------- | --- | ------- || transfer | 150 | - new - |
Token Program Testi
Test için Mollusk'a Token Program, token2022 programı (Token Extensions) ve
Associated Token Program eklemek için
mollusk-svm-programs-token
paketini kullanın.
use {mollusk_svm::Mollusk,mollusk_svm_programs_token::{associated_token, token, token2022},};let mut mollusk = Mollusk::default();// Add SPL Token Programtoken::add_program(&mut mollusk);// Add SPL Token-2022 Programtoken2022::add_program(&mut mollusk);// Add Associated Token Account Programassociated_token::add_program(&mut mollusk);
Aşağıdaki örnek, Mollusk kullanarak bir token transferinin test edilmesini göstermektedir.
Aşağıdaki örnek, gösterim amacıyla test hesaplarını manuel olarak tanımlar.
mollusk-svm-programs-token
ayrıca mint ve token hesaplarını oluşturmak için
yardımcı fonksiyonlar da içerir.
use {mollusk_svm::{result::Check, Mollusk},mollusk_svm_programs_token::token,solana_sdk::{account::Account, program_pack::Pack, pubkey::Pubkey},spl_token::{instruction::transfer_checked,state::{Account as TokenAccount, Mint},},};fn main() {// Initialize Mollusk with Token programlet mut mollusk = Mollusk::default();token::add_program(&mut mollusk);// Create account keyslet mint = Pubkey::new_unique();let source = Pubkey::new_unique();let destination = Pubkey::new_unique();let authority = Pubkey::new_unique();// Token configurationlet decimals = 6;let transfer_amount = 1_000_000; // 1 token with 6 decimalslet initial_balance = 10_000_000; // 10 tokens// Calculate rent-exempt minimumslet mint_rent = mollusk.sysvars.rent.minimum_balance(Mint::LEN);let account_rent = mollusk.sysvars.rent.minimum_balance(TokenAccount::LEN);// Create mint accountlet mut mint_data = vec![0u8; Mint::LEN];Mint::pack(Mint {mint_authority: Some(authority).into(),supply: initial_balance,decimals,is_initialized: true,freeze_authority: None.into(),},&mut mint_data,).unwrap();// Create source token accountlet mut source_data = vec![0u8; TokenAccount::LEN];TokenAccount::pack(TokenAccount {mint,owner: authority,amount: initial_balance,delegate: None.into(),state: spl_token::state::AccountState::Initialized,is_native: None.into(),delegated_amount: 0,close_authority: None.into(),},&mut source_data,).unwrap();// Create destination token accountlet mut destination_data = vec![0u8; TokenAccount::LEN];TokenAccount::pack(TokenAccount {mint,owner: Pubkey::new_unique(),amount: 0,delegate: None.into(),state: spl_token::state::AccountState::Initialized,is_native: None.into(),delegated_amount: 0,close_authority: None.into(),},&mut destination_data,).unwrap();// Setup accounts for transfer_checkedlet accounts = vec![(source,Account {lamports: account_rent,data: source_data,owner: token::ID,executable: false,rent_epoch: 0,},),(mint,Account {lamports: mint_rent,data: mint_data,owner: token::ID,executable: false,rent_epoch: 0,},),(destination,Account {lamports: account_rent,data: destination_data,owner: token::ID,executable: false,rent_epoch: 0,},),(authority,Account {lamports: 1_000_000,data: vec![],owner: Pubkey::default(),executable: false,rent_epoch: 0,},),];// Create transfer_checked instructionlet instruction = transfer_checked(&token::ID,&source,&mint,&destination,&authority,&[],transfer_amount,decimals,).unwrap();// Expected balances after transferlet expected_source_balance = (initial_balance - transfer_amount).to_le_bytes();let expected_dest_balance = transfer_amount.to_le_bytes();// Define validation checkslet checks = vec![Check::success(),Check::account(&source).data_slice(64, &expected_source_balance) // Token amount is at offset 64.build(),Check::account(&destination).data_slice(64, &expected_dest_balance).build(),];// Process and validate the instructionlet result = mollusk.process_and_validate_instruction(&instruction, &accounts, &checks);println!("{:#?}", result);// Deserialize token account datalet source_account = result.get_account(&source).unwrap();let source_token = TokenAccount::unpack(&source_account.data).unwrap();println!("Source Token Account: {:#?}", source_token);let destination_account = result.get_account(&destination).unwrap();let dest_token = TokenAccount::unpack(&destination_account.data).unwrap();println!("Destination Token Account: {:#?}", dest_token);}
Is this page helpful?