ボイラープレートの嵐に包まれずにSolana向けスマートコントラクトを書く方法
純粋なRustでSolana用のプログラムを書いたことがある人なら、きっと覚えているでしょう。5行のビジネスロジックを書くのに、アカウント検証チェックを50行も書かないといけない、手動でのBorshによるデシリアライズ、署名検証など。トランザクション内のキーの順序を間違えたら、デバッグに何時間もかかるランタイムエラーに遭遇します。
Ethereumの世界では、開発者はずっとSolidityやHardhat、Foundryのような готов-madeラッパーを使い慣れていました。Solanaでは、同様の標準となったのがAnchorです——データ解析、アクセス制御チェック、クライアントコード生成というすべての定型作業を処理するフレームワークです。
Anchorが代わりに処理してくれること
本質的に、AnchorはRustの上にあるDSL(ドメイン特化言語)です。Solanaの内部BPF実行モデルを変更するわけではありませんが、低レベルの呼び出しを理解しやすい宣言型マクロにパッケージ化します。
Anchorでプログラムを書くと、フレームワークが4つの主要なタスクを解決します:
- 手動のアンパッキングメソッド呼び出しなしでアカウントデータのシリアライズとデシリアライズを行います。
- 属性を通じて構造体シグネチャ内で直接アカウント制約検証を行います(所有者チェック、署名検証、メモリ初期化)。
- IDL(インターフェース記述言語)仕様書の生成——EthereumのABIに相当します。
- フロントエンドやテストからコントラクトと直接やり取りするための使用可能な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)] コンストラクトは、自動的に Counter 構造体内の authority フィールドが渡されたアカウント authority と一致すること、そしてこのアカウントが実際にトランザクションに署名していることを確認します。署名がない場合、または異なるウォレットが渡された場合は、関数本体 increment に入る前に実行が中止されます。
головної болі IDLとフロントエンドで頭を悩ませずに
Anchorバンドルで最も便利なのはJSON形式のIDLファイルです。コンパイラはプロジェクトをビルドする際に自動的にこれを生成します。
IDLは、プログラムのすべての命令、データ構造、可能なカスタムエラータイプを記述します。このファイルを基に、@anchor-lang/core ライブラリがJavaScriptまたはTypeScript用の型付きインターフェースを生成します。
クライアント側でトランザクションを組み立てる際にバイトオフセットを覚えておく必要はもうありません。フロントエンドからメソッドを呼び出すことは通常の関数呼び出しになります:
await program.methods
.increment()
.accounts({
counter: counterPubkey,
authority: wallet.publicKey,
})
.rpc();
TypeScriptは、必要なアカウントの受け渡しを忘れた場合や誤った引数タイプを指定した場合にエラーをハイライトします。
脆弱性発見のための組み込みファジング
スマートコントラクトでは、ミスのコストはあまりにも高いため、テストが特別な役割を果たします。CLIには、カバレッジガイド付きファジングテスト用のCrucibleツールとの統合が含まれています。
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コンパイラとフレームワーク自体の間のバージョンの競合を回避できます異なるプロジェクトが異なるサブバージョンを必要とする場合です。
ユーティリティは1行でインストールできます:
curl -sSfL https://raw.githubusercontent.com/otter-sec/anchor/master/avm/install | sh
インストール後、ナイトリービルドに切り替えたり、ワークスペースの特定のリリースにピン留めしたりできます:
avm nightly
avm nightly --disable
Anchor誰が恩恵を受けるか
Solana開発を始めたばかりなら、Anchorなしで始めることは事実上無意味です。アカウント解析と識別子検証のために車輪の再発明に何週間も費やすことになります。
このフレームワークは主なニーズをカバーしています:
- Rustバックエンド開発者に厳密な型付けと、一般的な検証脆弱性パターンからの保護を提供します。
- フロントエンド開発者に готовые TypeScript型とトランザクション送信のための便利なメソッドを提供します。
Anchorが過剰に見える唯一のケースは、極端なバイナリサイズ最適化(計算バジェットリミット)を持つマイクロプログラムを書く場合で、命令のすべてのバイトが実際に重要になります。他のすべてのシナリオでは、何百もの作業時間を節約する事実上の標準です。
関連プロジェクト