Документація SolanaРозробка програмТестування програм

Mollusk

Mollusk — це легка тестова оболонка для тестування програм Solana. Вона надає простий інтерфейс для тестування інструкцій програм Solana у мінімізованому середовищі Solana Virtual Machine (SVM). Усі тестові облікові записи мають бути явно визначені, що забезпечує детерміновані та повторювані тести.

Встановлення

Додайте mollusk-svm як залежність у Cargo.toml:

Terminal
$
cargo add mollusk-svm --dev
Cargo.toml
[dev-dependencies]
mollusk-svm = "0.5"

Для оцінки використання обчислювальних одиниць додайте mollusk-svm-bencher як залежність у Cargo.toml:

Terminal
$
cargo add mollusk-svm-bencher --dev
Cargo.toml
[dev-dependencies]
mollusk-svm-bencher = "0.5"

Щоб використовувати Token Program, Token2022 Program (Token Extensions) та Associated Token Program для тестування з Mollusk, додайте mollusk-svm-programs-token як залежність у Cargo.toml:

Terminal
$
cargo add mollusk-svm-programs-token --dev
Cargo.toml
[dev-dependencies]
mollusk-svm-programs-token = "0.5"

Mollusk SVM

Наступний приклад показує мінімальну конфігурацію для тестування базової програми Solana за допомогою Mollusk.

Програма Hello World

Цей приклад демонструє, як тестувати базову програму Solana за допомогою Mollusk. Програма просто виводить "Hello, world!" у журнал програми при виклику.

Запуск cargo build-sbf генерує скомпільовану програму в /target/deploy/<program_name>.so.

src/lib.rs
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()]);
}
}

Для тестування програми Solana з Mollusk:

  1. Створіть екземпляр Mollusk - Ініціалізуйте Mollusk з ідентифікатором програми та шляхом до скомпільованої програми (файл .so)
  2. Створіть інструкцію - Створіть інструкцію для виклику програми
  3. Обробіть та перевірте - Обробіть інструкцію за допомогою Mollusk та перевірте результат
src/lib.rs
#[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()]);
}
}

Щоб виконати тест, запустіть cargo test.

Коли тест виконається успішно, ви побачите вивід, схожий на наступний:

Terminal
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 success
test tests::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Doc-tests hello_world

Програма Hello World

Цей приклад демонструє, як тестувати базову програму Solana за допомогою Mollusk. Програма просто виводить "Hello, world!" у журнал програми при виклику.

Запуск cargo build-sbf генерує скомпільовану програму в /target/deploy/<program_name>.so.

Для тестування програми Solana з Mollusk:

  1. Створіть екземпляр Mollusk - Ініціалізуйте Mollusk з ідентифікатором програми та шляхом до скомпільованої програми (файл .so)
  2. Створіть інструкцію - Створіть інструкцію для виклику програми
  3. Обробіть та перевірте - Обробіть інструкцію за допомогою Mollusk та перевірте результат
src/lib.rs
#[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()]);
}
}

Щоб виконати тест, запустіть cargo test.

Коли тест виконається успішно, ви побачите вивід, схожий на наступний:

Terminal
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 success
test tests::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Doc-tests hello_world
lib.rs
hello_world.so
Cargo.toml
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 надає простий інтерфейс для тестування програм Solana. Усіма полями можна керувати через кілька допоміжних методів, але користувачі також можуть безпосередньо отримувати доступ та змінювати їх, якщо бажають більшого контролю.

Щоб ініціалізувати Mollusk з екземпляром за замовчуванням, використовуйте метод Mollusk::default.

Example
// Default instance with no custom programs
let mollusk = Mollusk::default();

Щоб ініціалізувати Mollusk з конкретною програмою, використовуйте метод Mollusk::new.

Example
// Initialize Mollusk with a specific program from a file path
let program_id = Pubkey::new_unique();
let mollusk = Mollusk::new(&program_id, "target/deploy/my_program");

Щоб додати програму до Mollusk, використовуйте метод Mollusk::add_program.

Example
let mollusk = Mollusk::default();
let program_id = Pubkey::new_unique();
// Add a program to Mollusk
mollusk.add_program(
&program_id,
"target/deploy/my_program",
&bpf_loader_upgradeable::id(),
);

Коли вказуєте шлях до файлу, не включайте розширення .so. Наприклад, "path/to/my_program" правильно, а "path/to/my_program.so" неправильно.

Обробка інструкцій

Mollusk надає чотири основні методи для обробки інструкцій:

МетодОпис
process_instructionОбробляє інструкцію та повертає результат.
process_and_validate_instructionОбробляє інструкцію та виконує серію перевірок результату, викликаючи panic, якщо будь-яка перевірка не пройдена.
process_instruction_chainОбробляє кілька інструкцій та повертає результат.
process_and_validate_instruction_chainОбробляє кілька інструкцій та виконує серію перевірок для кожного результату, викликаючи panic, якщо будь-яка перевірка не пройдена.

InstructionResult містить деталі обробленої інструкції.

Одиночна інструкція

Використовуйте метод process_instruction для обробки однієї інструкції без перевірок результату. Ви можете вручну перевірити результати після обробки.

Method Signature
pub fn process_instruction(
&self,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
) -> InstructionResult

Наступний приклад обробляє інструкцію переказу SOL без перевірок валідації.

Приклади нижче запускають Mollusk у функції main для демонстраційних цілей. На практиці ви зазвичай використовуватимете Mollusk у тестовому модулі з атрибутом #[test].

Single Instruction
use {
mollusk_svm::Mollusk,
solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},
};
fn main() {
// Initialize Mollusk
let mollusk = Mollusk::default();
// Set up accounts
let sender = Pubkey::new_unique();
let recipient = Pubkey::new_unique();
let initial_lamports = 1_000_000;
let transfer_amount = 250_000;
// Create transfer instruction
let instruction = system_instruction::transfer(&sender, &recipient, transfer_amount);
// Define initial account states
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,
},
),
];
// Process the instruction
let result = mollusk.process_instruction(&instruction, &accounts);
println!("{:#?}", result);
// Check the result
assert!(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);
}
Console
Click to execute the code.

Одна інструкція з перевірками

Використовуйте метод process_and_validate_instruction для обробки однієї інструкції з перевірками валідації. Цей метод викличе panic, якщо будь-яка перевірка не пройде.

Method Signature
pub fn process_and_validate_instruction(
&self,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
checks: &[Check],
) -> InstructionResult

Наступний приклад обробляє інструкцію переказу SOL з перевірками валідації.

With Validation
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 checks
let 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);
}
Console
Click to execute the code.

Кілька інструкцій

Використовуйте метод process_instruction_chain для послідовної обробки кількох інструкцій без перевірок валідації.

Method Signature
pub fn process_instruction_chain(
&self,
instructions: &[Instruction],
accounts: &[(Pubkey, Account)],
) -> InstructionResult

Наступний приклад обробляє дві інструкції переказу SOL без перевірок валідації.

Multiple Instructions
use {
mollusk_svm::Mollusk,
solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},
};
fn main() {
let mollusk = Mollusk::default();
// Set up accounts
let alice = Pubkey::new_unique();
let bob = Pubkey::new_unique();
let charlie = Pubkey::new_unique();
let initial_lamports = 1_000_000;
// Create chain of transfers
let instructions = vec![
system_instruction::transfer(&alice, &bob, 300_000), // Alice -> Bob
system_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 chain
let result = mollusk.process_instruction_chain(&instructions, &accounts);
println!("{:#?}", result);
// Final balances: Alice=700K, Bob=200K, Charlie=100K
assert_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);
}
Console
Click to execute the code.

Кілька інструкцій з перевірками

Використовуйте метод process_and_validate_instruction_chain для обробки кількох інструкцій з перевірками валідації після кожної інструкції. Кожна інструкція має свій власний набір перевірок, які повинні пройти.

Method Signature
pub fn process_and_validate_instruction_chain(
&self,
instructions: &[(&Instruction, &[Check])],
accounts: &[(Pubkey, Account)],
) -> InstructionResult

Наступний приклад обробляє послідовність із двох інструкцій переказу SOL з перевірками валідації після кожної інструкції.

With Validation
use {
mollusk_svm::{result::Check, Mollusk},
solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},
};
fn main() {
let mollusk = Mollusk::default();
// Create accounts
let alice = Pubkey::new_unique();
let bob = Pubkey::new_unique();
let charlie = Pubkey::new_unique();
let initial_lamports = 1_000_000;
// Create transfer instructions
let transfer1 = system_instruction::transfer(&alice, &bob, 300_000);
let transfer2 = system_instruction::transfer(&bob, &charlie, 100_000);
// Initial accounts
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,
},
),
];
// Define checks for each instruction
let 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 step
let 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);
}
Console
Click to execute the code.

Перевірки валідації

Mollusk надає набір допоміжних методів для перевірки результатів обробленої інструкції.

Example
use mollusk_svm::result::Check;

Використовуйте такі методи для перевірки результатів інструкцій:

Example
// Program execution succeeded
Check::success()
// Program returned specific error
Check::err(ProgramError::InvalidArgument)
// Instruction level error
Check::instruction_err(InstructionError::InsufficientFunds)
// Check with specific program result
Check::program_result(ProgramResult::Success)
// Compute units consumed
Check::compute_units(1000)
// Execution time
Check::time(100)
// Return data from instruction execution
Check::return_data(&[1, 2, 3, 4])

Використовуйте такі методи для перевірки станів облікових записів:

Example
// Single account validation
Check::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 exempt
Check::all_rent_exempt()

Постійний стан облікового запису

MolluskContext є обгорткою навколо Mollusk, яка підтримує стан облікового запису під час кількох викликів інструкцій через свій account_store. Методи обробки інструкцій ідентичні до Mollusk.

На відміну від Mollusk, який вимагає передачі accounts до кожного методу (наприклад, process_instruction), MolluskContext керує обліковими записами внутрішньо через свій account_store. Це усуває потребу в параметрі accounts під час обробки інструкцій.

Створіть account_store за допомогою методу with_context:

Example
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);

Наступний приклад обробляє дві окремі інструкції переказу SOL з постійним станом облікового запису між інструкціями через account_store.

Stateful Testing
use {
mollusk_svm::Mollusk,
solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},
std::collections::HashMap,
};
fn main() {
// Initialize Mollusk
let mollusk = Mollusk::default();
// Create accounts
let sender = Pubkey::new_unique();
let recipient = Pubkey::new_unique();
// Create account store with initial balances
let 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 context
let context = mollusk.with_context(account_store);
// First transfer: 200,000 lamports
let 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 balances
let 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);
}
Console
Click to execute the code.

Системні змінні Mollusk

Mollusk надає власну структуру Sysvars для зміни її значень для тестування.

Використовуйте метод warp_to_slot для оновлення системного годинника, щоб імітувати рух вперед або назад у часі до конкретного slot.

Warp to Slot
use mollusk_svm::Mollusk;
fn main() {
// Initialize Mollusk
let mut mollusk = Mollusk::default();
// Show initial slot
println!("Initial slot: {}", mollusk.sysvars.clock.slot);
// Warp to slot 1000
mollusk.warp_to_slot(100);
println!("After warp: {}", mollusk.sysvars.clock.slot);
// Warp to slot 10
mollusk.warp_to_slot(10);
println!("After second warp: {}", mollusk.sysvars.clock.slot);
}
Console
Click to execute the code.

Наступний приклад показує, як змінювати системну змінну Mollusk безпосередньо через доступ до поля sysvars для зміни параметрів rent. Ви можете змінювати інші значення системних змінних таким самим способом.

Modify Sysvars
use {mollusk_svm::Mollusk, solana_sdk::rent::Rent};
fn main() {
let mut mollusk = Mollusk::default();
// Show default rent
println!(
"Default rent exemption for 1000 bytes: {} lamports",
mollusk.sysvars.rent.minimum_balance(1000)
);
// Customize rent parameters
mollusk.sysvars.rent = Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
burn_percent: 0,
};
// Show custom rent
println!(
"Custom rent exemption for 1000 bytes: {} lamports",
mollusk.sysvars.rent.minimum_balance(1000)
);
}
Console
Click to execute the code.

Тестування обчислювальних одиниць

MolluskComputeUnitBencher відстежує використання обчислювальних одиниць інструкціями програми. Результати записуються у файл markdown.

Потребує mollusk-svm-bencher як залежність.

Наступний приклад тестує використання обчислювальних одиниць інструкцією переказу SOL.

Benchmark Compute Units
use {
mollusk_svm::Mollusk,
mollusk_svm_bencher::MolluskComputeUnitBencher,
solana_sdk::{account::Account, pubkey::Pubkey, system_instruction, system_program},
};
fn main() {
// Initialize Mollusk
let mollusk = Mollusk::default();
// Create test accounts
let sender = Pubkey::new_unique();
let receiver = Pubkey::new_unique();
// Transfer instruction
let 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 benchmark
MolluskComputeUnitBencher::new(mollusk)
.bench(("transfer", &transfer, &accounts))
.must_pass(true)
.out_dir("./target/benches")
.execute();
}

Результати тестування записуються у вказаний out_dir як файл markdown з назвою compute_units.md.

comput_units.md
#### 2025-09-19 22:28:53.691839 UTC
Solana CLI Version: solana-cli 2.2.20 (src:dabc99a5; feat:3073396398,
client:Agave)
| Name | CUs | Delta |
| -------- | --- | ------- |
| transfer | 150 | - new - |

Тестування Token Program

Використовуйте mollusk-svm-programs-token пакет для додавання token program, token2022 program (token extensions) та associated token program до Mollusk для тестування.

Example
use {
mollusk_svm::Mollusk,
mollusk_svm_programs_token::{associated_token, token, token2022},
};
let mut mollusk = Mollusk::default();
// Add SPL Token Program
token::add_program(&mut mollusk);
// Add SPL Token-2022 Program
token2022::add_program(&mut mollusk);
// Add Associated Token Account Program
associated_token::add_program(&mut mollusk);

Наступний приклад демонструє тестування переказу токенів за допомогою Mollusk.

Приклад нижче вручну визначає тестові облікові записи для демонстраційних цілей. mollusk-svm-programs-token також містить допоміжні функції для створення mint та token account.

Token Transfer Example
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 program
let mut mollusk = Mollusk::default();
token::add_program(&mut mollusk);
// Create account keys
let mint = Pubkey::new_unique();
let source = Pubkey::new_unique();
let destination = Pubkey::new_unique();
let authority = Pubkey::new_unique();
// Token configuration
let decimals = 6;
let transfer_amount = 1_000_000; // 1 token with 6 decimals
let initial_balance = 10_000_000; // 10 tokens
// Calculate rent-exempt minimums
let mint_rent = mollusk.sysvars.rent.minimum_balance(Mint::LEN);
let account_rent = mollusk.sysvars.rent.minimum_balance(TokenAccount::LEN);
// Create mint account
let 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 account
let 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 account
let 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_checked
let 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 instruction
let instruction = transfer_checked(
&token::ID,
&source,
&mint,
&destination,
&authority,
&[],
transfer_amount,
decimals,
)
.unwrap();
// Expected balances after transfer
let expected_source_balance = (initial_balance - transfer_amount).to_le_bytes();
let expected_dest_balance = transfer_amount.to_le_bytes();
// Define validation checks
let 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 instruction
let result = mollusk.process_and_validate_instruction(&instruction, &accounts, &checks);
println!("{:#?}", result);
// Deserialize token account data
let 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);
}
Console
Click to execute the code.

Is this page helpful?