Розгортування смарт-контрактів у Cosmos (CosmWasm)
CosmWasm — це не EVM. Якщо ви приходите з досвідом Solidity, перше, що збиває з толку: контракти написані на Rust, компілюються в WebAssembly і взаємодіють через message-passing, а не через прямі виклики функцій. Архітектурна модель ближча до actor model, ніж до викликів методів об'єкту. Це важливо зрозуміти до написання першого рядка коду.
Середовище розробки та toolchain
Для роботи з CosmWasm потрібен Rust з цільовою платформою wasm32-unknown-unknown:
rustup target add wasm32-unknown-unknown
cargo install cosmwasm-check # валідація скомпільованого wasm
cargo install cargo-generate # шаблони проектів
Стандартний шаблон проекту через cw-template:
cargo generate --git https://github.com/CosmWasm/cw-template.git --name my-contract
Структура CosmWasm контракту
Контракт складається з трьох обов'язкових точок входу:
// src/contract.rs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> { ... }
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> { ... }
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(
deps: Deps,
env: Env,
msg: QueryMsg,
) -> StdResult<Binary> { ... }
Ключова відмінність від Solidity: DepsMut (для запису) і Deps (тільки читання) явно розділені. Фізично неможливо випадково записати у storage з query-функції — компілятор це не дозволить.
Управління станом через cw-storage-plus:
use cw_storage_plus::{Item, Map};
pub const CONFIG: Item<Config> = Item::new("config");
pub const BALANCES: Map<&Addr, Uint128> = Map::new("balances");
Map з адресою як ключем — стандартний паттерн для зберігання балансів. Немає mapping(address => uint256) з Solidity, але семантика та ж.
Компіляція та оптимізація
Raw cargo build --target wasm32-unknown-unknown --release дає бінарник з debug символами і неоптимізованим розміром. Для production використовуємо офіційний оптимізатор:
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.15.0
Результат — artifacts/my_contract.wasm. Розмір має бути < 800KB (hard limit більшості мереж). Перевірка:
cosmwasm-check artifacts/my_contract.wasm
Розгортування на мережі, сумісній з Cosmos
Покажу на прикладі Neutron (однієї з основних CosmWasm-мереж для DeFi). Використовуємо neutrond CLI або CosmJS:
Через CLI:
# Store — завантаження bytecode в мережу
neutrond tx wasm store artifacts/my_contract.wasm \
--from <wallet> \
--gas auto --gas-adjustment 1.3 \
--fees 5000untrn \
--chain-id neutron-1 \
--node https://rpc-lb.neutron.org:443
# Отримуємо code_id з подій транзакції
# Instantiate — створення інстанцу контракту
neutrond tx wasm instantiate <CODE_ID> \
'{"admin": "neutron1...", "param": "value"}' \
--label "my-contract-v1" \
--admin <wallet_address> \
--from <wallet> \
--gas auto --gas-adjustment 1.3 \
--fees 5000untrn \
--chain-id neutron-1
Через CosmJS (для автоматизації):
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: "neutron",
});
const client = await SigningCosmWasmClient.connectWithSigner(
"https://rpc-lb.neutron.org:443",
wallet
);
// Завантаження
const uploadResult = await client.upload(senderAddress, wasmBinary, "auto");
const codeId = uploadResult.codeId;
// Instantiate
const { contractAddress } = await client.instantiate(
senderAddress,
codeId,
{ admin: senderAddress },
"my-contract-v1",
"auto"
);
Контракти з можливістю міграції
CosmWasm має вбудований механізм міграції — аналог upgradeable proxy в EVM, але простіший. Потрібно додати точку входу:
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
deps: DepsMut,
env: Env,
msg: MigrateMsg,
) -> Result<Response, ContractError> {
// Логіка міграції стану, якщо потрібна
Ok(Response::default())
}
При розгортуванні інстанцу з адресою --admin ця адреса може викликати MigrateContract з новим code_id. Без --admin — контракт immutable. Для production протоколів admin має бути multisig (через cosmwasm-multisig або DAO DAO).
Орієнтири по термінах
Контракт з готовою логікою, написаний досвідченим Rust-розробником: розгортування та верифікація 4–8 годин. Якщо потрібна розробка з нуля або портування з Solidity — 1–2 дні залежно від складності.







