Разработка смарт-контрактов на Clarity (Stacks)
Stacks — блокчейн, который работает поверх Bitcoin и использует Bitcoin как расчётный слой. Смарт-контракты на Stacks выполняются через консенсус Proof of Transfer (PoX), финальность обеспечивается Bitcoin-блоками. Это уникальная позиция: Turing-complete смарт-контракты с финальностью Bitcoin, без необходимости bridge'ить BTC.
Язык Clarity спроектирован намеренно непохожим на Solidity. Он интерпретируемый (не компилируется в байткод — код читается и исполняется напрямую), decidable (поведение полностью предсказуемо без выполнения), и намеренно не Turing-complete в части рекурсии (нет рекурсивных вызовов между контрактами в одной транзакции).
Что значит decidable — и почему это важно для аудита
В Solidity аудитор должен симулировать выполнение кода, чтобы понять, что произойдёт при данном вызове. В Clarity синтаксис написан так, что поведение функции можно вывести статически, без выполнения. Нет динамических вызовов через указатель, нет произвольной рекурсии, нет self-destruct.
На практике это означает: reentrancy атака в классическом понимании невозможна. Контракт A вызывает контракт B — B не может вызвать A обратно в той же транзакции. Это фундаментальное ограничение языка, не просто lint-правило. Целый класс уязвимостей, унёсших сотни миллионов долларов на EVM, здесь просто отсутствует.
Но у этого есть цена: паттерны, привычные по Solidity, не работают напрямую.
Синтаксис и типовая система Clarity
Clarity написан в Lisp-подобном синтаксисе (S-expressions). Для разработчиков с опытом Solidity/JavaScript это непривычно. Пример простейшей функции:
(define-public (transfer (amount uint) (sender principal) (recipient principal))
(begin
(asserts! (is-eq tx-sender sender) err-not-authorized)
(try! (ft-transfer? my-token amount sender recipient))
(ok true)
)
)
principal — тип для адресов (Stacks-адрес или contract principal). uint — беззнаковое целое. Нет неявных конвертаций типов. try! разворачивает Result, реверт на ошибку.
Типы данных
| Тип | Аналог в Solidity | Особенности |
|---|---|---|
uint |
uint256 |
Только беззнаковые |
principal |
address |
Включает contract principals |
(buff N) |
bytes |
Фиксированная длина |
(string-ascii N) |
string |
ASCII, фиксированная длина |
(list N T) |
T[] |
Фиксированная максимальная длина |
(optional T) |
нет прямого аналога | Явная обработка отсутствия значения |
Фиксированные длины — важный момент. В Clarity нет динамических массивов произвольной длины. (list 200 uint) — список максимум из 200 элементов. Это сделано осознанно: gas model в Stacks рассчитывается статически на основе максимальных размеров данных.
SIP-009 и SIP-010 — стандарты токенов
SIP-010 — стандарт Fungible Token (аналог ERC-20). Обязательные функции: transfer, get-balance, get-total-supply, get-decimals, get-name, get-symbol, get-token-uri.
SIP-009 — Non-Fungible Token (аналог ERC-721). Включает: get-last-token-id, get-token-uri, get-owner, transfer.
В отличие от ERC-20, SIP-010 требует, чтобы transfer принимал sender как явный параметр и проверял tx-sender == sender. Это предотвращает один из классических векторов: вызов transferFrom от имени другого адреса без проверки.
Trait система
В Clarity нет интерфейсов как в Solidity. Вместо этого — traits: именованные наборы функций, которым контракт обязуется соответствовать. При вызове функции с параметром типа <trait> рантайм проверяет, что переданный контракт реализует все функции trait'а. Это позволяет строить composable системы — например, marketplace, который принимает любой SIP-009-совместимый NFT-контракт.
Углублённо: работа с Bitcoin в Clarity
Это главная уникальная возможность Stacks. Через Clarity Bitcoin библиотеку контракт может читать Bitcoin-транзакции напрямую (без bridge). Функция get-burn-block-info? возвращает данные о Bitcoin-блоке. verify-merkle-proof позволяет on-chain верифицировать включение транзакции в Bitcoin-блок.
Это открывает паттерн: пользователь отправляет BTC на Bitcoin-адрес, Clarity-контракт верифицирует транзакцию через Merkle proof и минтит токены на Stacks. Без trust assumptions, без wrapped BTC, без bridge — чистая криптографическая верификация.
Реализация этого паттерна нетривиальна: нужно понимать структуру Bitcoin-транзакций (segwit vs. legacy), Merkle tree Bitcoin блоков, и правильно парсить (buff 1024) как UTXO-данные. Но это первоклассная функциональность языка, а не хак.
Инструменты разработки
- Clarinet — CLI для разработки и тестирования Clarity контрактов (аналог Hardhat/Foundry для Stacks)
-
clarinet new— инициализация проекта -
clarinet test— запуск тестов через Deno/TypeScript -
clarinet console— REPL для интерактивного взаимодействия с контрактами -
clarinet integrate— локальная сеть с симуляцией Bitcoin-блоков - Hiro Explorer — block explorer для Stacks (mainnet + testnet)
- stacks.js — JavaScript-библиотека для взаимодействия с контрактами (аналог ethers.js)
Тесты пишутся на TypeScript с использованием Vitest или Jest. Clarinet предоставляет simnet — симулятор сети в памяти, позволяющий тестировать несколько блоков, advance time, симулировать Bitcoin-транзакции.
Типичные сложности при разработке
Нет msg.value/Payable функций. STX-платежи обрабатываются через stx-transfer?. Если контракт должен принимать STX, нужно явно обрабатывать трансфер в логике функции. Нет автоматического «ETH прикреплён к вызову».
Post-conditions на стороне клиента. stacks.js и кошельки (Leather, Xverse) поддерживают post-conditions: пользователь подписывает транзакцию с явным указанием максимального изменения баланса. Если контракт попытается изменить баланс больше указанного — транзакция отклоняется. Это защита от drain-атак, но требует правильной настройки в SDK.
Read-only функции и их ограничения. define-read-only функции не могут изменять state, но могут читать state других контрактов через contract-call?. При сложных read-only вычислениях упирается в cost limit для read-only calls (значительно ниже, чем для обычных транзакций).
Процесс разработки
Проектирование. Определяем структуры данных (maps, vars), функции с правами доступа, стандарты токенов. Рисуем граф вызовов между контрактами — важно для анализа reentrancy (которого нет) и порядка деплоя.
Разработка в Clarinet. Итеративная разработка с тестами. Clarinet console для проверки логики. Покрытие тестами через simnet.callPublicFn, simnet.callReadOnlyFn.
Аудит перед деплоем. Clarity позволяет формальный анализ — проверяем все пути выполнения, убеждаемся в корректности post-conditions в клиенте.
Деплой. clarinet deployments generate --testnet → clarinet deployments apply. Контракты деплоятся в порядке зависимостей автоматически.
Сроки
Стандартный SIP-010 токен с кастомной логикой — 3-5 рабочих дней. Сложный протокол (DEX, lending, NFT marketplace с SIP-009) — от 2 до 4 недель. Контракты с Bitcoin-верификацией через Clarity Bitcoin — от 2 недель, зависит от сложности UTXO-логики.
Стоимость рассчитывается индивидуально после анализа требований.







