Fonctionnalités avancées
Mollusk offre des options d'initialisation flexibles pour s'adapter à différents scénarios de test. Vous pouvez créer des instances préchargées avec votre programme ou commencer avec un environnement minimal et ajouter des composants selon vos besoins.
Lors du test d'un programme spécifique, initialisez Mollusk avec votre programme préchargé :
use mollusk_svm::Mollusk;
use solana_sdk::pubkey::Pubkey;
const ID: Pubkey = solana_sdk::pubkey!("22222222222222222222222222222222222222222222");
#[test]
fn test() {
let mollusk = Mollusk::new(&ID, "target/deploy/program");
}Cette approche charge automatiquement votre programme compilé et le rend disponible pour les tests, simplifiant ainsi le processus de configuration pour les suites de tests spécifiques à un programme.
Pour des scénarios de test plus larges ou lorsque vous devez ajouter des programmes dynamiquement, commencez avec l'instance par défaut :
use mollusk_svm::Mollusk;
#[test]
fn test() {
// System Program, ...
let mollusk = Mollusk::default();
}L'instance par défaut inclut des programmes intégrés essentiels comme le System Program, fournissant une base pour la plupart des opérations Solana sans la surcharge des programmes dont vous n'avez pas besoin.
Lorsque vos tests nécessitent le System Program, Mollusk fournit un assistant pratique pour générer les références de compte nécessaires :
let (system_program, system_program_account) = keyed_account_for_system_program();Pour reproduire la fonctionnalité de chargement de programme ou charger des programmes personnalisés qui ne sont pas présents par défaut, vous pouvez utiliser ces assistants :
use mollusk_svm::Mollusk;
use mollusk_svm::program::create_program_account_loader_v3;
#[test]
fn test() {
let mut mollusk = Mollusk::default();
// Get the account that you need
let program = &ID; // ID of the program we're trying to load into mollusk
let program_account = create_program_account_loader_v3(&ID);
// Load the program into your mollusk instance
mollusk.add_program(
&ID,
"target/deploy/program",
&mollusk_svm::program::loader_keys::LOADER_V3
);
}Token Program
Les assistants de programme de jetons de Mollusk simplifient considérablement les scénarios de test impliquant des jetons SPL. La crate mollusk-svm-programs-token fournit un support préconfiguré pour les programmes Token, Token2022 et Associated Token.
Comptes de programme
Après avoir inclus la crate d'assistant de jetons, ajoutez les programmes de jetons spécifiques dont vos tests ont besoin :
use mollusk_svm::Mollusk;
#[test]
fn test() {
let mut mollusk = Mollusk::default();
// Add the SPL Token Program
mollusk_svm_programs_token::token::add_program(&mut mollusk);
// Add the Token2022 Program
mollusk_svm_programs_token::token2022::add_program(&mut mollusk);
// Add the Associated Token Program
mollusk_svm_programs_token::associated_token::add_program(&mut mollusk);
}Et créez les références de compte nécessaires pour vos scénarios de test :
// SPL Token Program
let (token_program, token_program_account) =
mollusk_svm_programs_token::token::keyed_account();
// Token2022 Program
let (token2022_program, token2022_program_account) =
mollusk_svm_programs_token::token2022::keyed_account();
// Associated Token Program
let (associated_token_program, associated_token_program_account) =
mollusk_svm_programs_token::associated_token::keyed_account();Ces assistants garantissent que les tests liés aux jetons ont accès aux comptes de programme corrects avec une configuration appropriée, permettant des tests complets des opérations de jetons sans configuration manuelle du programme.
Comptes d'état
Pendant nos tests, nous pourrions avoir besoin d'un compte Mint, Token ou Associated Token qui a déjà été initialisé. Heureusement pour nous, Mollusk dispose de quelques assistants pratiques.
Pour créer un compte Mint nous pouvons utiliser la fonction create_account_for_mint() comme ceci :
use spl_token::state::Mint;
use mollusk_svm_programs_token::token::create_account_for_mint;
let mint_data = Mint {
mint_authority: Pubkey::new_unique(),
supply: 10_000_000_000,
decimals: 6,
is_initialized: true,
freeze_authority: None,
};
let mint_account = create_account_for_mint(mint_data)Pour créer un compte Token nous pouvons utiliser la fonction create_account_for_token_account() comme ceci :
use spl_token::state::{TokenAccount, AccountState};
use mollusk_svm_programs_token::token::create_account_for_token_account;
let token_data = TokenAccount {
mint: Pubkey::new_unique(),
owner: Pubkey::new_unique(),
amount: 1_000_000,
delegate: None,
state: AccountState::Initialized,
is_native: None,
delegated_amount: 0,
close_authority: None,
};
let token_account = create_account_for_token_account(token_data)Pour créer un compte Associated Token nous pouvons utiliser la fonction create_account_for_associated_token_account() comme ceci :
use spl_token::state::{TokenAccount, AccountState};
use mollusk_svm_programs_token::associated_token::create_account_for_associated_token_account;
let token_data = TokenAccount {
mint: Pubkey::new_unique(),
owner: Pubkey::new_unique(),
amount: 1_000_000,
delegate: None,
state: AccountState::Initialized,
is_native: None,
delegated_amount: 0,
close_authority: None,
};
let associated_token_account = create_account_for_associated_token_account(token_data)Évaluation comparative des unités de calcul
Mollusk inclut un système dédié d'évaluation comparative des unités de calcul qui permet de mesurer et de suivre avec précision l'efficacité computationnelle de votre programme. Le MolluskComputeUnitBencher fournit une API simplifiée pour créer des benchmarks complets qui surveillent la consommation d'unités de calcul dans différents scénarios d'instructions.
Ce système d'évaluation comparative est particulièrement précieux pour l'optimisation des performances, car il génère des rapports détaillés montrant à la fois l'utilisation actuelle des unités de calcul et les écarts par rapport aux exécutions précédentes.
Cela vous permet de voir immédiatement l'impact des modifications de code sur l'efficacité de votre programme, vous aidant à optimiser les goulots d'étranglement critiques de performance.
Le système d'évaluation s'intègre parfaitement à votre configuration de test Mollusk existante :
use {
mollusk_svm_bencher::MolluskComputeUnitBencher,
mollusk_svm::Mollusk,
/* ... */
};
// Optionally disable logging.
solana_logger::setup_with("");
/* Instruction & accounts setup ... */
let mollusk = Mollusk::new(&program_id, "my_program");
MolluskComputeUnitBencher::new(mollusk)
.bench(("bench0", &instruction0, &accounts0))
.bench(("bench1", &instruction1, &accounts1))
.bench(("bench2", &instruction2, &accounts2))
.bench(("bench3", &instruction3, &accounts3))
.must_pass(true)
.out_dir("../target/benches")
.execute();Options de configuration
Le système d'évaluation offre plusieurs options de configuration :
must_pass(true): déclenche une panique si un benchmark ne s'exécute pas correctement, garantissant que vos benchmarks restent valides lors des modifications de codeout_dir("../target/benches"): spécifie où le rapport markdown sera généré, permettant l'intégration avec les systèmes CI/CD et les flux de travail de documentation
Intégration avec Cargo
Pour exécuter des benchmarks en utilisant cargo bench, ajoutez une configuration de benchmark à votre Cargo.toml :
[[bench]]
name = "compute_units"
harness = falseRapports de benchmark
Le benchmarker génère des rapports en markdown qui fournissent à la fois des métriques de performance actuelles et des comparaisons historiques :
| Name | CUs | Delta |
|--------|-------|--------|
| bench0 | 450 | -- |
| bench1 | 579 | -129 |
| bench2 | 1,204 | +754 |
| bench3 | 2,811 | +2,361 |Le format du rapport comprend :
Nom : L'identifiant de benchmark que vous avez spécifié
CUs : Consommation actuelle d'unités de calcul pour ce scénario
Delta : Changement par rapport à l'exécution précédente du benchmark (positif indique une utilisation accrue, négatif indique une optimisation)
Custom Syscalls
Mollusk prend en charge la création et le test d'appels système personnalisés, vous permettant d'étendre la machine virtuelle Solana avec des fonctionnalités spécialisées pour les scénarios de test.
Cette capacité est particulièrement précieuse pour tester la création de nouveaux appels système qui peuvent être ajoutés via SIMD en simulant des comportements d'exécution spécifiques, ou en créant des environnements contrôlés pour les tests.
Définition des appels système personnalisés
Les appels système personnalisés sont définis à l'aide de la macro declare_builtin_function!, qui crée un appel système pouvant être enregistré dans l'environnement d'exécution de Mollusk :
use {
mollusk_svm::{result::Check, Mollusk},
solana_instruction::Instruction,
solana_program_runtime::{
invoke_context::InvokeContext,
solana_sbpf::{declare_builtin_function, memory_region::MemoryMapping},
},
solana_pubkey::Pubkey,
};
declare_builtin_function!(
/// A custom syscall to burn compute units for testing
SyscallBurnCus,
fn rust(
invoke_context: &mut InvokeContext,
to_burn: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_arg5: u64,
_memory_mapping: &mut MemoryMapping,
) -> Result<u64, Box<dyn std::error::Error>> {
// Consume the specified number of compute units
invoke_context.consume_checked(to_burn)?;
Ok(0)
}
);La signature de la fonction d'appel système suit un modèle spécifique :
invoke_context: Fournit un accès au contexte d'exécution et à l'état d'exécutionArguments 1-5 : Jusqu'à cinq arguments de 64 bits peuvent être passés depuis le programme
memory_mapping : Fournit un accès à l'espace mémoire du programme
Valeur de retour : Un
Result<u64, Box<dyn std::error::Error>>indiquant le succès ou l'échec
Enregistrement des appels système personnalisés
Une fois définis, les appels système personnalisés doivent être enregistrés dans l'environnement d'exécution du programme Mollusk avant de pouvoir être utilisés :
#[test]
fn test_custom_syscall() {
std::env::set_var("SBF_OUT_DIR", "../target/deploy");
let program_id = Pubkey::new_unique();
let mollusk = {
let mut mollusk = Mollusk::default();
// Register the custom syscall with a specific name
mollusk
.program_cache
.program_runtime_environment
.register_function("sol_burn_cus", SyscallBurnCus::vm)
.unwrap();
// Add your program that uses the custom syscall
mollusk.add_program(
&program_id,
"test_program_custom_syscall",
&mollusk_svm::program::loader_keys::LOADER_V3,
);
mollusk
};
}L'appel système est enregistré avec un nom ("sol_burn_cus" dans cet exemple) que votre programme peut référencer lors de l'exécution de l'appel système.
Test du comportement des appels système personnalisés
Les appels système personnalisés peuvent être testés comme n'importe quelle autre fonctionnalité du programme, avec l'avantage supplémentaire d'un contrôle précis sur leur comportement :
fn instruction_burn_cus(program_id: &Pubkey, to_burn: u64) -> Instruction {
Instruction::new_with_bytes(*program_id, &to_burn.to_le_bytes(), vec![])
}
#[test]
fn test_custom_syscall() {
// ... mollusk setup ...
// Establish baseline compute unit usage
let base_cus = mollusk
.process_and_validate_instruction(
&instruction_burn_cus(&program_id, 0),
&[],
&[Check::success()],
)
.compute_units_consumed;
// Test different compute unit consumption levels
for to_burn in [100, 1_000, 10_000] {
mollusk.process_and_validate_instruction(
&instruction_burn_cus(&program_id, to_burn),
&[],
&[
Check::success(),
Check::compute_units(base_cus + to_burn), // Verify exact CU consumption
],
);
}
}Configuration Methods
Mollusk fournit des options de configuration complètes qui vous permettent de personnaliser l'environnement d'exécution pour répondre à des exigences de test spécifiques, comme nous pouvons le voir dans Mollusk Context :
/// Instruction context fixture.
pub struct Context {
/// The compute budget to use for the simulation.
pub compute_budget: ComputeBudget,
/// The feature set to use for the simulation.
pub feature_set: FeatureSet,
/// The runtime sysvars to use for the simulation.
pub sysvars: Sysvars,
/// The program ID of the program being invoked.
pub program_id: Pubkey,
/// Accounts to pass to the instruction.
pub instruction_accounts: Vec<AccountMeta>,
/// The instruction data.
pub instruction_data: Vec<u8>,
/// Input accounts with state.
pub accounts: Vec<(Pubkey, Account)>,
}Ces méthodes de configuration permettent un contrôle précis des budgets de calcul, de la disponibilité des fonctionnalités et des variables système, rendant possible le test des programmes dans diverses conditions d'exécution.
Configuration de base
use mollusk_svm::Mollusk;
use solana_sdk::feature_set::FeatureSet;
#[test]
fn test() {
let mut mollusk = Mollusk::new(&program_id, "path/to/program.so");
// Configure compute budget for performance testing
mollusk.set_compute_budget(200_000);
// Configure feature set to enable/disable specific Solana features
mollusk.set_feature_set(FeatureSet::all_enabled());
// Sysvars are handled automatically but can be customized if needed
}Le budget de calcul détermine combien d'unités de calcul sont disponibles pour l'exécution du programme. C'est crucial pour tester les programmes qui approchent ou dépassent les limites de calcul :
// Test with standard compute budget
mollusk.set_compute_budget(200_000);L'ensemble des fonctionnalités de Solana contrôle quelles fonctionnalités de la blockchain sont actives pendant l'exécution du programme. Mollusk vous permet de configurer ces fonctionnalités pour tester la compatibilité à travers différents états du réseau :
use solana_sdk::feature_set::FeatureSet;
// Enable all features (latest functionality)
mollusk.set_feature_set(FeatureSet::all_enabled());
// All features disabled
mollusk.set_feature_set(FeatureSet::default());Pour une liste complète des fonctionnalités disponibles, consultez la documentation du crate agave-feature-set documentation, qui détaille toutes les fonctionnalités configurables de la blockchain et leurs implications.
Mollusk fournit un accès à toutes les variables système (sysvars) que les programmes peuvent interroger pendant l'exécution. Bien que celles-ci soient automatiquement configurées avec des valeurs par défaut raisonnables, vous pouvez les personnaliser pour des scénarios de test spécifiques :
/// Mollusk sysvars wrapper for easy manipulation
pub struct Sysvars {
pub clock: Clock, // Current slot, epoch, and timestamp
pub epoch_rewards: EpochRewards, // Epoch reward distribution info
pub epoch_schedule: EpochSchedule, // Epoch timing and slot configuration
pub last_restart_slot: LastRestartSlot, // Last validator restart information
pub rent: Rent, // Rent calculation parameters
pub slot_hashes: SlotHashes, // Recent slot hash history
pub stake_history: StakeHistory, // Historical stake activation data
}Vous pouvez personnaliser des variables système spécifiques pour tester la logique dépendante du temps, les calculs de loyer, ou d'autres comportements dépendants du système ou utiliser certains des assistants :
#[test]
fn test() {
let mut mollusk = Mollusk::new(&program_id, "path/to/program.so");
// Customize clock for time-based testing
mollusk.sysvars.clock.epoch = 10;
mollusk.sysvars.clock.unix_timestamp = 1234567890;
// Jump to Slot 1000
mollusk.warp_to_slot(1000);
}