0. 综述
1. 概述
在上篇博客中,我们介绍了如何部署和与程序进行交互。除了从客户端调用程序的指令外, Solana 还允许程序通过一种称为跨程序调用的机制互相调用。在这篇文章中,我们将说明跨程序调用的使用方法。测试代码在此处:https://github.com/blocksecteam/solana_demo。
2. 跨程序调用
跨程序调用通常使用 invoke 函数实现。本节将通过在程序内部转账 lamports 来演示 invoke 的用法。
在 Solana 中,有一个名为 System Program 的原生程序,它负责创建新账户和转账 lamports (SOL)。
在这种情况下,要在程序内部(例如,程序 A)转账 lamports,程序 A 将调用 System Program 中的 transfer() 函数。让我们来逐步分析下面的具体示例。
2.1 代码审查

第 3 行到第 10 行导入了必要的库。请注意,invoke() 函数位于 solana_program::program 库中。

在 process_instruction 函数中,第 22 行到第 30 行提取了客户端传入的三个账户。请注意,lamports 将从 from_account 转账到 to_account。从第 33 行到第 44 行,调用了 invoke() 函数。它接收两个参数。第一个参数是目标调用的指令,第二个参数是一组账户。
在本例中,目标指令是用于转账 lamports 的 transfer()。账户包括被调用指令所需的所有账户。在本例中,就是 from_account 和 to_account。
已部署的程序可以在以下链接中找到。
https://explorer.solana.com/address/EPaLuYQ4c11BJAe9ucLbta3xGFb17Zy3cZh3UDPbXRG9?cluster=devnet
2.2 转账 Lamports

如前所述,我们将交易发送到已部署的程序。已部署的程序将进一步调用 System Program 将 lamports 转账到目标地址。
上面的代码显示了客户端如何构建交易。第 93 行到第 101 行提取已部署程序的 programId。第 103 行到第 109 行生成发送方、接收方和 System Program 的地址。第 111 行到第 117 行构造交易。第 118 行,交易被发送到 Solana 集群。请注意,此交易中唯一的签名者是发送方,在第 112 行设置。发送方必须授权(即签名)从其账户扣款的交易,而接收方则不需要(第 113 行)。
您可以通过以下链接找到该交易。
https://explorer.solana.com/tx/4cxqnff8SakVcE9y5phmh6utcbBUGaLDPvqMRgSy9aPGdNVH6DCsQyJXCCzRGvW5CpygUi5pqhgBQxczXnWCoqPJ?cluster=devnet
3. Invoke 或 Invoke_signed
在 solana 中,程序可以在运行时生成称为程序派生地址 (PDA) 的账户。如果程序调用的目标指令包含带有 PDA 的签名账户,则应使用 invoke_signed() 而不是 invoke()。我们将使用另一个示例来演示 invoke_signed() 的用法。现在,让我们先回顾一下合约代码。
3.1 代码审查
在本例中,我们在程序内部(即程序 B)创建了一个 PDA。要创建 PDA,应在程序 B 中调用 System Program。

第 3 行到第 10 行将导入所需的库。请注意,这次导入了 invoke_signed()(第 6 行)。

与第 2 节中的示例类似,我们提取了所需的账户(第 22-27 行),即 System Program 和 PDA。然后,我们使用 find_program_address() 函数生成 PDA 的地址以及用于将此 PDA 推离 ed25519 曲线的种子(第 29-30 行)。这是为了保证该地址没有关联的私钥。在本例中,客户端和程序使用相同的种子(即“You pass butter”)来生成 PDA。因此,公钥应该是相同的,这一点从第 31 行到第 34 行进行检查。之后,我们从第 37 行到第 47 行调用 invoke_signer 来发出 allocate() 指令。与 invoke() 函数不同,它还接受一个额外的参数,该参数是用于创建 PDA 的种子以及 find_program_address() 函数使用的 bump seed。
要创建/分配账户,账户本身应提供签名,在本例中 PDA 是签名者。要签名 PDA,则使用 invoke_signed。具体来说,solana 将使用种子和调用方(即程序 B)的 programId 来 重新创建 PDA,并将其与提供的账户(第二个参数)进行匹配。如果它们相等,则 PDA 将被签名。
可以在下方查看已部署的程序。
https://explorer.solana.com/address/4h3RXGsouTRvUWNG1Dqq2tuuASTXFebHC3wFzv3tSFCK?cluster=devnet
3.2 创建 PDA
让我们来看第二个示例的客户端脚本。

在第 104 行,我们通过 findProgramAddress() 函数提取 PDA 和 bump seed。
从第 112 行到第 120 行,将创建交易并发送出去。请注意,PDA 的属性 (isSigner) 在这里是“false”(第 113 行),因为它不能由客户端签名。在运行时,程序 B 将签名 PDA 并调用 System Program 中的 allocate 指令。
您可以在以下交易中找到已分配的 PDA。
https://explorer.solana.com/tx/5fzdfzbD4281pY1HJm37f1fxSEMGmWtQmbmFPzEiP9v6hWF8GRRZmfcMDvPJugTrs3npnCsaWUGvw15URyBhx3LS?cluster=devnet
3.3 我们可以使用 Invoke() 吗?
为了演示 invoke 在这里不能被使用,我们使用另一个程序(即程序 C)来调用 allocation 指令。
我们所做的唯一更改是将 invoke_signed() 函数更改为 invoke() 函数。我们注意到账户无法创建。

错误显示跨程序调用需要签名者才能成功执行。也就是说,需要 invoke_signed() 函数。
4. 结论
在本文中,我们介绍了如何通过 invoke() 函数实现跨程序调用。我们还使用不同的示例来说明 invoke() 和 invoke_signed() 之间的区别。敬请期待本系列的更多文章。



