>_ DevTrendspl

Język

Strona główna

Języki

Sekcje

Frontend Backend Mobilne DevOps AI / ML GameDev Blockchain Systemy wbudowane Bezpieczeństwo
Rust

Jak pisać smart kontrakty na Solanie bez szaleństwa od boilerplate'u

Jeśli kiedykolwiek próbowałeś pisać programy dla Solany w czystym Rust, prawdopodobnie pamiętasz to uczucie, gdy pięć linii logiki biznesowej wymagało napisania pięćdziesięciu linii sprawdzających walidację kont, ręcznej deserializacji przez Borsh i walidacji podpisów. Pomyliłeś kolejność kluczy w transakcji — dostajesz błąd runtimeowy, który czasem zajmuje godziny, żeby go zdebugować.

W świecie Ethereum programiści od dawna przyzwyczajeni są do Solidity i gotowych wrapperów jak Hardhat czy Foundry. Na Solanie analogicznym standardem stał się Anchor — framework, który zajmuje się całą rutynową pracą: parsowaniem danych, sprawdzaniem kontroli dostępu i generowaniem kodu klienta.

Za co Anchor się bierze

W istocie Anchor to DSL (domain-specific language) na szczycie Rust. Nie zmienia wewnętrznego modelu wykonawczego BPF Solany, ale pakuje niskopoziomowe wywołania w zrozumiałe deklaratywne makra.

Gdy piszesz program z Anchor, framework rozwiązuje cztery główne zadania:

  1. Serializacja i deserializacja danych kont bez ręcznego wywoływania metod rozpakowujących.
  2. Walidacja ograniczeń kont bezpośrednio w sygnaturze struktury przez atrybuty (sprawdzenie właściciela, weryfikacja podpisu, inicjalizacja pamięci).
  3. Generowanie specyfikacji IDL (Interface Description Language) — odpowiednika ABI z Ethereuma.
  4. Tworzenie gotowych do użycia klientów TypeScript i Rust do interakcji z kontraktem bezpośrednio z frontendu lub testów.

Jak wygląda kod w praktyce

Spójrzmy na klasyczny przykład licznika. W czystym SDK Solany musiałbyś ręcznie parsować tablicę bajtów InstructionData, wyodrębniać wycinki kont, sprawdzać is_signer na adresie wywołującego i walidować adres programu systemowego.

Oto jak wygląda ten sam kontrakt w 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,
}

Różnica jest od razu widoczna. Cała logika walidacji została przeniesiona do struktur Initialize i Increment.

Atrybut #[account(init, payer = authority, space = 48)] mówi runtime'owi: utwórz nowe konto, zaalokuj dla niego 48 bajtów i obciążaj czynsz za to konto do portfela authority.

Konstrukcja #[account(mut, has_one = authority)] automatycznie sprawdza, czy pole authority wewnątrz struktury Counter odpowiada przekazanemu kontu authority, oraz czy to konto faktycznie podpisało transakcję. Jeśli podpisu nie ma lub przekazano inny portfel, wykonanie przerwie się przed wejściem do ciała funkcji increment.

IDL i frontend bez bólu głowy

Najwygodniejszą rzeczą w pakiecie Anchor jest plik IDL w formacie JSON. Kompilator składa go automatycznie podczas budowania projektu.

IDL opisuje wszystkie instrukcje, struktury danych i możliwe niestandardowe typy błędów programu. Na podstawie tego pliku biblioteka @anchor-lang/core generuje typowane interfejsy dla JavaScript lub TypeScript.

Nie musisz już pamiętać przesunięć bajtowych podczas składania transakcji po stronie klienta. Wywołanie metody z frontendu staje się zwykłym wywołaniem funkcji:

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

TypeScript podświetli błędy, jeśli zapomnisz przekazać wymagane konto lub podasz zły typ argumentu.

Wbudowany fuzzing do wyszukiwania podatności

W smart kontraktach koszt błędu jest zbyt wysoki, dlatego testowanie odgrywa szczególną rolę. CLI zawiera integrację z narzędziem Crucible do testowania fuzzingowego z prowadzeniem pokrycia kodu.

Polecenie anchor fuzz init generuje szkielet testowy, a anchor fuzz run uruchamia go z losowymi danymi wejściowymi, próbując znaleźć przypadki brzegowe prowadzące do panik lub nieprawidłowych stanów kont. Pomaga to wychwycić nieoczywiste przepełnienia lub pominięte sprawdzenia przed wdrożeniem na testnet.

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

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

Instalacja i rozpoczęcie pracy

Do zarządzania wersjami toolchaina programiści stworzyli specjalne narzędzie o nazwie AVM (Anchor Version Manager). Chroni cię przed konfliktami wersji między kompilatorem Solany a samym frameworkiem, gdy różne projekty wymagają różnych sub-wersji.

Narzędzie instaluje się jedną linią:

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

Po instalacji możesz przełączyć się na nightly builds lub przypiąć konkretne wydania dla swoich workspace'ów:

avm nightly
avm nightly --disable

Kto skorzysta na Anchor

Jeśli dopiero zaczynasz z programowaniem na Solanie, rozpoczynanie bez Anchor jest praktycznie bezcelowe. Spędzisz tygodnie na wymyślaniu koła na nowo dla parsowania kont i walidacji dyskryminatorów.

Framework pokrywa główne potrzeby:

  • Daje programistom backendu w Rust ścisłe typowanie i ochronę przed typowymi wzorcami podatności walidacyjnych.
  • Daje programistom frontendu gotowe typy TypeScript i wygodne metody do wysyłania transakcji.

Jedynym przypadkiem, gdy Anchor może wydawać się nadmiarowy, jest pisanie mikro-programów z skrajną optymalizacją rozmiaru binarnego (limit budżetu obliczeniowego), gdzie naprawdę każdy bajt instrukcji ma znaczenie. We wszystkich innych scenariuszach to de facto standard, który oszczędza setki godzin pracy.

Powiązane projekty