保障 Solana 生态系统(二)——程序间的调用

掌握 Solana 中的跨程序调用艺术,通过我们全面的指南和实践示例,将您的智能合约开发提升到一个新的水平

保障 Solana 生态系统(二)——程序间的调用

0. 回顾

1. 概述

在上篇 博客 中,我们介绍了如何部署和与程序进行交互。除了从客户端调用程序的指令外,Solana 还允许程序通过一种称为跨程序调用(cross-program invocation)的机制相互调用。在这篇文章中,我们将说明跨程序调用的使用方法。测试代码在这里: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_accountto_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 中,程序可以在运行时生成账户,这些账户称为程序派生地址(Program Derived Addresses, 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 行进行了检查。之后,我们调用 invoke_signer 从第 37 行到第 47 行发出 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() 之间的区别。请继续关注,本系列还将发布更多文章。

阅读本系列的更多文章:

Sign up for the latest updates