Разработка смарт-контрактов на 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 — Mulit-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: атакующий добавляет тысячи записей, исчерпывая balance контракта на storage fees, и контракт перестаёт работать.







