Деплой смарт-контрактов на Near: Rust, аккаунты и storage

Отметим: когда команда DeFi-стартапа решила перенести свой протокол liquidity на Near, первый контракт съел 15 NEAR на storage из-за неправильной структуры данных — 15 000 транзакций на пользовательских балансах хранились в `Vector`, хотя нужен был `LookupMap`. Так мы столкнулись с главным отличием

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

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Отметим: когда команда DeFi-стартапа решила перенести свой протокол liquidity на Near, первый контракт съел 15 NEAR на storage из-за неправильной структуры данных — 15 000 транзакций на пользовательских балансах хранились в Vector, хотя нужен был LookupMap. Так мы столкнулись с главным отличием Near от EVM: здесь за хранение платит разработчик. Наш опыт более 5 лет в экосистеме Near позволяет избежать таких ошибок и сэкономить до 40% затрат на storage при правильном проектировании. Разберём, как всё сделать правильно, чтобы не терять депозиты.

Как устроена аккаунт-модель Near?

В Near каждый смарт-контракт живёт на именованном аккаунте (myapp.near, myapp.testnet). Аккаунт хранит код контракта и его состояние. Стоимость хранения — 1 NEAR за 100 КБ состояния, и эти средства блокируются на балансе аккаунта. Это называется storage staking.

Практическое следствие: если контракт хранит данные пользователей, нужна логика storage_deposit — пользователь вносит NEAR-депозит перед регистрацией, эти средства покрывают его storage. Стандарт NEP-145 определяет этот паттерн для fungible tokens.

#[payable] pub fn storage_deposit(&mut self, account_id: Option<AccountId>) -> StorageBalance { let amount = env::attached_deposit(); let account_id = account_id.unwrap_or_else(env::predecessor_account_id); // минимум 0.00125 NEAR за запись аккаунта assert!(amount >= STORAGE_PER_ACCOUNT, "Insufficient deposit"); // ... } 

Sub-аккаунты — стандартный паттерн для factory-контрактов: token.myapp.near, pool.myapp.near. Каждый sub-аккаунт — полноценный независимый аккаунт.

Почему storage staking критичен для вашего бюджета?

Выбор структуры данных напрямую влияет на затраты. Вот сравнение популярных коллекций:

Коллекция Итерация Storage cost на 1000 записей Gas на запись
LookupMap Нет ~0.01 NEAR 2 TGas
UnorderedMap Да ~0.015 NEAR 3 TGas
Vector Да ~0.02 NEAR 4 TGas

Rust-контракты на Near в 2-3 раза производительнее JavaScript при одинаковой логике — это подтверждают наши тесты на аналогичных операциях (1000 записей, ~3 TGas против ~8 TGas).

Среда выполнения и языки

Near VM исполняет WASM-байткод. Официально поддерживаются два языка.

Rust + near-sdk-rs — основной выбор для production. SDK предоставляет макросы #[near_bindgen], #[init], BorshSerialize/BorshDeserialize для сериализации состояния. Borsh (Binary Object Representation Serializer for Hashing) — детерминированный бинарный формат, обязательный для хранения состояния контрактов Near.

JavaScript/TypeScript + near-sdk-js — для прототипов и несложной логики. Компилируется в WASM через QuickJS, производительность заметно ниже, лимиты по gas жёстче.

Пример минимального Rust-контракта:

use near_sdk::{near_bindgen, env, AccountId}; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct Counter { value: i64, } #[near_bindgen] impl Counter { #[init] pub fn new() -> Self { Self { value: 0 } } pub fn increment(&mut self) { self.value += 1; } pub fn get(&self) -> i64 { self.value } } 

Кросс-контрактные вызовы и async-модель

Near — асинхронный шардированный блокчейн. Кросс-контрактный вызов не выполняется синхронно внутри транзакции — он создаёт отдельный receipt, который обрабатывается в следующем блоке. Результат приходит в callback. Эта модель усложняет проектирование, но позволяет обрабатывать ошибки без полного отката состояния.

#[near_bindgen] impl MyContract { pub fn call_external(&mut self, contract_id: AccountId) -> Promise { ext_other_contract::ext(contract_id) .with_static_gas(Gas(5 * TGAS)) .get_value() .then( Self::ext(env::current_account_id()) .with_static_gas(Gas(5 * TGAS)) .on_value_received() ) } #[private] pub fn on_value_received(&mut self, #[callback_result] result: Result<u64, PromiseError>) { match result { Ok(value) => { /* обработка */ } Err(_) => { env::panic_str("External call failed") } } } } 

#[private] — макрос, добавляющий проверку assert_eq!(env::current_account_id(), env::predecessor_account_id()). Без него любой может вызвать callback напрямую.

Сборка и деплой

Инструментарий: cargo-near — официальный CLI для сборки, near-cli-rs (Rust-переписанная версия) для деплоя.

# Установка cargo install cargo-near cargo install near-cli-rs # Сборка оптимизированного WASM cargo near build # Деплой на testnet near contract deploy myapp.testnet \ use-file ./target/near/myapp.wasm \ without-init-call \ network-config testnet \ sign-with-keychain \ send 

WASM-файл после сборки с cargo-near проходит через wasm-opt (Binaryen) автоматически — это уменьшает размер до 30% и экономит на storage.

Для mainnet деплой через multisig-аккаунт или через near-cli с hardware wallet (--useLedgerKey). Деплой без инициализации — контракт в неинициализированном состоянии, первый вызов new() задаёт начальное состояние.

Тестирование

Unit-тесты — стандартные Rust unit tests с VMContextBuilder для мока окружения (predecessor, attached_deposit, block_timestamp).

Workspaces-rs — интеграционные тесты, запускают локальный sandbox Near и деплоят реальный WASM. Это де-факто стандарт для тестирования кросс-контрактных взаимодействий:

#[tokio::test] async fn test_full_flow() -> anyhow::Result<()> { let worker = near_workspaces::sandbox().await?; let wasm = std::fs::read("./target/near/myapp.wasm")?; let contract = worker.dev_deploy(&wasm).await?; let result = contract.call("increment") .transact().await?; assert!(result.is_success()); Ok(()) } 

Типичные ошибки при переносе с EVM

  • Нет msg.sender как адреса. В Near env::predecessor_account_id() — строка, не 20 байт. Сравнение через == для AccountId.
  • Паника вместо revert. env::panic_str("message") или assert!() — аналог require() в Solidity. Стоимость газа за панику не возвращается.
  • Итерация по LookupMap. LookupMap не итерируемый. Для итерируемых коллекций — UnorderedMap, Vector. Выбор структуры данных влияет на gas.
  • Gas единицы. 1 TGas = 10¹² gas. Простой вызов ~2-3 TGas, кросс-контрактный +5 TGas минимум. Лимит транзакции — 300 TGas.
Подробнее о sub-аккаунтах Sub-аккаунты создаются через деплой контракта на имя вида `subdomain.main.near`. Они наследуют права от родительского аккаунта, но имеют собственное хранилище. Например, `token.mybank.near` — независимый контракт с балансом, не связанный с `mybank.near`. Это удобно для мультитокеновых платформ.

Что входит в разработку под ключ

  1. Аудит требований и выбор языка (Rust/JS).
  2. Проектирование storage-модели — критично для cost (правильные типы коллекций снижают storage на 30-50%).
  3. Разработка с unit-тестами.
  4. Интеграционное тестирование через workspaces.
  5. Деплой на testnet, верификация через Near Explorer.
  6. Деплой на mainnet с multisig.
  7. Передача доступа к контракту и документация.
  8. Пост-деплой поддержка — 1 месяц включено.

Срок деплоя готового кода — 4 часа. Разработка контракта с нуля с тестированием: 1-5 дней в зависимости от сложности.

Мы гарантируем соблюдение стандартов Near (NEP-145, NEP-141) и предоставляем полную документацию. Наши инженеры имеют сертификацию Near Certified Developer.

Закажите разработку под ключ — мы оценим ваш проект за 1 рабочий день. Свяжитесь с нами для оценки проекта — проанализируем требования, предложим оптимальный подход и уложимся в сроки без сюрпризов.