プログラム間呼び出し(CPI)

プログラム間呼び出し(Cross-Program Invocation/CPI) あるプログラムから別のプログラムへの直接呼び出しで、Solanaプログラムの構成可能性を高めることができます。クライアントが JSON RPC を使用して任意のプログラムを呼び出すことができるように、任意のプログラムが CPI を介して他のプログラムを呼び出すことができます。CPI は基本的に、Solana エコシステム全体を開発者が自由に使える1つの巨大なAPIに変えます。

このセクションの目的は、高レベルの概要CPIを提供することです。詳細な説明、例、チュートリアルについては、以下のリンク先のリソースを参照してください。

概要

Fact Sheet

  • プログラム間呼び出し(CPI)は、呼び出されているプログラムの特定の命令を対象とした、あるプログラムから別のプログラムへの呼び出しです。
  • CPI を使用すると、呼び出し元プログラムは署名者特権を呼び出し先プログラムに拡張できます。
  • プログラムは、命令内でinvokeまたはinvoke_signedを使用してCPIを実行できます。
  • invokeは、PDAが署名者として機能する必要なく、呼び出しの前に必要なすべての署名にアクセスできる場合に使用されます。
  • invoke_signedは、呼び出しプログラムからのPDAがCPIの署名者として必要な場合に使用されます。
  • 別のプログラムに対してCPIが作成された後、呼び出し先プログラムは最大深さ4まで他のプログラムに対してさらにCPIを作成できます。

詳細

プログラム間呼び出し(CPI) は、Solanaプログラムの構成可能性を高め、開発者が既存のプログラムの命令を利用して構築できるようにします。

CPI を実行するには、solana_programクレートにあるinvokeopen in new windowメソッド、またはinvoke_signedopen in new windowメソッドを利用します。

// Used when there are not signatures for PDAs needed
pub fn invoke(
    instruction: &Instruction,
    account_infos: &[AccountInfo<'_>]
) -> ProgramResult

// Used when a program must provide a 'signature' for a PDA, hence the signer_seeds parameter
pub fn invoke_signed(
    instruction: &Instruction,
    account_infos: &[AccountInfo<'_>],
    signers_seeds: &[&[&[u8]]]
) -> ProgramResult

CPI を作成するには、呼び出されるプログラムで命令を指定および構築し、その命令に必要なアカウントのリストを提供する必要があります。署名者としてPDAが必要な場合は、invoke_signedを使用するときにsigners_seedsも指定する必要があります。

invokeを用いたCPI

invokeは、署名者として機能する PDA を必要としない CPI を作成するときに使用されます。CPIを作成するとき、Solanaランタイムは、プログラムに渡された元の署名を呼び出し先プログラムに拡張します。

invoke(
    &some_instruction,                           // instruction to invoke
    &[account_one.clone(), account_two.clone()], // accounts required by instruction
)?;

invoke_signedを用いたCPI

署名者としてPDAを必要とするCPIを作成するには、invoke_signed関数を使用し、必要なシードを提供して呼び出しプログラムの必要なPDAを導出します。

invoke_signed(
    &some_instruction,                   // instruction to invoke
    &[account_one.clone(), pda.clone()], // accounts required by instruction, where one is a pda required as signer
    &[signers_seeds],                    // seeds to derive pda
)?;

PDA には独自の秘密鍵はありませんが、CPI を介して命令で署名者として機能することができます。PDAが呼び出しプログラムに属していることを確認するには、署名者として必要なPDAを生成するために使用されるシードを、signers_seedsとして含める必要があります。

Solanaランタイムは、提供されたシードと呼び出しプログラムのprogram_idを使用してcreate_program_addressを内部的に呼び出します。結果のPDAは、命令で提供されたアドレスと比較されます。一致する場合、PDAは有効な署名者と見なされます。

CPI Instruction

呼び出しを行っているプログラムによっては、Instructionを作成するためのヘルパー関数を備えたクレートが利用できる場合があります。多くの個人や組織は、プログラムの呼び出しを簡素化するために、これらの種類の関数を公開するプログラムと一緒に、公開されているクレートを作成しています。

CPI に必要なInstructionタイプの定義には、以下が含まれます:

  • program_id - 命令を実行するプログラムの公開鍵
  • accounts - 命令の実行中に読み書きできるすべてのアカウントのリスト
  • data - instructionが必要とする命令データ
pub struct Instruction {
    pub program_id: Pubkey,
    pub accounts: Vec<AccountMeta>,
    pub data: Vec<u8>,
}

AccountMeta構造体には次の定義があります:

pub struct AccountMeta {
    pub pubkey: Pubkey,
    pub is_signer: bool,
    pub is_writable: bool,
}

CPIを作成するときは、次の構文を使用して各アカウントのAccountMetaを指定します:

  • AccountMeta::new - 書き込み可能である
  • AccountMeta::new_readonly - 書き込み不可
  • (pubkey, true) - アカウントが署名者である
  • (pubkey, false) - アカウントが署名者でない

以下はその一例です:

use solana_program::instruction::AccountMeta;

let account_metas = vec![
    AccountMeta::new(account1_pubkey, true),
    AccountMeta::new(account2_pubkey, false),
    AccountMeta::new_readonly(account3_pubkey, false),
    AccountMeta::new_readonly(account4_pubkey, true),
]

CPI AccountInfo

invokeおよびinvoke_signedを使用するには、account_infosのリストも必要です。命令のAccountMetaのリストと同様に、呼び出しているプログラムが読み書きする各アカウントのすべてのAccountInfoを含める必要があります。

参考までに、AccountInfo構造体には次の定義があります:

/// Account information
#[derive(Clone)]
pub struct AccountInfo<'a> {
    /// Public key of the account
    pub key: &'a Pubkey,
    /// Was the transaction signed by this account's public key?
    pub is_signer: bool,
    /// Is the account writable?
    pub is_writable: bool,
    /// The lamports in the account.  Modifiable by programs.
    pub lamports: Rc<RefCell<&'a mut u64>>,
    /// The data held in this account.  Modifiable by programs.
    pub data: Rc<RefCell<&'a mut [u8]>>,
    /// Program that owns this account
    pub owner: &'a Pubkey,
    /// This account's data contains a loaded program (and is now read-only)
    pub executable: bool,
    /// The epoch at which this account will next owe rent
    pub rent_epoch: Epoch,
}

Cloneopen in new windowトレイトを使用して、必要なアカウントごとにAccountInfoのコピーを作成できます。これはsolana_programクレートのAccountInfoopen in new window構造体に実装されています。

let accounts_infos = [
    account_one.clone(),
    account_two.clone(),
    account_three.clone(),
];

このセクションではCPIの概要を説明しましたが、より詳細な説明、例、チュートリアルについては、以下のリンク先のリソースを参照してください。

その他参考資料

Last Updated:
Contributors: PokoPoko2ry