本文へスキップ
BecomeCoder

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

#[derive(Accounts)] とアカウント制約

ローカル実施

導入

前レッスンの Initialize 構造体には #[account(init, payer = user, space = ...)] のような注釈が付いていました。これが Anchor の核心である**アカウント制約(account constraints)**です。ネイティブ開発if 文を積み重ねていた検証が、宣言的な属性1行に置き換わります。

説明

#[derive(Accounts)] を付けた構造体の各フィールドが、命令が受け取る1つのアカウントに対応します。フィールドの属性の組み合わせで、Anchorが実行前に自動でチェックします。

#[derive(Accounts)]
pub struct UpdateGreeting<'info> {
    #[account(mut, has_one = owner, seeds = [b"greeting", owner.key().as_ref()], bump)]
    pub greeting_account: Account<'info, GreetingAccount>,
    pub owner: Signer<'info>,
}

主な制約とその意味を、ネイティブ開発で書いていた手書きチェックと対応させると次のとおりです。

Anchorの制約/型意味ネイティブ開発での対応
Signer<'info>このアカウントの署名が必須if !account.is_signer { return Err(...) }
#[account(mut)]このアカウントは書き換えられるif !account.is_writable { return Err(...) }
#[account(init, payer=..., space=...)]新規アカウントを作成し、指定のアカウントが賃料(レント)を支払うsystem_instruction::create_account の手動呼び出し
#[account(seeds=[...], bump)]PDAであることをシードから検証・導出Pubkey::find_program_address を呼んで比較
has_one = ownerこのアカウントの owner フィールドが、渡された owner アカウントと一致することif account.owner_field != owner.key() { return Err(...) }
Account<'info, T>Tとしてデシリアライズでき、かつ所有者プログラムがこのプログラム自身であること手動でのデータ長チェック+所有者プログラムIDの比較
Program<'info, System>渡されたアカウントがSystem ProgramそのものであることプログラムIDの文字列比較
flowchart LR
    Req[クライアントからの呼び出し] --> Check["#[derive(Accounts)]の型・属性を<br/>Anchorが1つずつ検証"]
    Check -- 失敗 --> Err[命令を実行せずエラーを返す]
    Check -- 成功 --> Handler[命令ハンドラ本体を実行]

一番のメリットは、**チェックが「その場に書いてあるので見て分かる」**ことです。ネイティブ開発の if の羅列は、どこかを消してもコンパイルは通ってしまいますが、Anchorの制約は「型そのものが要件」なので、うっかり緩めることが起きにくくなっています。

演習

UpdateGreetinggreeting_account から has_one = owner を外してしまったら、どんな不正ができてしまうでしょうか。第5〜6章のPDA・アカウント検証の内容を思い出しながら考えてみましょう。

ヒント1を見る

has_one がないと、「自分が作ったのではない他人の greeting_account」を渡して更新できてしまうかもしれません。

ヒント2を見る

これはレッスン35で扱う「アカウント取り違え(account confusion)」という脆弱性のパターンです。