Деплой смарт-контрактів у Polkadot/Kusama

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Деплой смарт-контрактів у Polkadot/Kusama
Середній
від 4 годин до 2 днів
Часті запитання

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

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

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

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

Розгортування смарт-контрактів на Polkadot/Kusama

Якщо ви працюєте з EVM та вперше дивитесь на Polkadot — забудьте про аналогії з Ethereum. Тут немає єдиної VM для всієї мережі. Смарт-контракти розгортуються не в Relay Chain, а в парачейни зі включеним модулем pallet-contracts. Kusama — це "канарикова мережа" Polkadot з швидшим управлінням та менш суворими вимогами до слотів. Для тестування production-логіки використовуємо Kusama, для остаточного розгортування — Polkadot або спеціалізований парачейн (Astar, Phala, Aleph Zero).

Ink! — мова контрактів екосистеми Substrate

Контракти для pallet-contracts пишуться на ink! — вбудованому DSL поверх Rust. Це не клон Solidity та не транспайлер; це нативний Rust з макросами, які генерують метадані контракту та ABI-сумісний інтерфейс.

#[ink::contract]
mod vault {
    use ink::storage::Mapping;

    #[ink(storage)]
    pub struct Vault {
        balances: Mapping<AccountId, Balance>,
        owner: AccountId,
    }

    impl Vault {
        #[ink(constructor)]
        pub fn new() -> Self {
            Self {
                balances: Mapping::default(),
                owner: Self::env().caller(),
            }
        }

        #[ink(message, payable)]
        pub fn deposit(&mut self) {
            let caller = self.env().caller();
            let value = self.env().transferred_value();
            let current = self.balances.get(caller).unwrap_or(0);
            self.balances.insert(caller, &(current + value));
        }

        #[ink(message)]
        pub fn withdraw(&mut self, amount: Balance) -> Result<(), Error> {
            let caller = self.env().caller();
            let balance = self.balances.get(caller).unwrap_or(0);
            if balance < amount {
                return Err(Error::InsufficientBalance);
            }
            self.balances.insert(caller, &(balance - amount));
            self.env().transfer(caller, amount)
                .map_err(|_| Error::TransferFailed)
        }
    }
}

Кілька ключових відмінностей від Solidity, які ламають інтуїцію EVM-розробника:

  • Немає msg.value у звичайних функціях — тільки в #[ink(message, payable)]. Виклик payable-функції без атрибуту повертає помилку.
  • Storage через Mapping без ітерації — немає mapping.keys(). Якщо потрібні списки — зберігайте окремо як Vec<AccountId>.
  • AccountId замість address — 32 байти, не 20. Адреси у форматі SS58, не hex.
  • Balance це u128, не uint256. Але практично різниці немає.

Інструментарій: cargo-contract

cargo install cargo-contract --force
cargo contract new my_contract
cd my_contract

# Збірка: генерує .contract, .wasm, .json (ABI)
cargo contract build --release

# Запуск тестів
cargo test

Файл .contract — це архів з WASM bytecode та метаданими. Це те, що ми розгортуємо.

Середовища розгортування та тестування

Локальне тестування: substrate-contracts-node

substrate-contracts-node --dev --tmp

Запускає однонодову мережу з попередньо профінансованими аккаунтами (Alice, Bob, Charlie — як Hardhat accounts[0]). Contracts UI на contracts-ui.substrate.io або програмно через @polkadot/api.

Тестнет: Contracts on Rococo

Rococo — тестнет Polkadot з парачейном contracts спеціально для тестування ink!. Faucet: paritytech.github.io/polkadot-testnet-faucet.

Production: Astar Network

Astar — найзрілішій парачейн зі pallet-contracts в екосистемі Polkadot. Підтримує як ink! (Wasm), так і EVM (Solidity) контракти в одній мережі, cross-VM виклики через XVM. Для більшості production use cases на Polkadot — розгортуємо на Astar.

Розгортування через polkadot.js

import { ApiPromise, WsProvider, Keyring } from '@polkadot/api';
import { CodePromise } from '@polkadot/api-contract';
import * as fs from 'fs';

async function deploy() {
  const provider = new WsProvider('wss://rpc.astar.network');
  const api = await ApiPromise.create({ provider });

  const keyring = new Keyring({ type: 'sr25519' });
  const deployer = keyring.addFromUri(process.env.MNEMONIC!);

  const wasm = fs.readFileSync('./target/ink/vault.wasm');
  const abi = JSON.parse(fs.readFileSync('./target/ink/vault.json', 'utf8'));

  const code = new CodePromise(api, abi, wasm);

  const gasLimit = api.registry.createType('WeightV2', {
    refTime: 30_000_000_000n,
    proofSize: 1_000_000n,
  });
  const storageDepositLimit = null; // автоматично

  const tx = code.tx.new({ gasLimit, storageDepositLimit });

  await new Promise<void>((resolve, reject) => {
    tx.signAndSend(deployer, ({ contract, status }) => {
      if (status.isInBlock) {
        console.log('Contract address:', contract?.address.toString());
        resolve();
      }
    }).catch(reject);
  });

  await api.disconnect();
}

Gas модель: WeightV2

На відміну від EVM, де gas — одне число, Substrate використовує двомірний weight:

  • refTime — час вичислень (пікосекунди)
  • proofSize — розмір storage proof для light clients

При розгортуванні потрібно оцінити обидва параметри. Спосіб: сухий прогін (contracts.instantiate з estimateGas: true) або користуємо cargo-contract:

cargo contract instantiate --dry-run \
  --constructor new \
  --args \
  --suri //Alice \
  --url ws://127.0.0.1:9944

Storage deposit

pallet-contracts вимагає депозиту за займане сховище — аналог EIP-1153 transient storage, але постійний. Вартість пропорційна байтам у сховищі контракту. При видаленні сховища (через ink::env::set_contract_storage::<K, ()>(&key, &())) депозит повертається. Це важливо для довгоживих контрактів з великим state.

Типові помилки при міграції з EVM

Паттерн EVM Рішення ink!
mapping.length() Окремий лічильник або Vec
block.timestamp self.env().block_timestamp() (u64, мілісекунди)
msg.sender self.env().caller()
payable за замовчуванням Явний атрибут #[ink(message, payable)]
Events з indexed #[ink(topic)] на полі event
require(cond, "msg") assert! або Result<_, Error>

Cross-contract виклики в ink! вимагають імпорту ABI викликаного контракту та явного вказання gas лімиту — немає автоматичного forwarding як у Solidity {gas: gasleft()}.