Как писать смарт-контракты под Solana и не сойти с ума от бойлерплейта

25 авг 2026
5,118
1,965
56
2 недели

Если вы хоть раз пробовали писать программы под Solana на чистом Rust, то наверняка помните то чувство, когда на пять строк бизнес-логики приходится писать пятьдесят строк проверок аккаунтов, ручной десериализации через Borsh и валидации подписей. Ошиблись в порядке передачи ключей в транзакции — получаете ошибку выполнения, которую порой приходится дебажить часами.

В мире Ethereum разработчики давно привыкли к Solidity и готовым обвязкам вроде Hardhat или Foundry. В Solana аналогичным стандартом стал Anchor — фреймворк, который берет на себя всю рутину по парсингу данных, проверке прав доступа и генерации клиентского кода.

Что берет на себя Anchor

По своей сути Anchor представляет собой DSL (предметно-ориентированный язык) поверх Rust. Он не меняет внутреннюю модель исполнения Solana BPF, но упаковывает низкоуровневые вызовы в понятные декларативные макросы.

Когда вы пишете программу с Anchor, фреймворк решает четыре основные задачи:

  1. Сериализация и десериализация данных аккаунтов без ручного вызова методов распаковки.
  2. Проверка ограничений аккаунтов прямо в сигнатуре структуры через атрибуты (проверка владельца, проверка подписи, инициализация памяти).
  3. Сборка спецификации IDL (Interface Description Language) — аналога ABI в Ethereum.
  4. Создание готовых TypeScript и Rust клиентов для взаимодействия с контрактом прямо из фронтенда или тестов.

Как выглядит код на практике

Давайте взглянем на классический пример счетчика. В чистом Solana SDK вам пришлось бы вручную парсить массив байтов InstructionData, доставать слайсы аккаунтов, проверять is_signer у вызывающего адреса и валидировать адрес системной программы.

Реклама

Вот как этот же контракт выглядит на Anchor:

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod counter {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.authority = *ctx.accounts.authority.key;
        counter.count = start;
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = authority, space = 48)]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut, has_one = authority)]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
}

#[account]
pub struct Counter {
    pub authority: Pubkey,
    pub count: u64,
}

Разница бросается в глаза сразу. Вся логика валидации вынесена в структуры Initialize и Increment.

Атрибут #[account(init, payer = authority, space = 48)] говорит рантайму: создай новый аккаунт, выдели под него 48 байт, а оплату за аренду спиши с кошелька authority.

Конструкция #[account(mut, has_one = authority)] автоматически проверяет, что поле authority внутри структуры Counter совпадает с переданным аккаунтом authority, и что этот аккаунт действительно подписал транзакцию. Если подписи нет или передан чужой кошелек, выполнение прервется еще до входа в тело функции increment.

IDL и фронтенд без головной боли

Самая удобная вещь в связке с Anchor — это файл IDL в формате JSON. Компилятор собирает его автоматически при сборке проекта.

В IDL описаны все инструкции, структуры данных и возможные кастомные ошибки программы. На базе этого файла библиотека @anchor-lang/core генерирует типизированный интерфейс для JavaScript или TypeScript.

Вам больше не нужно помнить смещения байтов при сборке транзакции на клиенте. Вызов метода с фронтенда превращается в обычный вызов функции:

await program.methods
  .increment()
  .accounts({
    counter: counterPubkey,
    authority: wallet.publicKey,
  })
  .rpc();

TypeScript подсветит ошибки, если вы забудете передать обязательный аккаунт или укажете не тот тип аргумента.

Встроенный фаззинг для поиска уязвимостей

В смарт-контрактах цена ошибки слишком высока, поэтому тестирование играет особую роль. В состав CLI входит интеграция с инструментом Crucible для coverage-guided фаззинг-тестирования.

Команда anchor fuzz init генерирует тестовую обвязку, а anchor fuzz run запускает прогон со случайными входными данными, пытаясь найти граничные случаи, которые приводят к панике или невалидному состоянию аккаунтов. Это помогает ловить неочевидные переполнения или пропущенные проверки еще до выкатки в тестовую сеть.

# Инициализация фазз-тестов
anchor fuzz init program_name

# Запуск тестов в release-сборке
anchor fuzz run program_name test_name --release

Установка и начало работы

Для управления версиями тулчейна разработчики создали специальную утилиту AVM (Anchor Version Manager). Это спасает от конфликтов версий компилятора Solana и самого фреймворка, когда разные проекты требуют разных подверсий.

Ставится утилита одной строчкой:

curl -sSfL https://raw.githubusercontent.com/otter-sec/anchor/master/avm/install | sh

После установки можно переключаться на ночные сборки или фиксировать конкретные релизы для своих рабочих воркспейсов:

avm nightly
avm nightly --disable

Кому пригодится Anchor

Если вы только заходите в разработку под Solana, начинать без Anchor практически бессмысленно. Вы потратите недели на изобретение собственного велосипеда для парсинга аккаунтов и проверки дискриминаторов.

Фреймворк закрывает основные потребности:

  • Бэкенд-разработчикам на Rust дает строгую типизацию и защиту от типовых уязвимостей валидации.
  • Фронтендерам дает готовые TypeScript-типы и удобные методы для отправки транзакций.

Единственный случай, когда Anchor может показаться избыточным — написание микро-программ с экстремальной оптимизацией размера бинарника (compute budget limit), где важен буквально каждый байт инструкций. Во всех остальных сценариях это стандарт де-факто, который экономит сотни часов работы.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.