返回部落格

保護 Solana 生態系統 (2) — 程序間調用

2022年3月18日
閱讀約 5 分鐘

0. 回顧

1. 概述

在上一篇 部落格文章 中,我們介紹了如何部署程式以及與其互動。除了從客戶端呼叫程式的指令外,Solana 還允許程式透過稱為「跨程式調用」(Cross-Program Invocation, CPI) 的機制互相呼叫。在本篇文章中,我們將說明如何使用跨程式調用。測試程式碼位於此處。

2. 跨程式調用 (Cross Program Invocation)

跨程式調用通常透過 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() 函數。它接收兩個參數:第一個是要呼叫的目標指令,第二個是一組帳戶。在此範例中,目標指令是 transfer(),用於轉移 lamports。這些帳戶包含了指令所需的所有帳戶,即本例中的 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 中,程式可以在執行期間生成帳戶,這些帳戶稱為程式衍生地址 (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 曲線的種子 (seed)(第 29 行 - 第 30 行)。這是為了確保該地址沒有對應的私鑰。在此範例中,客戶端和程式使用相同的種子(即 'You pass butter')來生成 PDA。因此,其公鑰應該相同,這在第 31 行到第 34 行間進行了驗證。之後,我們從第 37 行到第 47 行使用 invoke_signed 發出 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() 之間的差異。請持續關注,後續將會發布此系列的更多文章。

閱讀本系列的其他文章:

訂閱最新動態
Bitget 3.875億美元鏈下違規事件:超越金鑰與合約的範疇
Security Insights

Bitget 3.875億美元鏈下違規事件:超越金鑰與合約的範疇

2026年9月24日,攻擊者利用第三方安全產品漏洞,獲取內部憑證並偽造提款指令,導致約3.875億美元從Bitget部分營運錢包(涵蓋Ethereum、其他EVM網絡、XRP Ledger、Zcash及TRON)轉出;私鑰及冷錢包未受影響。本文深入梳理事件路徑與資金流向,分析快速轉換為原生資產及生態恢復應對,提出機構縱深防禦框架,並說明授權區塊鏈滲透測試如何驗證跨層假設。

約1130萬美元損失:Multicall Router、Nostra|BlockSec Weekly
Security Insights

約1130萬美元損失:Multicall Router、Nostra|BlockSec Weekly

本報告涵蓋2026/09/14至2026/09/20期間,探討以太坊與Starknet上兩起合計損失約1130萬美元的安全事件。較大一起中,多重呼叫路由器將自身位址接受為分派目標,導致巢狀呼叫以路由器自身已授權身分而非外部呼叫者身分進入Safe錢包的Gateway模組,約2900枚`aEthrsETH`被轉出至攻擊者建立的Uniswap v4池。在Starknet上,Nostra預言機整合僅要求最少一個聚合來源,當三個設定價格來源中僅兩個達成聚合時,操縱的薄池報價與正常報價平均後將`NSTR`估值約49.52美元,支撐約350萬美元以高估抵押品借貸。

损失約3.2億美元:Liquid Network與Symbiosis遭攻擊事件 | BlockSec
Security Insights

损失約3.2億美元:Liquid Network與Symbiosis遭攻擊事件 | BlockSec

本報告涵蓋2026/09/07至2026/09/13,探討兩起造成約3.2億美元損失的安全事件,包括前份報告未提及、發生於2026/09/06的Liquid Network漏洞事件。較大者為該Liquid Network漏洞:Elements中的rangeproof驗證快取藉由雜湊四個欄位(其中兩個長度可變)串接而成金鑰,且未標示欄位邊界,導致某輸出的驗證結果被錯誤套用至另一個未經檢驗證明的輸出,使攻擊者得以憑空創造4,000枚無擔保L-BTC,並將幾乎全數兌回比特幣。在橫跨BNB Smart Chain、Ethereum與Rootstock的Symbiosis跨鏈橋比特幣路徑中,讀取比特幣存款的鏈下程式碼從存款人可自行控制的欄位取得其身份,並在未檢查手續費是否為負值的情況下自存款中扣除,使330聰的存款鑄造出`46,116,860,184.27388234 syBTC`;其須賣出所經之資金池僅有11.26 syBTC,致使流動性提供者與用戶損失約9.97 BTC(約77萬美元)。