Skip to content

Commit

Permalink
cpi: Add rust program that CPIs into system program
Browse files Browse the repository at this point in the history
#### Problem

There's no example that performs a cross-program invocation to see
compute units used.

#### Summary of changes

Add a simple rust example and test.
  • Loading branch information
joncinque committed Oct 3, 2024
1 parent c37ac49 commit fe48a2f
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
name: Run tests against Rust implementations
strategy:
matrix:
program: [helloworld, transfer-lamports]
program: [helloworld, transfer-lamports, cpi]
fail-fast: false
runs-on: ubuntu-latest
steps:
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"cpi",
"helloworld",
"transfer-lamports"
]
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,14 @@ lets the VM assume it worked.
This one starts to get interesting since it requires parsing the instruction
input. Since the assembly version knows exactly where to find everything, it can
be hyper-optimized. The C version is also very performant.
Zig's version should perform the same as C, but there are some inefficiencies that are currently fixing.

Zig's version should perform the same as C, but there are some inefficiencies that
are currently being fixed.

* CPI: allocates a PDA given by the seed "You pass butter" and a bump seed in
the instruction data. This requires a call to `create_program_address` to check
the address and `invoke_signed` to CPI to the system program.

| Language | CU Usage |
| --- | --- |
| Rust | 3662 |
18 changes: 18 additions & 0 deletions cpi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "solana-program-rosetta-cpi"
version = "1.0.0"
edition = "2021"

[features]
no-entrypoint = []
test-sbf = []

[dependencies]
solana-program = "2.0.3"

[dev-dependencies]
solana-program-test = "2.0.3"
solana-sdk = "2.0.3"

[lib]
crate-type = ["cdylib", "lib"]
14 changes: 14 additions & 0 deletions cpi/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Program entrypoint
#![cfg(not(feature = "no-entrypoint"))]

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};

solana_program::entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process_instruction(program_id, accounts, instruction_data)
}
6 changes: 6 additions & 0 deletions cpi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Rust example demonstrating invoking another program
#![deny(missing_docs)]
#![forbid(unsafe_code)]

mod entrypoint;
pub mod processor;
44 changes: 44 additions & 0 deletions cpi/src/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Program instruction processor
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
};

/// Amount of bytes of account data to allocate
pub const SIZE: usize = 42;

/// Instruction processor
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// Create in iterator to safety reference accounts in the slice
let account_info_iter = &mut accounts.iter();

// Account info to allocate
let allocated_info = next_account_info(account_info_iter)?;
// Account info for the program being invoked
let _system_program_info = next_account_info(account_info_iter)?;

let expected_allocated_key =
Pubkey::create_program_address(&[b"You pass butter", &[instruction_data[0]]], program_id)?;
if *allocated_info.key != expected_allocated_key {
// allocated key does not match the derived address
return Err(ProgramError::InvalidArgument);
}

// Invoke the system program to allocate account data
invoke_signed(
&system_instruction::allocate(allocated_info.key, SIZE as u64),
&[allocated_info.clone()],
&[&[b"You pass butter", &[instruction_data[0]]]],
)?;

Ok(())
}
55 changes: 55 additions & 0 deletions cpi/tests/functional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use {
solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
rent::Rent,
system_program,
},
solana_program_test::*,
solana_sdk::{account::Account, signature::Signer, transaction::Transaction},
solana_program_rosetta_cpi::processor::{process_instruction, SIZE},
std::str::FromStr,
};

#[tokio::test]
async fn test_cross_program_invocation() {
let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap();
let (allocated_pubkey, bump_seed) =
Pubkey::find_program_address(&[b"You pass butter"], &program_id);
let mut program_test = ProgramTest::new(
"solana_program_rosetta_cpi",
program_id,
processor!(process_instruction),
);
program_test.add_account(
allocated_pubkey,
Account {
lamports: Rent::default().minimum_balance(SIZE),
..Account::default()
},
);

let (mut banks_client, payer, recent_blockhash) = program_test.start().await;

let mut transaction = Transaction::new_with_payer(
&[Instruction::new_with_bincode(
program_id,
&[bump_seed],
vec![
AccountMeta::new(allocated_pubkey, false),
AccountMeta::new_readonly(system_program::id(), false),
],
)],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer], recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();

// Associated account now exists
let allocated_account = banks_client
.get_account(allocated_pubkey)
.await
.expect("get_account")
.expect("associated_account not none");
assert_eq!(allocated_account.data.len(), SIZE);
}

0 comments on commit fe48a2f

Please sign in to comment.