Разработка смарт-контрактов на Cairo (StarkNet)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка смарт-контрактов на Cairo (StarkNet)
Сложная
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка смарт-контрактов на Cairo (StarkNet)

Cairo — это не «ещё один язык для смарт-контрактов». Это язык, написанный с нуля под одну задачу: генерировать доказуемые вычисления для STARK-пруверов. Если вы привыкли к Solidity, первые недели с Cairo будут болезненными. Если вы привыкли к Rust — немного проще, но всё равно потребуют перестройки мышления.

Почему Cairo — не просто «StarkNet Solidity»

Главное, что нужно понять: Cairo 1 (текущая версия) компилируется в Sierra — промежуточное представление, которое затем компилируется в CASM (Cairo Assembly). Sierra гарантирует, что любая программа завершима — это убирает класс атак, когда контракт специально потребляет весь газ и откатывается, оставляя секвенсер без вознаграждения.

На практике это значит: нет infinite loops без явного счётчика, нет произвольных jumps. Это ограничение, с которым приходится работать.

Вторая принципиальная вещь: StarkNet — это ZK-роллап, и каждая транзакция в конечном счёте подтверждается STARK-доказательством на L1 (Ethereum). Это даёт дешевизну исполнения на L2 при гарантиях безопасности L1. Но модель стоимости газа в StarkNet отличается от EVM: газ считается в «шагах Cairo VM», а не в EVM opcodes.

Типичные ошибки при переходе с Solidity на Cairo

Storage — не mapping, а отдельный мир

В Solidity mapping(address => uint256) — привычная конструкция. В Cairo storage работает через StorageMap с явной сериализацией. Проблема возникает, когда разработчик пытает хранить сложные структуры с вложенными коллекциями: Cairo требует ручной реализации Store трейта для кастомных типов, иначе не скомпилируется.

Реальный кейс: контракт токена с balances: LegacyMap<ContractAddress, u256> работает штатно. Контракт с positions: LegacyMap<ContractAddress, UserPosition>, где UserPosition — кастомная структура, требует #[derive(Store)] и корректную реализацию. Если структура содержит вложенный Array<u256>, хранить её напрямую в storage нельзя — Cairo не поддерживает динамические типы в storage. Это ломает паттерны из Solidity, где mapping(address => uint256[]) работает из коробки.

Решение: декомпозировать структуры в плоские маппинги. positions_amount: LegacyMap<ContractAddress, u256>, positions_token: LegacyMap<ContractAddress, ContractAddress> — вместо одного маппинга с nested struct.

Reentrancy в StarkNet — другая механика

В EVM reentrancy работает через call stack: контракт A вызывает контракт B, B вызывает обратно A до завершения первого вызова. В StarkNet в текущей архитектуре (версия протокола 0.13+) reentrancy тоже возможна через call_contract_syscall. Но есть нюанс: состояние storage обновляется немедленно при записи, а не в конце транзакции (в отличие от некоторых интерпретаций).

Паттерн защиты такой же: checks-effects-interactions. Сначала обновляем balance пользователя, потом делаем внешний вызов. ReentrancyGuard в Cairo реализуется через storage-флаг:

#[storage]
struct Storage {
    _reentrancy_guard: bool,
}

fn _lock(ref self: ContractState) {
    assert(!self._reentrancy_guard.read(), 'ReentrancyGuard: reentrant call');
    self._reentrancy_guard.write(true);
}

fn _unlock(ref self: ContractState) {
    self._reentrancy_guard.write(false);
}

OpenZeppelin Cairo (github.com/OpenZeppelin/cairo-contracts) предоставляет готовый ReentrancyGuardComponent. Используем его, а не изобретаем свой.

Целочисленные типы и overflow

Cairo 1 использует u256, u128, u64, u32, u8, felt252. Тип felt252 — поле простого числа (порядка 2^251), не знаковое целое. Арифметика по модулю: если складываете два felt252 и результат выходит за границу поля, получаете wrapping — без паники, без ошибки. Это ловушка для тех, кто переносит логику с Solidity, где uint256 по умолчанию паникует при overflow в последних версиях.

Для финансовой логики используем u256 с явными проверками через u256_overflow_add, или checked_add на целочисленных типах. Никогда не храним суммы токенов в felt252.

Компоненты OpenZeppelin Cairo — архитектурный паттерн

Cairo-контракты в экосистеме StarkNet строятся на компонентной архитектуре. OpenZeppelin Cairo предоставляет компоненты для ERC-20, ERC-721, ERC-1155, Ownable, AccessControl, Upgradeable и других паттернов.

Компонент — это переиспользуемая единица логики со своим storage-пространством, которая подмешивается в контракт через #[starknet::contract] и component!() макрос. Важная деталь: у каждого компонента своё storage-namespace, коллизий не возникает. Это решает проблему storage collision, знакомую по proxy-паттернам в Solidity.

Пример инициализации ERC-20 с Ownable:

#[starknet::contract]
mod MyToken {
    use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
    use openzeppelin::access::ownable::OwnableComponent;

    component!(path: ERC20Component, storage: erc20, event: ERC20Event);
    component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);

    #[storage]
    struct Storage {
        #[substorage(v0)]
        erc20: ERC20Component::Storage,
        #[substorage(v0)]
        ownable: OwnableComponent::Storage,
    }
}

Этот паттерн отличается от Solidity-наследования: здесь нет иерархии, есть композиция. Попытка реализовать наследование через вызов функций родительского контракта в Cairo не работает так же, как в Solidity.

Апгрейдабельность: proxy или replace_class_syscall

В StarkNet есть нативный механизм апгрейда: replace_class_syscall. Контракт может заменить собственный класс-хэш на новый, при этом storage остаётся. Это похоже на UUPS-паттерн, но без отдельного proxy-контракта.

Риски те же: если новая версия класса меняет layout storage (порядок переменных в #[storage]), данные могут быть интерпретированы неверно. В Cairo storage addresses вычисляются из имён переменных (хэш от имени), поэтому переименование переменной = потеря данных в ней после апгрейда.

OpenZeppelin предоставляет UpgradeableComponent, который ограничивает вызов upgrade() только owner-ом и эмитит событие:

fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
    self.ownable.assert_only_owner();
    self.upgradeable.upgrade(new_class_hash);
}

Перед апгрейдом в mainnet — обязательный тест на fork. Проверяем, что новый класс читает старый storage корректно.

Тестирование Cairo-контрактов

Экосистема инструментов моложе EVM, но быстро растёт.

Starknet Foundry (snforge) — основной инструмент тестирования. Синтаксис тестов близок к Foundry для EVM:

#[cfg(test)]
mod tests {
    use snforge_std::{declare, ContractClassTrait, start_cheat_caller_address};

    #[test]
    fn test_transfer() {
        let contract = declare("MyToken").unwrap();
        let (contract_address, _) = contract.deploy(@array![]).unwrap();
        // тесты
    }
}

Fuzz-тестирование в snforge поддерживается через #[fuzzer] атрибут. Менее развито, чем Foundry fuzzing на EVM, но базовые сценарии покрывает.

Тестовые сети. Sepolia — текущий рекомендованный тестнет StarkNet. Goerli выведен из эксплуатации. Для локальной разработки используем Katana (из пакета dojo) или starknet-devnet-rs.

Инструмент Назначение Статус
snforge Unit/integration тесты Активно развивается
sncast CLI для деплоя и взаимодействия Стабилен
Katana Локальная нода StarkNet Активно развивается
starknet-devnet-rs Альтернативная локальная нода Стабилен
Voyager Block explorer mainnet/testnet Продакшн

Account Abstraction по умолчанию

В StarkNet нет понятия EOA (Externally Owned Account) в смысле EVM. Каждый аккаунт — это смарт-контракт, реализующий интерфейс IAccount. Это Account Abstraction (AA) по умолчанию, без EIP-4337.

Для разработчика это значит: в контракте нельзя использовать tx.origin в смысле EOA (его просто нет), нет ECDSA-подписей hardcoded в протоколе на уровне аккаунта. Аккаунт может реализовать любую схему подписи — мультисиг, passkey, сессионные ключи.

Паттерн сессионных ключей особенно интересен для игровых контрактов: пользователь подписывает один раз выдачу сессионного ключа, потом игра делает транзакции от его имени в рамках разрешённого scope. Без накладных расходов EIP-4337 bundler-инфраструктуры.

Процесс работы над Cairo-контрактом

Аналитика. Разбираем требования под Cairo-специфику: какие данные идут в storage, какая логика требует межконтрактных вызовов (они дороже, чем internal вызовы), нужна ли апгрейдабельность.

Проектирование storage. Самый критичный этап. Неправильный layout storage исправить после деплоя без апгрейда нельзя.

Разработка. Cairo 2.x, Scarb как build tool, OpenZeppelin Cairo как база. Для DeFi-логики — изучаем существующие аудированные контракты в экосистеме (Ekubo, JediSwap, zkLend).

Тестирование. Snforge unit-тесты, integration тесты на Katana, manual тесты на Sepolia.

Аудит. Экосистема аудиторов для Cairo меньше, чем для EVM. Специализированные команды: Trail of Bits (имеют Cairo практику), ChainSecurity, Nethermind Security.

Деплой. Через sncast или starknet.js. Declare (публикация класса) + Deploy (создание инстанса) — два шага вместо одного в EVM.

Сроки

Разработка Cairo-контракта сопоставимой сложности занимает в 1.5-2 раза дольше, чем аналог на Solidity, если команда переходит с EVM впервые. Это время на освоение Cairo-специфики, а не показатель сложности самого языка.

Оценочно: базовый ERC-20 с кастомной логикой — 3-5 дней. DeFi-протокол (AMM, lending) — 3-8 недель. Полная разработка с аудитом и деплоем на mainnet — от 2 месяцев.

Стоимость рассчитывается после технического брифинга и анализа требований.