Back to Blog

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

March 18, 2022
5 min read

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_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 曲線的種子 (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() 之間的差異。請持續關注,後續將會發布此系列的更多文章。

閱讀本系列的其他文章: