Rust
Test avec Mollusk

Test avec Mollusk

Fonctionnalités Avancées

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

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

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

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

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

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

rust
// 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 :

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

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

Remarque : ces exemples concernent le programme SPL-Token. Si vous souhaitez créer des comptes Mint et Token appartenant au programme Token2022, utilisez simplement mollusk_svm_programs_token::token2022::....

Pour créer un compte Associated Token nous pouvons utiliser la fonction create_account_for_associated_token_account() comme ceci :

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

Remarque : cet exemple concerne le programme SPL-Token. Si vous souhaitez créer un compte Associated Token appartenant au programme Token2022, utilisez simplement la fonction create_account_for_associated_token_2022_account.

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

rust
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 code

  • out_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 :

text
[[bench]]
name = "compute_units"
harness = false

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

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

Les appels système personnalisés fonctionnent au niveau de la VM, fournissant un accès direct au contexte d'invocation et à l'environnement d'exécution.

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 :

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

Ceci est un exemple d'un appel système personnalisé qui "brûle" simplement des CUs.

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écution

  • Arguments 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

C'est ainsi que tous les appels système sont créés en interne

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 :

rust
#[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 :

rust
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
            ],
        );
    }
}

Cet exemple démontre le test d'un appel système qui consomme des unités de calcul, en validant que le nombre exact d'unités demandées est consommé. La capacité à vérifier des données précises fait de Mollusk la meilleure façon de tester les appels système personnalisés avant l'implémentation.

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 :

rust
/// 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

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

rust
// 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 :

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

rust
/// 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 :

rust
#[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);
}
Blueshift © 2025Commit: e573eab