本文主要介绍了 Solana 上 Anchor 开发中跨程序调用(CPIs)的概念、使用场景和方法。CPIs 允许 Solana 程序在执行期间调用其他程序,实现不同程序之间的交互,从而实现诸如 token 转移等功能。同时介绍了如何使用程序派生地址(PDAs)使程序能够作为签名者。
作为在 Solana 上构建的中间 anchor 开发者,你很可能会遇到这样一种情况:你的程序相互交互更有利。Solana 中的这种可组合性是通过一种称为 Cross-Program Invocations(CPIs) 的机制实现的。
让我们首先分解什么是 CPI,你应该如何使用它们,以及在使用此功能时应该注意什么。
Solana 独特的集成架构使其能够在利用全球去中心化网络的同时,每秒处理数千笔交易。这种设计为一种模块化结构提供了空间,同时作为一个单链区块链,使构建在其之上的应用程序能够继承可组合性。这种可组合的拓扑结构使得应用程序(和程序)能够相互交互和构建,从而消除了复杂的链上活动,例如桥接和流动性碎片化。
CPI 是 Solana 程序(或智能合约)在其执行期间调用区块链上的另一个程序的内部机制之一。它是一种强大的机制,使不同的程序能够交互并执行诸如代币转账、数据操作或源程序定义的其他操作。
当你需要程序利用另一个程序来执行功能时,你应该使用 CPI。例如,像执行代币转账一样,使用 CPI 来利用 Token Program 将节省我们大量的计算资源,而不是重新实现代币转账功能,或者将 NFT 从一个帐户转移到另一个帐户。
基本上,使用 CPI 涉及以下步骤:
首先确定你希望与之交互的程序。我们将以 Token program 为例。
然后使用 Anchor 的 #[account] 宏定义 CPI 所需的必要帐户,并指定定义如何将它们传递到 CPI 的约束,如下例所示:
#[derive(Accounts)]
pub struct List<'info> {
#[account(mut)]
maker: Signer<'info>,
#[account(
seeds = [b"marketplace", marketplace.name.as_str().as_bytes()],\
bump = marketplace.bump,
)]
marketplace: Box<Account<'info, Marketplace>>,
maker_mint: Box<InterfaceAccount<'info, Mint>>,
collection_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
mut,
associated_token::authority = maker,
associated_token::mint = maker_mint,
)]
maker_ata: Box<InterfaceAccount<'info, TokenAccount>>, //stores the maker on the heap
#[account(
init_if_needed,
payer = maker,
associated_token::mint = maker_mint,
associated_token::authority = listing,
)]
vault: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
init,
payer = maker,
space = 8 + Listing::INIT_SPACE,
seeds = [marketplace.key().as_ref(), maker_mint.key().as_ref()],
bump
)]
listing: Box<Account<'info, Listing>>,
#[account(
seeds = [
b"metadata",
metadata_program.key().as_ref(),
maker_mint.key().as_ref()
],
seeds::program = metadata_program.key(),
bump,
constraint = metadata.collection.as_ref().unwrap().key.as_ref() == collection_mint.key().as_ref(),
constraint = metadata.collection.as_ref().unwrap().verified == true,
)]
metadata: Box<Account<'info, MetadataAccount>>,
#[account(
seeds = [
b"metadata",
metadata_program.key().as_ref(),
maker_mint.key().as_ref(),
b"edition"
],
seeds::program = metadata_program.key(),
bump,
)]
master_edition: Box<Account<'info, MasterEditionAccount>>,
metadata_program: Program<'info, Metadata>,
associated_token_program: Program<'info, AssociatedToken>,
system_program: Program<'info, System>,
token_program: Interface<'info, TokenInterface>,
}
下一步将涉及定义指令数据,例如,定义从 maker_ata 帐户到 vault 帐户的传输机制,建立 maker 帐户作为授权机构,以及 maker_mint 帐户作为 mint。将 NFT 存入 vault 所需的帐户是 maker_ata account, vault account, maker 和 maker_mint account
。
然后我们创建 CPI context
并调用 token program transfer 指令
pub fn deposit_nft(&mut self) -> Result<()> {
let accounts = TransferChecked {
from: self.maker_ata.to_account_info(),
to: self.vault.to_account_info(),
authority: self.maker.to_account_info(),
mint: self.maker_mint.to_account_info(),
};
//create a Cpi context
let cpi_ctx = CpiContext::new(self.token_program.to_account_info(), accounts);
// Invoke the token program’s transfer instruction
transfer_checked(cpi_ctx, 1, self.maker_mint.decimals)
}
}
上面的示例也是 CPI 的一个重要好处,称为 Privilege Extensions,顾名思义,它“扩展”了调用者对被调用者的特权。在我们上面的示例中,原始授权机构 maker account
暂时将授权机构的控制权“扩展”到 Token Program
,使其能够代表其执行传输操作。
很可能在某些情况下,而且通常是这种情况,你需要程序本身拥有对资产的授权。在上面的 NFT 市场示例中,你可能希望程序代表你取消列表甚至购买 NFT,或者贷款协议程序需要管理存入的抵押品,而自动化做市商程序需要管理投入其流动资金池的代币。
我们可以使用 Program Derived Addresses (PDAs) 来完成此操作。我们将在后续文章中讨论 PDA,但简单定义:PDA 是没有公钥因此也没有关联私钥的特殊地址。它们有两个优点:在链上构建类似哈希表的结构,并允许程序在没有私钥的情况下签署指令。
PDA 与 CPI 相结合是 anchor 开发中非常强大的功能,因为它们允许我们安全地管理资产和数据,从而确保只有拥有程序才能通过 CPI 签署影响特定帐户的交易,从而防止程序间通信期间的未经授权的签名。
要通过 CPI Context 使用 PDA 签署交易,而不是调用 CpiContext::new(cpi_program, accounts)
方法,我们需要使用 CpiContext::new_with_signer(cpi_program, accounts, seeds)
,其中种子参数是创建 PDA 时的种子和 bump,如下面的 close_mint_vault
实现所示:
pub fn close_mint_vault(&mut self) -> Result<()> {
let seeds = &[
&self.marketplace.key().to_bytes()[..],
&self.maker_mint.key().to_bytes()[..],
&[self.listing.bump],
];
let signer_seeds = &[&seeds[..]];
let accounts = CloseAccount {
account: self.vault.to_account_info(),
destination: self.maker.to_account_info(),
authority: self.listing.to_account_info(),
};
//Sign with the pda
let cpi_ctx = CpiContext::new_with_signer(
self.token_program.to_account_info(),
accounts,
signer_seeds
);
close_account(cpi_ctx)
}
这里发生的情况是,在执行期间,Solana 运行时将检查 hash(seeds, current_program_id) == account address
是否为真。如果是,则该帐户的 is_signer
标志将设置为 true。这意味着从某个程序派生的 PDA 只能用于签署源自该特定程序的 CPI。
总而言之,结合使用 CPI 和 PDA 使我们能够利用 Anchor 强大的抽象功能,同时使我们能够通过 PDA 减少我们需要的帐户数量,并确保应用程序上不同程序之间的可组合交互。
- 原文链接: dev.to/aseneca/cross-pro...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!