保障 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
Weekly Web3 Security Incident Roundup | Feb 9 – Feb 15, 2026

Weekly Web3 Security Incident Roundup | Feb 9 – Feb 15, 2026

During the week of February 9 to February 15, 2026, three blockchain security incidents were reported with total losses of ~$657K. All incidents occurred on the BNB Smart Chain and involved flawed business logic in DeFi token contracts. The primary causes included an unchecked balance withdrawal from an intermediary contract that allowed donation-based inflation of a liquidity addition targeted by a sandwich attack, a post-swap deflationary clawback that returned sold tokens to the caller while draining pool reserves to create a repeatable price-manipulation primitive, and a token transfer override that burned tokens directly from a Uniswap V2 pair's balance and force-synced reserves within the same transaction to artificially inflate the token price.

Top 10 "Awesome" Security Incidents in 2025

Top 10 "Awesome" Security Incidents in 2025

To help the community learn from what happened, BlockSec selected ten incidents that stood out most this year. These cases were chosen not only for the scale of loss, but also for the distinct techniques involved, the unexpected twists in execution, and the new or underexplored attack surfaces they revealed.

#10 Panoptic Incident: XOR Linearity Breaks the Position Fingerprint Scheme

#10 Panoptic Incident: XOR Linearity Breaks the Position Fingerprint Scheme

On August 29, 2025, Panoptic disclosed a Cantina bounty finding and confirmed that, with support from Cantina and Seal911, it executed a rescue operation on August 25 to secure roughly $400K in funds. The issue stemmed from a flaw in Panoptic’s position fingerprint calculation algorithm, which could have enabled incorrect position identification and downstream fund risk.