Skip to content

Commit 2ba047d

Browse files
authored
Merge pull request #70 from quicknode/claude/happy-ride-uwmjrp
Add Pinocchio cross-program-invocation and repository-layout examples
2 parents 15c28d4 + 079dc32 commit 2ba047d

22 files changed

Lines changed: 854 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 37 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ members = [
1919
"basics/create-account/anchor/programs/create-system-account",
2020
"basics/create-account/asm",
2121
"basics/cross-program-invocation/anchor/programs/*",
22+
"basics/cross-program-invocation/pinocchio/programs/hand",
23+
"basics/cross-program-invocation/pinocchio/programs/lever",
2224
"basics/hello-solana/native/program",
2325
"basics/hello-solana/anchor/programs/*",
2426
"basics/hello-solana/pinocchio/program",
@@ -41,6 +43,7 @@ members = [
4143
"basics/favorites/native/program",
4244
"basics/favorites/pinocchio/program",
4345
"basics/repository-layout/native/program",
46+
"basics/repository-layout/pinocchio/program",
4447
"basics/repository-layout/anchor/programs/*",
4548
"basics/transfer-sol/native/program",
4649
"basics/transfer-sol/pinocchio/program",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Cross Program Invocation: Solana Pinocchio
2+
3+
A [Cross Program Invocation (CPI)](https://solana.com/docs/core/cpi) example written using the [Pinocchio](https://github.com/anza-xyz/pinocchio) framework with only the Solana toolchain.
4+
5+
Two programs work together:
6+
7+
- `lever` - owns a `power` account that stores a single on/off byte. It exposes `initialize` (create the account) and `switch_power` (flip the byte and log who pulled it).
8+
- `hand` - takes a name, then invokes `lever`'s `switch_power` instruction via CPI, forwarding the name.
9+
10+
Because Pinocchio runs in `no_std` without an allocator, `hand` builds the CPI instruction buffer on the stack and caps the forwarded name length.
11+
12+
## Setup
13+
14+
1. Build both [programs](https://solana.com/docs/terminology#program):
15+
- `cargo build-sbf --manifest-path=./programs/hand/Cargo.toml`
16+
- `cargo build-sbf --manifest-path=./programs/lever/Cargo.toml`
17+
2. Run the Rust + LiteSVM tests: `cargo test --manifest-path=./programs/lever/Cargo.toml`
18+
19+
The tests exercise the full `initialize -> pull -> pull-again` CPI flow plus an invalid-discriminator rejection case. Rebuild the programs after every change before re-running the tests: the tests embed each `.so` at compile time, so a stale binary silently tests old code.
20+
21+
## Credits
22+
23+
Ported from the [Pinocchio cross-program-invocation example](https://github.com/solana-developers/program-examples/pull/584) contributed by [@MarkFeder](https://github.com/MarkFeder) to solana-developers/program-examples.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "cross-program-invocation-pinocchio-hand"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
pinocchio.workspace = true
8+
9+
[lib]
10+
name = "cross_program_invocation_pinocchio_hand"
11+
crate-type = ["cdylib", "lib"]
12+
13+
[features]
14+
custom-heap = []
15+
custom-panic = []
16+
17+
[lints.rust]
18+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#![no_std]
2+
3+
use pinocchio::{
4+
cpi::invoke,
5+
entrypoint,
6+
error::ProgramError,
7+
instruction::{InstructionAccount, InstructionView},
8+
nostd_panic_handler, AccountView, Address, ProgramResult,
9+
};
10+
11+
entrypoint!(process_instruction);
12+
nostd_panic_handler!();
13+
14+
// Matches lever's switch_power discriminator.
15+
const LEVER_IX_SWITCH_POWER: u8 = 1;
16+
17+
// Cap the forwarded name length so we can build the CPI buffer on the stack
18+
// (pinocchio runs in `no_std` without an allocator by default).
19+
const MAX_NAME_LEN: usize = 128;
20+
const CPI_DATA_BUF: usize = MAX_NAME_LEN + 1;
21+
22+
fn process_instruction(
23+
_program_id: &Address,
24+
accounts: &[AccountView],
25+
instruction_data: &[u8],
26+
) -> ProgramResult {
27+
let [power, lever_program] = accounts else {
28+
return Err(ProgramError::NotEnoughAccountKeys);
29+
};
30+
31+
let name = instruction_data;
32+
if name.len() > MAX_NAME_LEN {
33+
return Err(ProgramError::InvalidInstructionData);
34+
}
35+
36+
let mut cpi_data = [0u8; CPI_DATA_BUF];
37+
cpi_data[0] = LEVER_IX_SWITCH_POWER;
38+
cpi_data[1..1 + name.len()].copy_from_slice(name);
39+
40+
let metas = [InstructionAccount::writable(power.address())];
41+
let ix = InstructionView {
42+
program_id: lever_program.address(),
43+
accounts: &metas,
44+
data: &cpi_data[..1 + name.len()],
45+
};
46+
47+
invoke::<1>(&ix, &[power])
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "cross-program-invocation-pinocchio-lever"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
pinocchio.workspace = true
8+
pinocchio-log.workspace = true
9+
pinocchio-system.workspace = true
10+
11+
[lib]
12+
name = "cross_program_invocation_pinocchio_lever"
13+
crate-type = ["cdylib", "lib"]
14+
15+
[features]
16+
custom-heap = []
17+
custom-panic = []
18+
19+
[lints.rust]
20+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }
21+
22+
[dev-dependencies]
23+
litesvm = "0.11.0"
24+
solana-instruction = "3.0.0"
25+
solana-keypair = "3.0.1"
26+
solana-native-token = "3.0.0"
27+
solana-pubkey = "3.0.0"
28+
solana-transaction = "3.0.1"
29+
solana-system-interface.workspace = true
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#![no_std]
2+
3+
use pinocchio::{
4+
entrypoint,
5+
error::ProgramError,
6+
nostd_panic_handler,
7+
sysvars::{rent::Rent, Sysvar},
8+
AccountView, Address, ProgramResult,
9+
};
10+
use pinocchio_log::log;
11+
use pinocchio_system::instructions::CreateAccount;
12+
13+
entrypoint!(process_instruction);
14+
nostd_panic_handler!();
15+
16+
// Single-byte account: stores `is_on` as 0 or 1.
17+
const POWER_ACCOUNT_SPACE: u64 = 1;
18+
19+
// Instruction discriminators
20+
const IX_INITIALIZE: u8 = 0;
21+
const IX_SWITCH_POWER: u8 = 1;
22+
23+
fn process_instruction(
24+
program_id: &Address,
25+
accounts: &[AccountView],
26+
instruction_data: &[u8],
27+
) -> ProgramResult {
28+
match instruction_data.split_first() {
29+
Some((&IX_INITIALIZE, _)) => initialize(program_id, accounts),
30+
Some((&IX_SWITCH_POWER, name)) => switch_power(accounts, name),
31+
_ => Err(ProgramError::InvalidInstructionData),
32+
}
33+
}
34+
35+
fn initialize(program_id: &Address, accounts: &[AccountView]) -> ProgramResult {
36+
let [power, user, _system_program] = accounts else {
37+
return Err(ProgramError::NotEnoughAccountKeys);
38+
};
39+
40+
let lamports = Rent::get()?.try_minimum_balance(POWER_ACCOUNT_SPACE as usize)?;
41+
42+
CreateAccount {
43+
from: user,
44+
to: power,
45+
lamports,
46+
space: POWER_ACCOUNT_SPACE,
47+
owner: program_id,
48+
}
49+
.invoke()?;
50+
51+
let mut data = power.try_borrow_mut()?;
52+
data[0] = 0;
53+
Ok(())
54+
}
55+
56+
fn switch_power(accounts: &[AccountView], name: &[u8]) -> ProgramResult {
57+
let [power] = accounts else {
58+
return Err(ProgramError::NotEnoughAccountKeys);
59+
};
60+
61+
let mut data = power.try_borrow_mut()?;
62+
data[0] = if data[0] == 0 { 1 } else { 0 };
63+
let is_on = data[0] == 1;
64+
drop(data);
65+
66+
let name_str = core::str::from_utf8(name).map_err(|_| ProgramError::InvalidInstructionData)?;
67+
log!("{} is pulling the power switch!", name_str);
68+
69+
if is_on {
70+
log!("The power is now on.");
71+
} else {
72+
log!("The power is now off!");
73+
}
74+
75+
Ok(())
76+
}

0 commit comments

Comments
 (0)