In this part of Module 3, you'll delve into the practical aspects of coding a flip program that leverages Cross-Program Invocation (CPI) techniques in Solana using Solidity. You will start with creating an interface in Solidity and then move on to writing tests for the program using `TypeScript`, followed by building, deploying, and testing the flip program.
illustration
b
Designing Code for CPI Flip Program

In this lesson, you will:

  • Develop the code for the CPI Flip program.
  • Gain an understanding of implementing CPI in Solidity.

This lesson provides a comprehensive guide on setting up and using Solang for Cross-Program Invocation (CPI) in Solana. It covers the initialization of the project and details the creation of two programs: a caller (hand.sol) and a callee (lever.sol). The caller program will interact with the callee through CPI. The lesson thoroughly explains the code for both programs and the handling of CPI in Solang, which simplifies the process compared to Anchor. This serves as a practical demonstration of composability in Solana and Solang for inter-program interactions.

For our project setup, we'll follow the same system setup from previous articles, found here.

Start by initializing the project:

anchor init cpiinvoke --solidity

Then, navigate to the project directory:

cd cpiinvoke

And open it in VSCode:

code .

We'll use two Solidity files in the solidity directory: hand.sol (the caller) and lever.sol (the callee).

We're constructing a flip program with a simple purpose: to toggle power on or off using a boolean. This involves two programs: Program A (the caller) and Program B (the callee). Program A will call Program B using Cross-Program Invocation (CPI).

Create lever.sol in the solidity directory and input the following code:

@program_id("J5J3mD4ABcRtB7YcgrJBgugiQAP3DfcZi5bSAdiMAZWh")
contract lever {
 // Switch state
 bool private isOn = true;

 @payer(payer)
 constructor() {}

 // Switch power on or off
 function switchPower(string name) public {
 // Toggling the switch
 isOn = !isOn;

 // Announcing the switch action
 print("{:} is pulling the power switch!".format(name));

 // Displaying current state
 if (isOn){
 print("The power is now on.");
 } else {
 print("The power is now off!");
 }
 }

 // Returning the switch state
 function get() public view returns (bool) {
 return isOn;
 }
}

This code sets up a switch mechanism, toggling the isOn state with switchPower. It includes a constructor and a get function to retrieve the current state.

In hand.sol, we create an interface to the lever program and set up the contract:

import "solana";

// Lever program interface
leverInterface constant leverProgram = leverInterface(address'J5J3mD4ABcRtB7YcgrJBgugiQAP3DfcZi5bSAdiMAZWh');
interface leverInterface {
 function switchPower(string name) external;
}

@program_id("7zJ7E8uZccmCH89eykH8yZsB6G685nbz7sSq8N9sZTtq")
contract hand {
 @payer(payer)
 constructor() {}

 function pullLever(address dataAccount, string name) public {
 AccountMeta[1] metas = [
 AccountMeta({pubkey: dataAccount, is_writable: true, is_signer: false})
 ];

 string instructionData = name;

 leverProgram.switchPower{accounts: metas}(instructionData);
 }
}

hand.sol uses an interface to interact with lever.sol. The pullLever function invokes the switchPower function in lever.sol using CPI.

In Solang, CPI is simpler than in Anchor Rust. Using

The syntax <address>.call(), a CPI is automatically constructed, and Solang takes care of invoke or invoke_signed.

In the upcoming lesson, we'll delve into writing tests for the CPI Flip program, exploring how to utilize invoke_signed for scenarios involving Program-Derived Addresses (PDA).