本文へスキップ
BecomeCoder

Solanaコース · 第7章 Anchorフレームワーク · レッスン26

#[program] と命令ハンドラ、Context

ローカル実施

導入

Anchorでプログラムのロジックを書く場所は、たった1つのマクロ #[program] の中です。ネイティブ開発で書いた「命令の種類をmatchで振り分けるentrypoint」は、もう自分で書く必要がありません。

説明

#[program] を付けた mod の中に、pub fn を並べます。それぞれの関数が1つの命令(instruction)に対応し、クライアントから直接その名前で呼び出せます。

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn initialize(ctx: Context<Initialize>, message: String) -> Result<()> {
        let account = &mut ctx.accounts.greeting_account;
        account.message = message;
        account.owner = ctx.accounts.user.key();
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 4 + 200 + 32)]
    pub greeting_account: Account<'info, GreetingAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct GreetingAccount {
    pub message: String,
    pub owner: Pubkey,
}

命令ハンドラは必ず第一引数に Context<T> を取ります。T は次のレッスンで見る #[derive(Accounts)] を付けた構造体で、「この命令が触ってよいアカウントの一覧」を表します。ハンドラの中では ctx.accounts.フィールド名 で、検証済みのアカウントにアクセスできます。

sequenceDiagram
    participant Client as クライアント
    participant Program as Anchorプログラム
    participant Handler as initialize関数
    Client->>Program: initialize("hello") を呼び出す
    Program->>Program: Context<Initialize>のアカウントを検証
    Program->>Handler: ctx, message を渡す
    Handler->>Handler: ctx.accounts.greeting_account に書き込む
    Handler-->>Client: Ok(()) を返す

ctx.accounts の中身は、後続レッスンの制約(initmutSigner など)によって「すでに安全性が検証済みの状態」で渡ってきます。だからハンドラの中身は、ネイティブ開発と比べて驚くほどシンプルになります。

演習

上のコードで、initialize 関数の中では「署名者チェック」や「所有者チェック」のコードが1行も書かれていません。それでも安全なのはなぜだと思いますか。次のレッスンの内容を予想してみましょう。

ヒント1を見る

チェックの責任は Initialize 構造体(Context<Initialize> の型引数)側に移っています。

ヒント2を見る

Signer<'info> という型自体が「このアカウントは署名者でなければならない」という意味を持っています。