Back to Blog

Secure the Solana Ecosystem (2) — Calling Between Programs

March 18, 2022

0. Review

1. Overview

In the previous blog, we introduced how to deploy and interact with the program. Apart from invoking the instructions of a program from the client side, Solana also allows programs to call each other via a mechanism called cross-program invocation. In this post, we illustrate how the cross-program invocation is used. The test code is here.

2. Cross Program Invocation

Cross program invocation is usually implemented with function invoke. In this section, we illustrate the usage of invoke by transfering lamports inside a program.

In Solana, there is a native program named System Program that plays a role in creating new accounts and transferring lamports (SOL). In this case, to transfer lamports inside a program (e.g., Program A), program A will invoke the function transfer() in System Program. Let's walk through the concreate example below.

2.1 Code Review

Line 3 to line 10 import the required libraries. Note that the function invoke() is in library solana_program::program.

In function process_instruction, line 22 to line 30 extract the three accounts passed by the client. Note the lamports will be transfered from from_account to to_account. From line 33 to line 44, function invoke() is invoked. It receives two arguments. The first one is the target invoked instruction and the second one is a set of accounts. In this example, the target instruction is transfer() which is used for transferring lamports. The accounts include all the accounts required by the instruction being invoked. In this case, the from_account and to_account.

The deployed program can be find in the following link.

https://explorer.solana.com/address/EPaLuYQ4c11BJAe9ucLbta3xGFb17Zy3cZh3UDPbXRG9?cluster=devnet

2.2 Transfer the Lamports

As mentioned, we send the transaction to the deployed program. The deployed program will further invoke the System Program to transfer the lamports to the target address. The above code shows how the client build the transactions. Line 93 to line 101 extract the programId of the deployed program. Line 103 to line 109 generate the address of the sender, receiver, and the System Program. Line 111 to line 117 construct the transaction. Line 118, the transaction is sent to Solana cluster. Note that the only signer in this transaction is the sender.which is set in line 112. The sender has to authorize (i.e., sign) the transaction which takes money from his/her account, and the receiver doesn't have to (line 113).

The transaction can be found with the following link.

https://explorer.solana.com/tx/4cxqnff8SakVcE9y5phmh6utcbBUGaLDPvqMRgSy9aPGdNVH6DCsQyJXCCzRGvW5CpygUi5pqhgBQxczXnWCoqPJ?cluster=devnet

3. Invoke or Invoke_signed

In solana, programs can generate accounts, which are called Program Derived Addresses (PDA), in runtime. If the target instruction invoked by a program contains the signed accounts with PDA, invoke_signed() instead of invoke() should be used. We will use another example to demonstrate the usage of invoke_signed(). Now let's first walk through the contract code.

3.1 Code Review

In this example, we create a PDA in runtime inside a program (i.e., Program B). To create a PDA, System Program should be invoked in program B.

Line 3 to Line 10 will import the required libraries. Note invoke_signed() is imported this time (line 6).

Similar to the example in Section 2, we extract the required accounts(line 22 - line 27), which are System Program and PDA. We then use function find_program_address() to generate the address of PDA and the seed used to push this PDA off the ed25519 curve (line 29 - line 30). This is to guarantee that the address has no associated private key. In this example, the client and the program use the same seed (i.e., 'You pass butter') to generate the PDA. Thus, the public key should be same, which is checked from line 31 to line 34. After that, we invoke invoke_signer to issuethe instruction allocate() from line 37 to line 47). Different from the function invoke(), it takes one more argument which is the seeds that used to create the PDA as well as the bump seed used by function find_program_address().

To create/allocate an account, the account itself should provide a signature, and in this example the PDA is the signer. To sign the PDA, invoke_signed is used. Specifically, the solana will use the seeds and the programId of the caller (i.e., Program B) to recreate the PDA, and match it with the given accounts (the second argument). If they are equal, the PDA will be signed.

The deployed program can be checked below.

https://explorer.solana.com/address/4h3RXGsouTRvUWNG1Dqq2tuuASTXFebHC3wFzv3tSFCK?cluster=devnet

3.2 Create a PDA

Let's go through the client script for the second example.

In line 104, we extract the PDA and the bump seed via the function findProgramAddress(). From line 112 to line 120, the transaction will be created and sent out. Note that the property ( isSigner) of the PDA is 'false' (line 113) here as it cannot be signed by the client. In the runtime, Program B will signed the PDA and invoke the allocate instruction in program System Program.

You can find the PDA allocated in the following transaction.

https://explorer.solana.com/tx/5fzdfzbD4281pY1HJm37f1fxSEMGmWtQmbmFPzEiP9v6hWF8GRRZmfcMDvPJugTrs3npnCsaWUGvw15URyBhx3LS?cluster=devnet

3.3 Can We Use Invoke()?

To demonstrate that invoke cannot be used here. We use another program (i.e. Program C) to invoke the allocation instruction.

The only change we made is to change the function invoke_signed() into the function invoke(). We noticed that the account cannot be created.

The error shows that the cross-program invocation needs a signer to execute successfully. That said, the function invoke_signed() is needed.

4. Conclusion

In this article, we introduce how to implement the cross-program invocation via the function invoke(). We also use different examples to illustrate the differences between invoke() and invoke_signed(). Stay turned and more articals for this series will be posted.

Sign up for the latest updates
Newsletter - April 2026
Security Insights

Newsletter - April 2026

In April 2026, the DeFi ecosystem experienced three major security incidents. KelpDAO lost ~$290M due to an insecure 1-of-1 DVN bridge configuration exploited via RPC infrastructure compromise, Drift Protocol suffered ~$285M from a multisig governance takeover leveraging Solana's durable nonce mechanism, and Rhea Finance incurred ~$18.4M following a business logic flaw in its margin-trading module that allowed circular swap path manipulatio

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.