
Проковзування в Assembly
У цьому розділі ми використаємо sBPF Assembly для створення базової інструкції перевірки проковзування. Включивши таку інструкцію в останній індекс нашого масиву інструкцій, ми можемо створити додатковий захист останньої інстанції від помилок у смарт-контрактах або зловмисних контрактів з бітовими помилками.
Є кілька властивостей перевірки проковзування, які роблять її ідеальним кандидатом для асемблера:
Єдиний, обмежений варіант використання
Немає потреби виконувати перевірки підписувача/облікового запису
Може лише покращити безпеку
Якщо ви не знайомі з тим, як писати програми на асемблері, пройдіть вступний курс з Assembly
Дизайн програми
Наша програма реалізує просту, але важливу операцію: перевірку того, що токен-акаунт має достатній баланс перед виконанням транзакції. Цей патерн зустрічається всюди в DeFi — від свопів AMM до кредитних протоколів.
Програма очікує:
Один SPL токен-акаунт у масиві акаунтів
8-байтову суму в даних інструкції
Повертає успіх, якщо баланс ≥ суми, інакше — помилку
Зміщення в пам'яті
Програми sBPF отримують дані акаунтів як суміжні області пам'яті. Ці константи визначають байтові зміщення. Припускаючи, що наша програма прийматиме лише один акаунт, і це буде SPL Token Account, можна статично визначити ці зміщення як:
.equ TOKEN_ACCOUNT_BALANCE, 0x00a0
.equ MINIMUM_BALANCE, 0x2918TOKEN_ACCOUNT_BALANCE (0x00a0): вказує на поле балансу в даних акаунта SPL Token. Токен-акаунти мають стандартну структуру, де баланс (8 байтів, little-endian) знаходиться на зміщенні 160.MINIMUM_BALANCE (0x2918): визначає, де Solana розміщує корисне навантаження даних вашої інструкції. Це зміщення є частиною структури інформації про акаунт середовища виконання.
На відміну від мов високого рівня, які абстрагують розташування пам'яті, асемблер вимагає точного знання, де саме знаходиться кожен фрагмент даних.
Entrypoint and Initial Validation
.globl entrypoint
entrypoint:
ldxdw r3, [r1+MINIMUM_BALANCE] // Get amount from IX data
ldxdw r4, [r1+TOKEN_ACCOUNT_BALANCE] // Get balance from token accountКожна програма sBPF починається з глобального символу .entrypoint. Середовище виконання Solana надає дані облікового запису та інструкції через регістр r1.
Інструкція ldxdw завантажує (ldx) 8-байтове (подвійне слово, dx) значення з пам'яті в регістр. Ось що відбувається:
ldxdw r3, [r1+MINIMUM_BALANCE]: обчислює адресу пам'яті, що містить нашу необхідну суму. Значення завантажується вr3.ldxdw r4, [r1+TOKEN_ACCOUNT_BALANCE]: вказує на поле балансу токен-акаунта. Це 64-бітне значення потрапляє вr4.
Обидві операції виконуються без копіювання: ми читаємо безпосередньо з даних облікового запису без накладних витрат на десеріалізацію.
Conditional Logic and Branching
jge r3, r4, end // Skip to exit if balance is validІнструкція jge (перехід, якщо більше або дорівнює) порівнює r3 (необхідну суму) з r4 (доступним балансом). Якщо r3 >= r4, ми переходимо до мітки end; це схоже на ранній вихід.
Якщо умова не виконується, виконання продовжується шляхом обробки помилок. Цей шаблон розгалуження за умовою — це спосіб реалізації логіки if/else в асемблері.
Error Handling and Logging
lddw r1, e // Load error message address
lddw r2, 17 // Load length of error message
call sol_log_ // Log out error message
lddw r0, 1 // Return error code 1Коли перевірка не вдається, ми записуємо зрозумілу для людини помилку перед завершенням:
lddwзавантажує безпосередні значення, у цьому випадку адресу нашого рядка помилки, який знаходиться в секції.rodata, та його довжину (17 байтів для "Slippage exceeded").call sol_log_викликає системний виклик логування Solana. Середовище виконання зчитує повідомлення з пам'яті та додає його до журналів транзакцій.Потім ми завантажуємо
1уr0, щоб сигналізувати про збій програми. Середовище виконання перерве транзакцію та поверне цей код помилки.
Завершення програми
end:
exitІнструкція exit завершує виконання програми та повертає контроль середовищу виконання Solana. Значення в r0 стає кодом виходу програми (0 для успішного виконання, ненульове значення для помилок).
На відміну від мов високого рівня з автоматичним очищенням, програми на асемблері повинні явно завершуватися. Вихід за межі вашого коду призводить до невизначеної поведінки.
Дані лише для читання
.rodata
e: .ascii "Slippage exceeded"Секція .rodata (дані лише для читання) містить наше повідомлення про помилку.
Висновок
Ця крихітна програма виконує те, що в Rust могло б зайняти десятки CU, використовуючи лише 4 CUs у випадку успіху, 6 CUs у випадку невдачі, або 106 CUs у випадку невдачі з виведенням повідомлення про помилку.
Компроміс полягає в тому, що ми повинні розуміти структури пам'яті, угоди про виклики та обробку помилок на найнижчому рівні. Але для операцій, критичних до продуктивності, переваги часто виправдовують зусилля.