Розробка смарт-контрактів на Michelson (Tezos)
Tezos — одна з небагатьох платформ, де смарт-контракти компілюються в стековий байткод Michelson, а не в EVM-опкоди. Це принципіальне відмінність: якщо ви прийшли з досвідом Solidity, перше знайомство з Michelson викликає щось середнє між повагою та культурним шоком. Стекова машина, формальна верифікація як перший клас, on-chain сховище типізоване до дрібниць. При цьому екосистема значно менша за Ethereum — менше готових бібліотек, менше аудиторів, менше паттернів. Помилки тут дорожче обходяться.
Чому Michelson — це не просто «інший мова»
Більшість розробників пишуть на високорівневих діалектах: SmartPy (Python-синтаксис), LIGO (діалекти CameLIGO, JsLIGO, PascaLIGO) або Archetype (для формальної верифікації). Але Michelson — проміжний шар, в який все це компілюється, та розуміти його необхідно.
Розглянемо конкретну ситуацію: контракт на SmartPy збирається без помилок, деплоїться в тестнет Ghostnet, але при виклику через Taquito транзакція фейлится з FAILED: script_rejected. Без читання Michelson-коду неможливо зрозуміти, в якій саме точці стек опинився в некоректному стані. Декомпілятор octez-client дає сирий Michelson — і от тут починається справжня відладка.
Типові помилки при переході з EVM
Storage layout не збіжиться з очікуваннями. У Solidity storage — це слоти. У Tezos storage — це big_map та map, вкладені записи (record), option-типи. Розробники, звикли до маппінгів Solidity, іноді неправильно моделюють вкладені структури в LIGO. В результаті get з big_map повертає None там, де очікував Some(value), тому що ключ було сконструйовано неправильно.
Entrypoint routing. У Tezos контракт може мати кілька точок входу (entrypoints), які в Michelson реалізуються через OR-дерево. SmartPy та LIGO генерують це дерево автоматично, але якщо клієнт (Taquito) викликає entrypoint за іменем, а ім'я в ABI не збіжиться з іменем в коді, транзакція відхиляється. Проблема особливо коварна при оновленні контракту — ABI у frontend може залишитися від старої версії.
Gas estimation на FA2. Стандарт FA2 (аналог ERC-1155 у Tezos) припускає batch-операції. При великому batch Taquito іноді недооцінює gas limit, та транзакція фейлится вже on-chain. Правильне рішення — явно задавати storageLimit та gasLimit або використовувати estimate() з Taquito з запасом 10-15%.
Стек та інструменти
Мови розробки
| Мова | Синтаксис | Краще всього для |
|---|---|---|
| SmartPy | Python-подібний | Швидкий прототип, тести on-chain |
| CameLIGO | OCaml-подібний | Типобезопасність, крупні проекти |
| JsLIGO | JavaScript-подібний | Команди з JS-бекграундом |
| Archetype | Декларативний | Формальна верифікація через Why3 |
| Michelson | Стековий | Аудит, оптимізація gas, відладка |
Для production ми використовуємо CameLIGO як основну мову. Статична типізація в стилі OCaml усуває цілий клас помилок ще на етапі компіляції. SmartPy застосовуємо для швидких експериментів та внутрішніх тестів.
Інфраструктура
- octez-client — CLI для взаємодії з нодою, деплой, виклик entrypoints, перегляд storage
- Ligo CLI — компіляція, dry-run, генерація Michelson
- Taquito — JavaScript-бібліотека для frontend-інтеграції (аналог ethers.js)
- Better Call Dev — block explorer з зручним переглядом storage та викликів
- SmartPy IDE — для швидкого тестування в браузері
- Ghostnet — основний тестнет
Формальна верифікація через Archetype
Архетип дозволяє писати контракти з формальними специфікаціями, які потім верифікуються через Why3 та Alt-Ergo. Це не абстрактна можливість — ми застосовували це на контракті escrow, де потрібно було математично довести, що кошти ніколи не можуть бути заблоковані при будь-якій послідовності викликів. Верифікація виявила один edge case: при певній комбінації refund та claim в одному блоці контракт міг увійти в стан, з якого неможливо вивести кошти. Ні unit-тести, ні fuzzing це не знайшли.
FA2 — стандарт, який потрібно знати
FA2 (TZIP-12) — основний стандарт для токенів у Tezos. Аналог ERC-1155, але з більш строгою специфікацією по операторам та перевіркам дозволів. Реалізація FA2 включає:
-
transfer— batch-трансфер з перевіркою операторів -
update_operators— додавання/видалення операторів для адреси -
balance_of— запит балансів (callback-паттерн, не view функція)
Callback-паттерн в balance_of — класичний камень преткновення. На відміну від Solidity, де view-функція повертає значення синхронно, у Tezos запит баланса — це окремі транзакція з callback-контрактом. Frontend повинен або читати storage напрямо через RPC, або реалізовувати on-chain callback. Taquito надає fa2.getBalance() поверх прямого читання storage.
Оператори vs. Allowances
У FA2 немає концепції allowance як у ERC-20. Замість цього — оператори: адреси, яким дозволено управляти токенами від імені власника. Це потужніше (оператор керує всіма токенами зразу), але й небезпечніше: якщо не реалізувати правильну перевірку в transfer, оператор може перевести що угодно. TZIP-12 чітко специфікує порядок перевірок, та ми слідуємо йому суворо.
Процес розробки
Аналіз вимог. Визначаємо: потрібен ли FA1.2 (аналог ERC-20) або FA2, чи є batch-операції, потрібна ли мультисиг-логіка (аналог Gnosis Safe — Multi-sig від Tezos Foundation).
Проектування storage. Storage у Tezos — це стан контракту, який зберігається on-chain та тарифікується окремо від gas. big_map — ленива структура, не завантажується в пам'ять цілком, платиться тільки за доступ до конкретних ключів. map — завантажується повністю. Для великих колекцій завжди big_map.
Розробка на CameLIGO. Локальна компіляція через Ligo CLI, dry-run для перевірки логіки без деплою.
Тестування. Юнит-тести через SmartPy test runner або Ligo test framework. Інтеграційні тести через Taquito на Ghostnet.
Деплой та верифікація. Деплой через octez-client originate. Верифікація вихідного коду на Better Call Dev та tzkt.io — аналог Etherscan для Tezos.
Сроки
Контракт середної складності (FA2 з кастомною логікою, без формальної верифікації) — від 3 до 5 робочих днів. Контракти з формальною верифікацією через Archetype/Why3 — від 2 тижнів. DeFi-протокол на Tezos (DEX, lending) — від місяця.
Вартість розраховується після аналізу вимог.
На що смотреть при аудиті Tezos-контрактів
Reentrancy через TRANSFER_TOKENS. Tezos не захищен від reentrancy автоматично. Якщо контракт викликає зовнішню адресу через TRANSFER_TOKENS та потім змінює storage, можлива атака. Паттерн захисту — checks-effects-interactions (спочатку змінюємо storage, потім роблимо зовнішній виклик). У Tezos це особливо важливо, тому що виклики контрактів виконуються асинхронно в черзі операцій.
Перевірка amount у payable entrypoints. У Tezos будь-який entrypoint може приймати tez. Якщо не перевіритися Tezos.get_amount() = 0tez в entrypoints, які не повинні приймати кошти, користувач випадково заблокує tez в контракті.
Storage fees. Запис нових даних у storage коштує tez. Якщо контракт дозволяє без обмеження додавати записи в map без оплати, це вектор для DoS: атакуючий додає тисячі записів, вичерпуючи баланс контракту на storage fees, та контракт перестає працювати.







