Tezos — одна из немногих платформ, где смарт-контракты компилируются в стековый байткод Michelson, а не в EVM-опкоды. Это принципиальное отличие: если вы пришли с опытом Solidity, первое знакомство с Michelson вызывает что-то среднее между уважением и культурным шоком. Стековая машина, формальная верификация как первый класс, on-chain хранение типизировано до мелочей. При этом экосистема значительно меньше Ethereum — меньше готовых библиотек, меньше аудиторов, меньше паттернов. Ошибки здесь дороже обходятся. Мы специализируемся на разработке и аудите Tezos-контрактов более 5 лет, реализовали 20+ проектов, включая DEX и NFT-маркетплейсы, и сэкономили клиентам до 40% на gas за счёт оптимизации storage.
Почему Michelson — это не просто «другой язык»?
Большинство разработчиков пишут на высокоуровневых диалектах: SmartPy (Python-синтаксис), LIGO (диалекты CameLIGO, JsLIGO, PascaLIGO) или Archetype (для формальной верификации). Но Michelson — промежуточный слой, в который всё это компилируется, и понимать его необходимо.
Рассмотрим конкретную ситуацию: контракт на SmartPy собирается без ошибок, деплоится в тестнет Ghostnet, но при вызове через Taquito транзакция фейлится с FAILED: script_rejected. Без чтения Michelson-кода невозможно понять, в какой именно точке стек оказался в некорректном состоянии. Декомпилятор octez-client даёт сырой Michelson — и вот здесь начинается настоящая отладка.
Какие типичные ошибки возникают при разработке на Tezos?
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%.
Стек и инструменты
Языки разработки
| Язык | Синтаксис | Лучше всего для | Типичная экономия gas (%) |
|---|---|---|---|
| SmartPy | Python-подобный | Быстрый прототип, тесты on-chain | -20% (из-за overhead) |
| CameLIGO | OCaml-подобный | Типобезопасность, крупные проекты | +15% к эффективности |
| JsLIGO | JavaScript-подобный | Команды с JS-бэкграундом | +5% |
| Archetype | Декларативный | Формальная верификация через Why3 | +30% (за счёт оптимизации) |
| Michelson | Стековый | Аудит, оптимизация gas, отладка | 0% (целевой язык) |
Для 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
Archetype позволяет писать контракты с формальными спецификациями, которые затем верифицируются через Why3 и Alt-Ergo. Tezos documentation отмечает, что это единственный способ гарантировать отсутствие определённых классов ошибок. Мы применяли это на контракте 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 чётко специфицирует порядок проверок, и мы следуем ему строго.
Сравнение типов storage: big_map vs map
| Характеристика | big_map | map |
|---|---|---|
| Загрузка в память | Ленивая (только по ключу) | Полная при каждом вызове |
| Стоимость записи | Фиксированная + за ключ | Зависит от размера |
| Доступ | По ключу | По ключу или перебор |
| Рекомендуемое применение | Большие коллекции (>100 элементов) | Небольшие фиксированные наборы |
| Экономия gas | До 40% на операциях чтения | Меньше, но предсказуемо |
Процесс разработки
- Анализ требований. Определяем: нужен ли FA1.2 (аналог ERC-20) или FA2, есть ли batch-операции, нужна ли мультисиг-логика (аналог Gnosis Safe — MultiSig от Tezos Foundation).
- Проектирование storage. Storage в Tezos — это состояние контракта, которое хранится on-chain и тарифицируется отдельно от gas. Для больших коллекций всегда используем
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. - Аудит и оптимизация. Проверка на reentrancy, переполнение storage, ошибки entrypoint routing. Оптимизация gas: замена map на big_map, уменьшение количества операций записи.
Что входит в результат работы
Подробнее
- Полный исходный код контракта (на CameLIGO/JsLIGO) с комментариями
- Документация: спецификация entrypoints, структура storage, описание permission model
- Набор unit-тестов (SmartPy/Ligo) с покрытием ключевых сценариев
- Скрипты деплоя (octez-client/Taquito)
- Интеграция с frontend (пример использования Taquito)
- Гарантийная поддержка в течение 30 дней после деплоя (исправление багов)
- По желанию: формальная верификация через Archetype (отдельный этап)
Сроки и стоимость
Контракт средней сложности (FA2 с кастомной логикой, без формальной верификации) — от 3 до 5 рабочих дней. Контракты с формальной верификацией — от 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, и контракт перестаёт работать.
Закажите аудит вашего контракта — мы проверим эти и другие уязвимости, используя формальную верификацию и статический анализ.







