Розробка смарт-контрактів на Ink! (Polkadot)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка смарт-контрактів на Ink! (Polkadot)
Складний
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1308
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1221
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    921
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1149
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    612
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    886

Розроблення смарт-контрактів на Ink! (Polkadot)

Substrate-цепи — це не EVM. Розробники, які приходять в екосистему Polkadot з досвідом Solidity, першим ділом натикаються на принципово іншу модель виконання: WebAssembly-рантайм замість EVM, Rust замість Solidity, та cargo-contract замість Hardhat. Ink! — це embedded DSL поверх Rust, який компілюється в Wasm та розгортається на паллеті pallet-contracts. Переносити ментальні моделі з EVM напрямо сюди небезпечно.

Чим Ink! принципово відрізняється від Solidity

Перше, що режет глаз — модель хранилища. У Solidity mapping(address => uint256) — це просто слот у storage з keccak256-ключем. У Ink! кожне поле #[ink(storage)] транслюється у окремі Lazy-записи у дереві Merkel storage Substrate. Це означає:

  • Немає поняття «слот» у EVM-смислі — не socket packing
  • Доступ до Mapping<AccountId, Balance> — це get з off-chain state, не арифметика над 32-байтовим словом
  • StorageVec у Ink! 5.x ленивий за замовчуванням: елементи завантажуються тільки при явному читанні

Друге принципіальне відрізнення — модель викликів. У EVM msg.sender — завжди безпосередній вихідний. У Ink! self.env().caller() повертає попередній вихідний у ланцюзі. Reentrancy у Ink! фізично відключена за замовчуванням через ReentrancyGuard на рівні середовища виконання, якщо не передан флаг --allow-reentrant-calls явно. Але це не значить, що можна розслабиться — cross-contract виклики з CallBuilder все ще потребують обережного управління станом.

Третя особливість — життєвий цикл контракту. Ink! підтримує #[ink(message, payable)] для прийому нативного токена, #[ink(constructor)] для ініціалізації, та — унікально для Polkadot-екосистеми — set_code_hash() для оновлення коду контракту без зміни адреси. Це аналог UUPS proxy з EVM-світу, але вбудований у протокол.

Типові ошибки при написанні Ink!-контрактів

Неправильне використання Mapping vs StorageHashMap

У Ink! 4.x був ink::storage::Mapping, який не реалізує ітерацію по ключах (це зроблено навмисне — off-chain індексування через события, не on-chain). Розробники, звикли до EnumerableMap з OpenZeppelin, починають зберігати ключі у Vec<AccountId> рядом з Mapping, та це ломиться при спробі масштабування: Vec завантажується цілком при кожному читанні, що робить виклик O(n) за gas weight.

Правильне рішення — індексувати через ink::env::emit_event! та будувати off-chain state через Subsquid або SubQuery. Не намагайтесь воссоздати on-chain ітеруємі структури.

Weight vs Gas: принципово інша модель стоимості

EVM вважає газ операційно. Substrate вважає weight — це двомірний ресурс: ref_time (наносекунди CPU) та proof_size (байти доказу для light client). При деплое через cargo-contract потрібно явно вказувати --gas-limit в weight-одиницях, або використовувати dry_run для оцінки.

Паттерн, який регулярно приводить до проблем: розробник робить cargo-contract call без попередньої dry_run, контракт падає з OutOfGas, та команда починає гадати, що не так — хоча достатньо було запустити:

cargo contract call --dry-run --contract <address> --message transfer --args <args>

Оновлення через set_code_hash

Ink! дозволяє оновити код контракту через self.env().set_code_hash(&new_code_hash). Але storage layout нового коду повинен бути сумісний зі старим. Якщо змінити порядок полів у #[ink(storage)] — дані у хранилищі будуть читатися неверно. У EVM proxy-паттерні це називається storage collision — тут та ж проблема, але без bytecode-level інструментів для перевірки.

Використовуємо ink_storage_traits::StorageLayout derive-макроси та вручну перевіряємо storage layout через cargo-contract info --output-json перед кожним апгрейдом.

Як будуємо Ink!-проекти

Стек та інструменти

Інструмент Роль
cargo-contract 4.x Компіляція, деплой, виклики
substrate-contracts-node Локальна нода для розроблення
drink! Unit-тестування без ноди (mock runtime)
openbrush Бібліотека стандартів (PSP22, PSP34)
Subsquid Індексування подій контракту
polkadot.js API Frontend-інтеграція

Тестуємо на трьох рівнях:

  1. Unit-тесты через #[ink::test] — синхронний, mock-окружение, швидко
  2. Integration-тесты через drink! — реальний runtime Substrate без мережі, можна тестувати cross-contract виклики
  3. E2E-тесты на substrate-contracts-node — повний стек з реальними транзакціями

Стандарти токенів в екосистемі Polkadot

PSP22 — аналог ERC-20. PSP34 — аналог ERC-721. Обидва стандарти реалізовані через openbrush, який надає trait-based систему розширень — подібно міксинам у Solidity через diamond proxy, але без selector clashing проблем, тому що Ink! використовує blake2b-хеши для диспетчеризації повідомлень.

Одна тонкість: PSP22::transfer приймає data: Vec<u8> — це хук для отримувача (аналог ERC-777 hook). Якщо контракт-отримувач реалізує PSP22Receiver, він може відреагувати на вхідний переведення. Якщо не реалізує — виклик все одно проходить. Це відрізняється від ERC-777, де tokensReceived обов'язковий для контрактних адрес.

Процес роботи

Аналітика. Вивчаємо цільову Substrate-ланцюг: яка версія pallet-contracts, є ли кастомні chain extensions, який нативний токен, потрібна ли інтеграція з XCM для кросс-чейн викликів.

Проектування. Визначаємо storage layout (змінити після деплоя без міграції неможливо), события для індексування, message-інтерфейс. На цьому етапі закладаємо можливість апгрейду через set_code_hash — якщо потрібна.

Розроблення. Пишемо контракт з тестами на drink!. Покриття логіки — 90%+. Cross-contract взаємодії тестуємо окремо на substrate-contracts-node.

Аудит та деплой. Статичний аналіз через cargo clippy + ручний просмотр критичних путей. Деплой на тестнет (Rococo Contracts), верифікація через polkadot.js Apps.

Ориентиры по срокам

Простой контракт (PSP22 токен, 1-2 кастомних повідомлення): 3-5 днів включаючи тесты. Контракт середної складності з cross-contract викликами та апгрейдом: 1-2 тижні. Складний протокол з XCM-інтеграцією та кастомними chain extensions: від 1 місяця.

Конкретні часові рамки залежать від цільової цепи — на контрактних парачейнах типу Astar або Shiden можуть бути свої особливості конфігурації pallet-contracts.