導入
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 の中身は、後続レッスンの制約(init・mut・Signer など)によって「すでに安全性が検証済みの状態」で渡ってきます。だからハンドラの中身は、ネイティブ開発と比べて驚くほどシンプルになります。
演習
上のコードで、initialize 関数の中では「署名者チェック」や「所有者チェック」のコードが1行も書かれていません。それでも安全なのはなぜだと思いますか。次のレッスンの内容を予想してみましょう。
ヒント1を見る
チェックの責任は Initialize 構造体(Context<Initialize> の型引数)側に移っています。
ヒント2を見る
Signer<'info> という型自体が「このアカウントは署名者でなければならない」という意味を持っています。