Інтроспекція інструкцій
Інтроспекція інструкцій — це потужна можливість у Solana, яка дозволяє програмі аналізувати інші інструкції в межах тієї ж транзакції, включаючи ті, які ще не виконані. Це дає змогу динамічно реагувати на ці інструкції або розширювати їх — наприклад, додаючи захисні механізми, перевіряючи поведінку або інтегруючи інструкції від зовнішніх програм у вашу власну логіку.
Це стає можливим завдяки спеціальному системному обліковому запису, який називається Instructions
sysvar. Sysvar — це облікові записи лише для читання, які підтримуються середовищем виконання Solana і надають програмам доступ до внутрішнього стану (наприклад, годинник, оренда, розклад епох тощо). Sysvar Instructions конкретно надає повний список інструкцій у поточній транзакції, разом з їхніми метаданими та серіалізованими даними.
Ось як Solana серіалізує цю інформацію під час виконання:
// First encode the number of instructions:
// 0..2 - num_instructions
//
// Then a table of offsets of where to find them in the data
// 3..2 * num_instructions table of instruction offsets
//
// Each instruction is then encoded as:
// 0..2 - num_accounts
// 2 - meta_byte -> (bit 0 signer, bit 1 is_writable)
// 3..35 - pubkey - 32 bytes
// 35..67 - program_id
// 67..69 - data len - u16
// 69..data_len - data
#[cfg(not(target_os = "solana"))]
fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec<u8> {
// 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
let mut data = Vec::with_capacity(instructions.len() * (32 * 2));
append_u16(&mut data, instructions.len() as u16);
for _ in 0..instructions.len() {
append_u16(&mut data, 0);
}
for (i, instruction) in instructions.iter().enumerate() {
let start_instruction_offset = data.len() as u16;
let start = 2 + (2 * i);
data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
append_u16(&mut data, instruction.accounts.len() as u16);
for account_meta in &instruction.accounts {
let mut account_meta_flags = InstructionsSysvarAccountMeta::empty();
if account_meta.is_signer {
account_meta_flags |= InstructionsSysvarAccountMeta::IS_SIGNER;
}
if account_meta.is_writable {
account_meta_flags |= InstructionsSysvarAccountMeta::IS_WRITABLE;
}
append_u8(&mut data, account_meta_flags.bits());
append_slice(&mut data, account_meta.pubkey.as_ref());
}
append_slice(&mut data, instruction.program_id.as_ref());
append_u16(&mut data, instruction.data.len() as u16);
append_slice(&mut data, instruction.data);
}
data
}
Це означає, що читаючи обліковий запис sysvar Instructions
всередині вашої програми, ви можете отримати доступ до всіх інструкцій, включених у поточну транзакцію.
Вам не потрібно вручну аналізувати необроблені байти. Solana надає допоміжні функції:
load_current_index_checked
: повертає індекс інструкції, яка зараз виконується.load_instruction_at_checked
: дозволяє завантажити конкретну інструкцію за її індексом у розібраному, десеріалізованому форматі.
Як це працює?
Кожна інструкція Solana містить:
- ідентифікатор програми, на яку вона націлена,
- облікові записи, з якими вона взаємодіє (включаючи метадані, такі як підписант/можливість запису),
- та корисне навантаження даних (зазвичай дискримінатор + аргументи).
І саме це вам надає функція load_instruction_at_checked
з sysvar Instructions:
- program_id: програма, яку викликає ця інструкція.
- accounts: список задіяних облікових записів з метаданими.
- data: необроблене вхідне корисне навантаження (якщо ми проводимо інтроспекцію інструкції anchor, вона часто починається з 8-байтового дискримінатора, за яким ідуть параметри).
Щоб безпечно та ефективно досліджувати інші інструкції, дотримуйтесь таких кроків:
- Визначте індекс поточної інструкції: Використовуйте
load_current_index_checked
для знаходження індексу інструкції, що виконується.
- Завантажте цільову інструкцію для аналізу: Маючи індекс, використовуйте
load_instruction_at_checked(index)
для завантаження іншої інструкції в тій самій транзакції — попередньої, наступної або за конкретною позицією.
- Створіть обмеження для запобігання зловмисній поведінці: Інтроспекція інструкцій — потужний інструмент, але вона створює нові вразливості. Обов'язково: перевіряйте, що досліджувана інструкція націлена на очікувану програму, підтверджуйте, що адреси облікових записів і дані відповідають очікуваним шаблонам, та уникайте припущень щодо порядку інструкцій, якщо це явно не забезпечено.
Дотримуючись цих запобіжних заходів, ви можете безпечно використовувати інтроспекцію інструкцій для створення потужних, компонованих і безпечних програм Solana.
Загальні обмеження для інтроспекції
При використанні інтроспекції інструкцій важливо застосовувати суворі обмеження для запобігання зловмисній або неочікуваній поведінці. Найпоширеніші запобіжні заходи включають:
- Перевірка інструкції: Перевіряйте ідентифікатор програми та дискримінатор інструкції, щоб переконатися, що вона правильна. Дискримінатор (зазвичай перші 1, 4 або 8 байтів даних інструкції) унікально ідентифікує функцію, яка викликається.
- Перевірка змінних: Після дискримінатора проаналізуйте та перевірте критичні змінні, які використовуються інструкцією. Це можуть бути суми, напрямки (наприклад, довгі/короткі) або ідентифікатори, і ви завжди повинні підтверджувати, що ці поля відповідають очікуваній логіці вашої інтеграції чи захисту.
- Перевірка облікових записів: Перевіряйте структуру та ідентичність облікових записів інструкції. Перевіряйте, що очікувані облікові записи з'являються на конкретних позиціях (наприклад, підписувач, сховище, застава) і переконайтеся, що ролі, такі як статус підписувача/можливість запису, відповідають вашим припущенням.
Застосовуючи ці обмеження, ви гарантуєте, що ваша програма реагує лише на дійсні та надійні інструкції, роблячи вашу логіку більш надійною, компонованою та безпечною.
Разом з атомарністю транзакцій, ці перевірки дозволяють вам створювати надійну, компоновану логіку, яка може безпечно взаємодіяти з іншими програмами та інструкціями в межах однієї транзакції.