導入
process_instruction の accounts 引数には、この命令が触れるすべてのアカウントが配列として渡ってきます。しかし配列の何番目が何のアカウントかは、コードを書く側が「この順番で送ってね」と決め、そのとおりに1つずつ取り出す必要があります。
説明
各アカウントは AccountInfo という型で表現され、次のような情報を持っています。
classDiagram
class AccountInfo {
+key: &Pubkey
+is_signer: bool
+is_writable: bool
+lamports: Rc~RefCell~u64~~
+data: Rc~RefCell~u8[]~~
+owner: &Pubkey
+executable: bool
}
key… このアカウントのアドレス(ウォレットのアドレスと同じPubkey型)。is_signer… このトランザクションに署名した本人かどうか。「本人確認が取れているか」のフラグです。is_writable… このアカウントへの書き込みが許可されているか。lamports… アカウントが保有する SOL の量(1 SOL = 10億 lamports)。data… アカウントに保存されているバイト列。カウンタの値などの状態はここに入ります。owner… このアカウントの所有者プログラムのアドレス。第5章の後半で重要になります。
accounts はただの配列(スライス)なので、.iter() でイテレータ(順番に取り出す仕組み)に変換し、next_account_info を使って1つずつ安全に取り出します。取り出すアカウントが足りない場合は自動的にエラーになるので、unwrap で落とすより安全です。
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
pubkey::Pubkey,
msg,
};
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// accountsを順番に取り出すためのイテレータ
let accounts_iter = &mut accounts.iter();
// 1番目: 操作する本人(署名者)のアカウント
let signer = next_account_info(accounts_iter)?;
// 2番目: カウンタの値を保存しているデータアカウント
let counter_account = next_account_info(accounts_iter)?;
msg!("signer: {}", signer.key);
msg!("counter account: {}", counter_account.key);
msg!("counter accountの所有者プログラム: {}", counter_account.owner);
Ok(())
}
「アカウントの並び順」はプログラムを書く人が決める**契約(インターフェース)**です。クライアント側(TypeScript などで書くアプリ)も、必ずこの順番でアカウントを並べてトランザクションを組み立てる必要があります。
// クライアント側(イメージ): サーバー側の順番と揃える必要がある
const instruction = new TransactionInstruction({
keys: [
{ pubkey: signer.publicKey, isSigner: true, isWritable: false },
{ pubkey: counterAccount, isSigner: false, isWritable: true },
],
programId,
data: instructionData,
});
演習
「アカウントの並び順を、プログラム側とクライアント側で必ず一致させないといけない」というルールは、間違えるとどんなバグにつながりそうですか。具体的に想像してみましょう。
ヒント1を見る
1番目に渡すはずが2番目に渡ってしまったら、プログラムは何を「署名者」だと勘違いするでしょうか。
ヒント2を見る
Anchor(第7章)はこの「順番の契約」を自動チェックする仕組みを持っています。