Mollusk
Mollusk es un entorno ligero de pruebas para probar programas de Solana. Proporciona una interfaz simple para probar instrucciones de programas de Solana en un entorno minimizado de la Máquina Virtual de Solana (SVM). Todas las cuentas de prueba deben definirse explícitamente, asegurando pruebas deterministas y repetibles.
Instalación
Añade mollusk-svm
como dependencia en Cargo.toml
:
$cargo add mollusk-svm --dev
[dev-dependencies]mollusk-svm = "0.5"
Para evaluar el uso de unidades de cómputo, añade mollusk-svm-bencher
como
dependencia en Cargo.toml
:
$cargo add mollusk-svm-bencher --dev
[dev-dependencies]mollusk-svm-bencher = "0.5"
Para usar el Token Program, el programa token2022 (Token Extensions), y el
Associated Token Program para pruebas con Mollusk, añade
mollusk-svm-programs-token
como dependencia en Cargo.toml
:
$cargo add mollusk-svm-programs-token --dev
[dev-dependencies]mollusk-svm-programs-token = "0.5"
Mollusk SVM
El siguiente ejemplo muestra una configuración mínima para probar un programa básico de Solana usando Mollusk.
Programa Hola Mundo
Este ejemplo demuestra cómo probar un programa básico de Solana usando Mollusk. El programa simplemente imprime "Hello, world!" en los registros del programa cuando se invoca.
Ejecutar cargo build-sbf
genera el programa compilado en
/target/deploy/<program_name>.so
.
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()]);}}
Para probar un programa de Solana con Mollusk:
- Crear una instancia de
Mollusk
- Inicializar Mollusk con un ID de programa y la ruta al programa compilado (archivo.so
) - Construir una instrucción - Crear una instrucción para invocar el programa
- Procesar y validar - Procesar la instrucción usando Mollusk y validar el resultado
#[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()]);}}
Para ejecutar la prueba, ejecuta cargo test
.
Cuando la prueba se ejecute correctamente, verás una salida similar a la siguiente:
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
La estructura
Mollusk
proporciona una interfaz simple para probar programas de Solana. Todos los
campos pueden ser manipulados a través de un puñado de métodos auxiliares, pero
los usuarios también pueden acceder y modificarlos directamente si desean más
control.
Para inicializar Mollusk con una instancia predeterminada, utiliza el método
Mollusk::default
.
// Default instance with no custom programslet mollusk = Mollusk::default();
Para inicializar Mollusk con un programa específico, utiliza el método
Mollusk::new
.
// 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");
Para añadir un programa a Mollusk, utiliza el método Mollusk::add_program
.
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(),);
Al proporcionar la ruta del archivo, no incluyas la extensión .so
. Por
ejemplo, "path/to/my_program"
es correcto, pero "path/to/my_program.so"
no
lo es.
Procesamiento de instrucciones
Mollusk proporciona cuatro métodos principales para procesar instrucciones:
Método | Descripción |
---|---|
process_instruction | Procesa una instrucción y devuelve el resultado. |
process_and_validate_instruction | Procesa una instrucción y realiza una serie de comprobaciones en el resultado, entrando en pánico si alguna comprobación falla. |
process_instruction_chain | Procesa múltiples instrucciones y devuelve el resultado. |
process_and_validate_instruction_chain | Procesa múltiples instrucciones y realiza una serie de comprobaciones en cada resultado, entrando en pánico si alguna comprobación falla. |
El
InstructionResult
contiene los detalles de una instrucción procesada.
Instrucción única
Utiliza el método process_instruction
para procesar una sola instrucción sin
comprobaciones en el resultado. Puedes validar manualmente los resultados
después del procesamiento.
pub fn process_instruction(&self,instruction: &Instruction,accounts: &[(Pubkey, Account)],) -> InstructionResult
El siguiente ejemplo procesa una instrucción de transferencia de SOL sin comprobaciones de validación.
Los ejemplos a continuación ejecutan Mollusk en la función main
con fines de
demostración. En la práctica, normalmente usarás Mollusk en un módulo de prueba
anotado con el atributo #[test]
.
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);}
Instrucción única con verificaciones
Usa el método process_and_validate_instruction
para procesar una sola
instrucción con verificaciones de validación. Este método generará un error si
alguna verificación falla.
pub fn process_and_validate_instruction(&self,instruction: &Instruction,accounts: &[(Pubkey, Account)],checks: &[Check],) -> InstructionResult
El siguiente ejemplo procesa una instrucción de transferencia de SOL con verificaciones de validación.
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);}
Múltiples instrucciones
Usa el método process_instruction_chain
para procesar múltiples instrucciones
secuencialmente sin verificaciones de validación.
pub fn process_instruction_chain(&self,instructions: &[Instruction],accounts: &[(Pubkey, Account)],) -> InstructionResult
El siguiente ejemplo procesa dos instrucciones de transferencia de SOL sin verificaciones de validación.
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);}
Múltiples instrucciones con verificaciones
Usa el método process_and_validate_instruction_chain
para procesar múltiples
instrucciones con verificaciones de validación después de cada instrucción. Cada
instrucción tiene su propio conjunto de verificaciones que deben pasar.
pub fn process_and_validate_instruction_chain(&self,instructions: &[(&Instruction, &[Check])],accounts: &[(Pubkey, Account)],) -> InstructionResult
El siguiente ejemplo procesa una cadena de dos instrucciones de transferencia de SOL con verificaciones de validación después de cada instrucción.
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);}
Comprobaciones de validación
Mollusk proporciona un conjunto de métodos auxiliares para verificar los resultados de una instrucción procesada.
use mollusk_svm::result::Check;
Utiliza los siguientes métodos para validar los resultados de las instrucciones:
// 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])
Utiliza lo siguiente para validar los estados de las cuentas:
// 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()
Estado persistente de la cuenta
El
MolluskContext
es un envoltorio alrededor de Mollusk
que mantiene el estado de la cuenta a
través de múltiples llamadas de instrucción mediante su account_store
. Los
métodos para procesar instrucciones son idénticos a Mollusk
.
A diferencia de Mollusk
, que requiere pasar accounts
a cada método (por
ejemplo process_instruction
), MolluskContext
gestiona las cuentas
internamente a través de su account_store
. Esto elimina la necesidad del
parámetro accounts
al procesar instrucciones.
Crea un account_store
utilizando el método with_context
:
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);
El siguiente ejemplo procesa dos instrucciones separadas de transferencia de SOL
con estado de cuenta persistente entre las instrucciones a través del
account_store
.
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);}
Sysvars de Mollusk
Mollusk proporciona una estructura personalizada
Sysvars
para modificar sus valores para pruebas.
Utiliza el método warp_to_slot
para actualizar el reloj sysvar y simular el
avance o retroceso en el tiempo a un slot específico.
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);}
El siguiente ejemplo muestra cómo modificar el sysvar de Mollusk directamente
accediendo al campo sysvars
para cambiar los parámetros de rent. Puedes
modificar otros valores de sysvar de la misma manera.
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));}
Evaluación comparativa de unidades de cómputo
MolluskComputeUnitBencher
realiza un seguimiento del uso de unidades de cómputo de las instrucciones de un
programa. Los resultados se escriben en un archivo markdown.
Requiere
mollusk-svm-bencher
como dependencia.
El siguiente ejemplo evalúa el uso de unidades de cómputo de una instrucción de transferencia de SOL.
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();}
Los resultados de la evaluación comparativa se escriben en el out_dir
especificado como un archivo markdown llamado compute_units.md
.
#### 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 - |
Pruebas del Token Program
Utiliza el paquete
mollusk-svm-programs-token
para añadir el Token Program, el programa token2022 (Token Extensions) y el
Associated Token Program a Mollusk para realizar pruebas.
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);
El siguiente ejemplo demuestra cómo probar una transferencia de token usando Mollusk.
El ejemplo a continuación define manualmente las cuentas de prueba con fines
de demostración. El paquete mollusk-svm-programs-token
también incluye
funciones auxiliares para crear las cuentas de mint y token.
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?