導入
自分のプログラムだけで完結する処理もありますが、Solana の面白さは「すでにある別のプログラムの機能を、自分のプログラムから呼び出せる」点にあります。これを **CPI(Cross-Program Invocation / プログラム間呼び出し)**と呼びます。
説明
CPI を使うと、たとえば「自分のプログラムの中から、System Program(アカウント作成を担当する組み込みプログラム)や Token Program(トークンの送金を担当するプログラム)を呼び出す」といったことができます。これにより、一からすべてを実装しなくても、他のプログラムの実績あるロジックを部品として組み合わせられます。この「組み合わせて新しい機能を作れる」性質を 合成可能性(composability) と呼び、Web3 の世界でしばしば強みとして語られます。
CPI には2種類の呼び出し方があります。
sequenceDiagram
participant Client as クライアント
participant A as 自分のプログラム(A)
participant B as 呼び出し先プログラム(B)
Client->>A: 命令を送信(署名つき)
A->>A: invoke(instruction, accounts)
Note over A,B: 通常の署名者情報をそのまま引き継いで呼び出す
A->>B: 命令を転送
B-->>A: 実行結果
A-->>Client: Ok(())
invoke… 元のトランザクションで署名された情報(誰が署名したか)をそのまま引き継いで別プログラムを呼び出します。通常のユーザーの署名で足りる操作(例: 送金元本人が署名済みの送金)に使います。invoke_signed… 呼び出す側のプログラムが、**PDA の代わりに「代理で署名」**して呼び出します。PDA には秘密鍵がないため、通常の署名はできません。その代わり、PDA を導出したときと同じシードをinvoke_signedに渡すことで、「このプログラムが確かにこの PDA の持ち主である」ことをランタイムに証明し、署名の代わりとします。
use solana_program::program::{invoke, invoke_signed};
use solana_program::system_instruction;
// 例1: ユーザー本人の署名をそのまま使って送金する
invoke(
&system_instruction::transfer(user.key, vault.key, 1_000_000),
&[user.clone(), vault.clone()],
)?;
// 例2: PDAが「代理署名」して実行する(seedsで証明する)
let seeds: &[&[u8]] = &[b"vault", user.key.as_ref(), &[bump]];
invoke_signed(
&system_instruction::transfer(vault.key, user.key, 1_000_000),
&[vault.clone(), user.clone()],
&[seeds],
)?;
「自分のプログラムが管理する PDA から SOL を払い出す」ような操作は、PDA 自身の署名が必要になるため、必ず invoke_signed を使う場面です。
演習
invoke と invoke_signed の使い分けを、「誰の代わりに署名しているか」という観点で自分の言葉で説明してみましょう。
ヒント1を見る
invoke はすでにトランザクションに含まれている署名(ユーザー本人など)を引き継ぐだけです。
ヒント2を見る
invoke_signed は、秘密鍵を持たないPDAの代わりに、プログラムがseedsを提示することで署名を代替します。