Skip to content

Commit cdbd764

Browse files
committed
cpi: Add rust program that CPIs into system program
#### 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.
1 parent c37ac49 commit cdbd764

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
name: Run tests against Rust implementations
4848
strategy:
4949
matrix:
50-
program: [helloworld, transfer-lamports]
50+
program: [helloworld, transfer-lamports, cpi]
5151
fail-fast: false
5252
runs-on: ubuntu-latest
5353
steps:

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"cpi",
34
"helloworld",
45
"transfer-lamports"
56
]

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,14 @@ lets the VM assume it worked.
169169
This one starts to get interesting since it requires parsing the instruction
170170
input. Since the assembly version knows exactly where to find everything, it can
171171
be hyper-optimized. The C version is also very performant.
172-
Zig's version should perform the same as C, but there are some inefficiencies that are currently fixing.
172+
173+
Zig's version should perform the same as C, but there are some inefficiencies that
174+
are currently being fixed.
175+
176+
* CPI: allocates a PDA given by the seed "You pass butter" and a bump seed in
177+
the instruction data. This requires a call to `create_program_address` to check
178+
the address and `invoke_signed` to CPI to the system program.
179+
180+
| Language | CU Usage |
181+
| --- | --- |
182+
| Rust | 3662 |

cpi/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "solana-program-rosetta-cpi"
3+
version = "1.0.0"
4+
edition = "2021"
5+
6+
[features]
7+
test-sbf = []
8+
9+
[dependencies]
10+
solana-program = "2.0.3"
11+
12+
[dev-dependencies]
13+
solana-program-test = "2.0.3"
14+
solana-sdk = "2.0.3"
15+
16+
[lib]
17+
crate-type = ["cdylib", "lib"]

cpi/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Rust example demonstrating invoking another program
2+
#![deny(missing_docs)]
3+
#![forbid(unsafe_code)]
4+
5+
use solana_program::{
6+
account_info::{next_account_info, AccountInfo},
7+
entrypoint::ProgramResult,
8+
program::invoke_signed,
9+
program_error::ProgramError,
10+
pubkey::Pubkey,
11+
system_instruction,
12+
};
13+
14+
solana_program::entrypoint!(process_instruction);
15+
16+
/// Amount of bytes of account data to allocate
17+
pub const SIZE: usize = 42;
18+
19+
/// Instruction processor
20+
pub fn process_instruction(
21+
program_id: &Pubkey,
22+
accounts: &[AccountInfo],
23+
instruction_data: &[u8],
24+
) -> ProgramResult {
25+
// Create in iterator to safety reference accounts in the slice
26+
let account_info_iter = &mut accounts.iter();
27+
28+
// Account info to allocate
29+
let allocated_info = next_account_info(account_info_iter)?;
30+
// Account info for the program being invoked
31+
let _system_program_info = next_account_info(account_info_iter)?;
32+
33+
let expected_allocated_key =
34+
Pubkey::create_program_address(&[b"You pass butter", &[instruction_data[0]]], program_id)?;
35+
if *allocated_info.key != expected_allocated_key {
36+
// allocated key does not match the derived address
37+
return Err(ProgramError::InvalidArgument);
38+
}
39+
40+
// Invoke the system program to allocate account data
41+
invoke_signed(
42+
&system_instruction::allocate(allocated_info.key, SIZE as u64),
43+
&[allocated_info.clone()],
44+
&[&[b"You pass butter", &[instruction_data[0]]]],
45+
)?;
46+
47+
Ok(())
48+
}

cpi/tests/functional.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use {
2+
solana_program::{
3+
instruction::{AccountMeta, Instruction},
4+
pubkey::Pubkey,
5+
rent::Rent,
6+
system_program,
7+
},
8+
solana_program_rosetta_cpi::{process_instruction, SIZE},
9+
solana_program_test::*,
10+
solana_sdk::{account::Account, signature::Signer, transaction::Transaction},
11+
std::str::FromStr,
12+
};
13+
14+
#[tokio::test]
15+
async fn test_cross_program_invocation() {
16+
let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap();
17+
let (allocated_pubkey, bump_seed) =
18+
Pubkey::find_program_address(&[b"You pass butter"], &program_id);
19+
let mut program_test = ProgramTest::new(
20+
"solana_program_rosetta_cpi",
21+
program_id,
22+
processor!(process_instruction),
23+
);
24+
program_test.add_account(
25+
allocated_pubkey,
26+
Account {
27+
lamports: Rent::default().minimum_balance(SIZE),
28+
..Account::default()
29+
},
30+
);
31+
32+
let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
33+
34+
let mut transaction = Transaction::new_with_payer(
35+
&[Instruction::new_with_bincode(
36+
program_id,
37+
&[bump_seed],
38+
vec![
39+
AccountMeta::new(allocated_pubkey, false),
40+
AccountMeta::new_readonly(system_program::id(), false),
41+
],
42+
)],
43+
Some(&payer.pubkey()),
44+
);
45+
transaction.sign(&[&payer], recent_blockhash);
46+
banks_client.process_transaction(transaction).await.unwrap();
47+
48+
// Associated account now exists
49+
let allocated_account = banks_client
50+
.get_account(allocated_pubkey)
51+
.await
52+
.expect("get_account")
53+
.expect("associated_account not none");
54+
assert_eq!(allocated_account.data.len(), SIZE);
55+
}

0 commit comments

Comments
 (0)