Skip to content

Commit 0f53170

Browse files
authored
Add system program client crate (#13)
* Add system helper crate * Add missing feature * Add more instructions * Rename to programs * Add remaining instructions * Add readme * Update banner image * Tweak formatting * Bump version
1 parent dd36317 commit 0f53170

19 files changed

+905
-0
lines changed

Cargo.lock

Lines changed: 8 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,6 +1,7 @@
11
[workspace]
22
resolver = "2"
33
members = [
4+
"programs/system",
45
"sdk/pinocchio",
56
"sdk/pubkey",
67
]

programs/system/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "pinocchio-system"
3+
description = "Pinocchio helpers to invoke System program instructions"
4+
version = "0.1.0"
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
readme = "./README.md"
8+
repository = "https://github.com/febo/pinocchio"
9+
10+
[dependencies]
11+
pinocchio = { version="^0", path="../../sdk/pinocchio" }
12+
pinocchio-pubkey = { version="^0", path="../../sdk/pubkey" }

programs/system/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# <img width="250" alt="pinocchio-system" src="https://github.com/user-attachments/assets/6a775333-c3a1-4623-aa7a-afdc8c492594"/>
2+
3+
This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for System program instructions.
4+
5+
Each instruction defines an `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI.
6+
7+
> **Note:** The API defined in this crate is subject to change.
8+
9+
## Examples
10+
11+
Creating a new account:
12+
```rust
13+
// This example assumes that the instruction receives a writable signer `payer_info`
14+
// and `new_account` accounts.
15+
CreateAccount {
16+
from: payer_info,
17+
to: new_account,
18+
lamports: 1_000_000_000, // 1 SOL
19+
space: 200, // 200 bytes
20+
owner: &spl_token::ID,
21+
}.invoke()?;
22+
```
23+
24+
Performing a transfer of lamports:
25+
```rust
26+
// This example assumes that the instruction receives a writable signer `payer_info`
27+
// account and a writable `recipient` account.
28+
Transfer {
29+
from: payer_info,
30+
to: recipient,
31+
lamports: 500_000_000, // 0.5 SOL
32+
}.invoke()?;
33+
```
34+
35+
## License
36+
37+
The code is licensed under the [Apache License Version 2.0](../LICENSE)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
entrypoint::ProgramResult,
4+
instruction::{AccountMeta, Instruction, Signer},
5+
program::invoke_signed,
6+
};
7+
8+
/// Consumes a stored nonce, replacing it with a successor.
9+
///
10+
/// ### Accounts:
11+
/// 0. `[WRITE]` Nonce account
12+
/// 1. `[]` RecentBlockhashes sysvar
13+
/// 2. `[SIGNER]` Nonce authority
14+
pub struct AdvanceNonceAccount<'a> {
15+
/// Nonce account.
16+
pub account: &'a AccountInfo,
17+
18+
/// RecentBlockhashes sysvar.
19+
pub recent_blockhashes_sysvar: &'a AccountInfo,
20+
21+
/// Nonce authority.
22+
pub authority: &'a AccountInfo,
23+
}
24+
25+
impl<'a> AdvanceNonceAccount<'a> {
26+
#[inline(always)]
27+
pub fn invoke(&self) -> ProgramResult {
28+
self.invoke_signed(&[])
29+
}
30+
31+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
32+
// account metadata
33+
let account_metas: [AccountMeta; 3] = [
34+
AccountMeta::writable(self.account.key()),
35+
AccountMeta::readonly(self.recent_blockhashes_sysvar.key()),
36+
AccountMeta::readonly_signer(self.authority.key()),
37+
];
38+
39+
// instruction
40+
let instruction = Instruction {
41+
program_id: &crate::ID,
42+
accounts: &account_metas,
43+
data: &[4],
44+
};
45+
46+
invoke_signed(
47+
&instruction,
48+
&[self.account, self.recent_blockhashes_sysvar, self.authority],
49+
signers,
50+
)
51+
}
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
entrypoint::ProgramResult,
4+
instruction::{AccountMeta, Instruction, Signer},
5+
program::invoke_signed,
6+
};
7+
8+
/// Allocate space in a (possibly new) account without funding.
9+
///
10+
/// ### Accounts:
11+
/// 0. `[WRITE, SIGNER]` New account
12+
pub struct Allocate<'a> {
13+
/// Account to be assigned.
14+
pub account: &'a AccountInfo,
15+
16+
/// Number of bytes of memory to allocate.
17+
pub space: u64,
18+
}
19+
20+
impl<'a> Allocate<'a> {
21+
#[inline(always)]
22+
pub fn invoke(&self) -> ProgramResult {
23+
self.invoke_signed(&[])
24+
}
25+
26+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
27+
// account metadata
28+
let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())];
29+
30+
// instruction data
31+
// - [0..4 ]: instruction discriminator
32+
// - [4..12]: space
33+
let mut instruction_data = [0; 12];
34+
instruction_data[0] = 8;
35+
instruction_data[4..12].copy_from_slice(&self.space.to_le_bytes());
36+
37+
let instruction = Instruction {
38+
program_id: &crate::ID,
39+
accounts: &account_metas,
40+
data: &instruction_data,
41+
};
42+
43+
invoke_signed(&instruction, &[self.account], signers)
44+
}
45+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
entrypoint::ProgramResult,
4+
instruction::{AccountMeta, Instruction, Signer},
5+
program::invoke_signed,
6+
pubkey::Pubkey,
7+
};
8+
9+
/// Allocate space for and assign an account at an address derived
10+
/// from a base public key and a seed.
11+
///
12+
/// ### Accounts:
13+
/// 0. `[WRITE]` Allocated account
14+
/// 1. `[SIGNER]` Base account
15+
pub struct AllocateWithSeed<'a, 'b, 'c> {
16+
/// Allocated account.
17+
pub account: &'a AccountInfo,
18+
19+
/// Base account.
20+
///
21+
/// The account matching the base Pubkey below must be provided as
22+
/// a signer, but may be the same as the funding account and provided
23+
/// as account 0.
24+
pub base: &'a AccountInfo,
25+
26+
/// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`.
27+
pub seed: &'b str,
28+
29+
/// Number of bytes of memory to allocate.
30+
pub space: u64,
31+
32+
/// Address of program that will own the new account.
33+
pub owner: &'c Pubkey,
34+
}
35+
36+
impl<'a, 'b, 'c> AllocateWithSeed<'a, 'b, 'c> {
37+
#[inline(always)]
38+
pub fn invoke(&self) -> ProgramResult {
39+
self.invoke_signed(&[])
40+
}
41+
42+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
43+
// account metadata
44+
let account_metas: [AccountMeta; 2] = [
45+
AccountMeta::writable_signer(self.account.key()),
46+
AccountMeta::readonly_signer(self.base.key()),
47+
];
48+
49+
// instruction data
50+
// - [0..4 ]: instruction discriminator
51+
// - [4..36 ]: base pubkey
52+
// - [36..40]: seed length
53+
// - [40.. ]: seed (max 32)
54+
// - [.. +8]: account space
55+
// - [.. +32]: owner pubkey
56+
let mut instruction_data = [0; 112];
57+
instruction_data[0] = 9;
58+
instruction_data[4..36].copy_from_slice(self.base.key());
59+
instruction_data[36..40].copy_from_slice(&u32::to_le_bytes(self.seed.len() as u32));
60+
61+
let offset = 40 + self.seed.len();
62+
instruction_data[40..offset].copy_from_slice(self.seed.as_bytes());
63+
instruction_data[offset..offset + 8].copy_from_slice(&self.space.to_le_bytes());
64+
instruction_data[offset + 8..offset + 40].copy_from_slice(self.owner.as_ref());
65+
66+
let instruction = Instruction {
67+
program_id: &crate::ID,
68+
accounts: &account_metas,
69+
data: &instruction_data[..offset + 40],
70+
};
71+
72+
invoke_signed(&instruction, &[self.account, self.base], signers)
73+
}
74+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
entrypoint::ProgramResult,
4+
instruction::{AccountMeta, Instruction, Signer},
5+
program::invoke_signed,
6+
pubkey::Pubkey,
7+
};
8+
9+
/// Assign account to a program
10+
///
11+
/// ### Accounts:
12+
/// 0. `[WRITE, SIGNER]` Assigned account public key
13+
pub struct Assign<'a, 'b> {
14+
/// Account to be assigned.
15+
pub account: &'a AccountInfo,
16+
17+
/// Program account to assign as owner.
18+
pub owner: &'b Pubkey,
19+
}
20+
21+
impl<'a, 'b> Assign<'a, 'b> {
22+
#[inline(always)]
23+
pub fn invoke(&self) -> ProgramResult {
24+
self.invoke_signed(&[])
25+
}
26+
27+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
28+
// account metadata
29+
let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())];
30+
31+
// instruction data
32+
// - [0..4 ]: instruction discriminator
33+
// - [4..36]: owner pubkey
34+
let mut instruction_data = [0; 36];
35+
instruction_data[0] = 1;
36+
instruction_data[4..36].copy_from_slice(self.owner.as_ref());
37+
38+
let instruction = Instruction {
39+
program_id: &crate::ID,
40+
accounts: &account_metas,
41+
data: &instruction_data,
42+
};
43+
44+
invoke_signed(&instruction, &[self.account], signers)
45+
}
46+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
entrypoint::ProgramResult,
4+
instruction::{AccountMeta, Instruction, Signer},
5+
program::invoke_signed,
6+
pubkey::Pubkey,
7+
};
8+
9+
/// Assign account to a program based on a seed.
10+
///
11+
/// ### Accounts:
12+
/// 0. `[WRITE]` Assigned account
13+
/// 1. `[SIGNER]` Base account
14+
pub struct AssignWithSeed<'a, 'b, 'c> {
15+
/// Allocated account.
16+
pub account: &'a AccountInfo,
17+
18+
/// Base account.
19+
///
20+
/// The account matching the base Pubkey below must be provided as
21+
/// a signer, but may be the same as the funding account and provided
22+
/// as account 0.
23+
pub base: &'a AccountInfo,
24+
25+
/// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`.
26+
pub seed: &'b str,
27+
28+
/// Address of program that will own the new account.
29+
pub owner: &'c Pubkey,
30+
}
31+
32+
impl<'a, 'b, 'c> AssignWithSeed<'a, 'b, 'c> {
33+
#[inline(always)]
34+
pub fn invoke(&self) -> ProgramResult {
35+
self.invoke_signed(&[])
36+
}
37+
38+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
39+
// account metadata
40+
let account_metas: [AccountMeta; 2] = [
41+
AccountMeta::writable_signer(self.account.key()),
42+
AccountMeta::readonly_signer(self.base.key()),
43+
];
44+
45+
// instruction data
46+
// - [0..4 ]: instruction discriminator
47+
// - [4..36 ]: base pubkey
48+
// - [36..40]: seed length
49+
// - [40.. ]: seed (max 32)
50+
// - [.. +32]: owner pubkey
51+
let mut instruction_data = [0; 104];
52+
instruction_data[0] = 10;
53+
instruction_data[4..36].copy_from_slice(self.base.key());
54+
instruction_data[36..40].copy_from_slice(&u32::to_le_bytes(self.seed.len() as u32));
55+
56+
let offset = 40 + self.seed.len();
57+
instruction_data[40..offset].copy_from_slice(self.seed.as_bytes());
58+
instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref());
59+
60+
let instruction = Instruction {
61+
program_id: &crate::ID,
62+
accounts: &account_metas,
63+
data: &instruction_data[..offset + 32],
64+
};
65+
66+
invoke_signed(&instruction, &[self.account, self.base], signers)
67+
}
68+
}

0 commit comments

Comments
 (0)