This section of Module 3 delves into the concept of composability in programs, focusing on how it operates in Solana and Ethereum, and introduces the implementation of Cross-Program Invocation (CPI) techniques.
Implementing CPI into Solana Programs

In this lesson, you'll learn about:

  • Implementing CPI using the invoke() function in Solana.

Solana's invoke() function enables program calls without the need for

Signers. It is essential for routing instructions, accounts, and data to the callee program. A Cross-Program Invocation (CPI) is created by defining the accessed accounts and the instruction data. CPIs in Solana allow for limited depth reentrancy. We'll demonstrate how to use CPI by calling a lever program in our code example.

The invoke function is crucial when making a CPI that does not require any Program-Derived Addresses (PDAs) to act as signers. In such CPIs, Solana runtime extends the original signature passed into a program to the callee program.

When calling a program, you often find libraries with useful functions to create the Instruction. These libraries, typically developed by individuals or groups, simplify the process of calling their programs.

The definition of the Instruction type required for a CPI includes:

  • invoke() is integrated into Solana's runtime, managing the routing of the given instruction to another program via the instruction's program_id field.
  • accounts - a list of AccountInfo corresponding to all accounts accessed by the callee program.
  • data - instruction data formatted to be understood by the callee program.

The AccountInfo structures provided to this function contain data utilized by the runtime. This data is transferred to and from the memory space of the program being called.

Cross-program invocations allow programs to call other programs, but currently, this capability is limited to a depth of four.

Reentrancy is currently permitted only in cases of direct self-recursion and is constrained to a predetermined depth. This restriction aims to prevent scenarios where a program might inadvertently call another program from an intermediary state. Direct recursion ensures that the program has full control over its state when it is re-invoked.

To effectively use invoke and invoke_signed, a list of account_infos is also necessary. Similar to the list of AccountMeta in the instructions, it is crucial to include all AccountInfo for each account that the callee program will access.

Since programs can only receive AccountInfo values from the runtime at the program's entry point, any account required by the callee program must also be needed by the calling program and provided by its caller. This rule also applies to the program ID of the callee program.

Below is a code example demonstrating how CPIs are created to interact with a leverProgram by calling their switchPower function:

function pullLever(address dataAccount, string name) public {
 // Account required by the switchPower instruction.
 // This is the data account created by the lever program (not this program), storing the state of the switch.
 AccountMeta[1] metas = [
 AccountMeta({pubkey: dataAccount, is_writable: true, is_signer: false})

 // Data required by the switchPower instruction.
 string instructionData = name;

 // Invoke the switchPower instruction on the lever program.
 leverProgram.switchPower{accounts: metas}(instructionData);

In this code snippet, we define a pullLever function, accepting dataAccount and name as parameters. The function configures the AccountMeta for the lever program's data account, then assigns name as the instruction data. Utilizing Cross-Program Invocation (CPI), it calls the switchPower method in the lever program. This results in the construction of a CPI, and the Solana runtime generates an invoke() call, providing the requisite accountInfos and instructionData.

When defining a CPI, the syntax for

Specifying the AccountMeta for each account includes:

  • AccountMeta::new - This signifies that the account is writable.
  • AccountMeta::new_readonly - This denotes that the account is not writable.
  • (pubkey, true) - Indicates that the account is a signer.
  • (pubkey, false) - Implies that the account is not a signer.

Typically, the Instruction is constructed within the calling program. However, it's also possible to source the Instruction externally through deserialization.

In our upcoming lesson, we will focus on writing tests for the CPI Flip program, demonstrating how to ensure that the CPI integration works correctly and efficiently within the Solana framework.