導入
前レッスンの 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の制約は「型そのものが要件」なので、うっかり緩めることが起きにくくなっています。
演習
UpdateGreeting の greeting_account から has_one = owner を外してしまったら、どんな不正ができてしまうでしょうか。第5〜6章のPDA・アカウント検証の内容を思い出しながら考えてみましょう。
ヒント1を見る
has_one がないと、「自分が作ったのではない他人の greeting_account」を渡して更新できてしまうかもしれません。
ヒント2を見る
これはレッスン35で扱う「アカウント取り違え(account confusion)」という脆弱性のパターンです。