Assembly
Assembly Slippage

Assembly Slippage

10 Graduates

Assembly Slippage

Assembly Slippage Challenge

Проковзування в Assembly

У цьому розділі ми використаємо sBPF Assembly для створення базової інструкції перевірки проковзування. Включивши таку інструкцію в останній індекс нашого масиву інструкцій, ми можемо створити додатковий захист останньої інстанції від помилок у смарт-контрактах або зловмисних контрактів з бітовими помилками.

Є кілька властивостей перевірки проковзування, які роблять її ідеальним кандидатом для асемблера:

  • Єдиний, обмежений варіант використання

  • Немає потреби виконувати перевірки підписувача/облікового запису

  • Може лише покращити безпеку

Якщо ви не знайомі з тим, як писати програми на асемблері, пройдіть вступний курс з Assembly

Дизайн програми

Наша програма реалізує просту, але важливу операцію: перевірку того, що токен-акаунт має достатній баланс перед виконанням транзакції. Цей патерн зустрічається всюди в DeFi — від свопів AMM до кредитних протоколів.

Програма очікує:

  • Один SPL токен-акаунт у масиві акаунтів

  • 8-байтову суму в даних інструкції

  • Повертає успіх, якщо баланс ≥ суми, інакше — помилку

Зміщення в пам'яті

Програми sBPF отримують дані акаунтів як суміжні області пам'яті. Ці константи визначають байтові зміщення. Припускаючи, що наша програма прийматиме лише один акаунт, і це буде SPL Token Account, можна статично визначити ці зміщення як:

sbpf
.equ TOKEN_ACCOUNT_BALANCE, 0x00a0
.equ MINIMUM_BALANCE, 0x2918
  • TOKEN_ACCOUNT_BALANCE (0x00a0): вказує на поле балансу в даних акаунта SPL Token. Токен-акаунти мають стандартну структуру, де баланс (8 байтів, little-endian) знаходиться на зміщенні 160.

  • MINIMUM_BALANCE (0x2918): визначає, де Solana розміщує корисне навантаження даних вашої інструкції. Це зміщення є частиною структури інформації про акаунт середовища виконання.

Ви можете згенерувати зміщення за допомогою наших інструментів на sbpf.xyz

На відміну від мов високого рівня, які абстрагують розташування пам'яті, асемблер вимагає точного знання, де саме знаходиться кожен фрагмент даних.

Entrypoint and Initial Validation

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

sbpf
jge r3, r4, end         // Skip to exit if balance is valid

Інструкція jge (перехід, якщо більше або дорівнює) порівнює r3 (необхідну суму) з r4 (доступним балансом). Якщо r3 >= r4, ми переходимо до мітки end; це схоже на ранній вихід.

Якщо умова не виконується, виконання продовжується шляхом обробки помилок. Цей шаблон розгалуження за умовою — це спосіб реалізації логіки if/else в асемблері.

Error Handling and Logging

sbpf
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, щоб сигналізувати про збій програми. Середовище виконання перерве транзакцію та поверне цей код помилки.

Завершення програми

sbpf
end:
    exit

Інструкція exit завершує виконання програми та повертає контроль середовищу виконання Solana. Значення в r0 стає кодом виходу програми (0 для успішного виконання, ненульове значення для помилок).

На відміну від мов високого рівня з автоматичним очищенням, програми на асемблері повинні явно завершуватися. Вихід за межі вашого коду призводить до невизначеної поведінки.

Дані лише для читання

sbpf
.rodata
    e: .ascii "Slippage exceeded"

Секція .rodata (дані лише для читання) містить наше повідомлення про помилку.

Висновок

Ця крихітна програма виконує те, що в Rust могло б зайняти десятки CU, використовуючи лише 4 CUs у випадку успіху, 6 CUs у випадку невдачі, або 106 CUs у випадку невдачі з виведенням повідомлення про помилку.

Компроміс полягає в тому, що ми повинні розуміти структури пам'яті, угоди про виклики та обробку помилок на найнижчому рівні. Але для операцій, критичних до продуктивності, переваги часто виправдовують зусилля.

Цей код сам по собі не є "безпечним". Ми не перевіряємо нічого щодо переданих облікових записів. Але це має бути додаткова інструкція, яка повинна виконуватися з добрими намірами.

Готові прийняти завдання?
Blueshift © 2025Commit: e573eab