Skip to content

Commit a5c106d

Browse files
committed
refactor: cpi context account, system program account checks, account compression program nullify create output order
1 parent 96c2faa commit a5c106d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2173
-1259
lines changed

program-libs/account-checks/src/account_info/pinocchio.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo {
1919
bytes
2020
}
2121

22+
#[inline(always)]
2223
fn is_writable(&self) -> bool {
2324
self.is_writable()
2425
}
2526

27+
#[inline(always)]
2628
fn is_signer(&self) -> bool {
2729
self.is_signer()
2830
}
2931

32+
#[inline(always)]
3033
fn executable(&self) -> bool {
3134
self.executable()
3235
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
use std::panic::Location;
2+
3+
use crate::{
4+
checks::{check_mut, check_non_mut, check_signer},
5+
AccountError, AccountInfoTrait,
6+
};
7+
8+
/// Iterator over accounts that provides detailed error messages when accounts are missing.
9+
///
10+
/// This iterator helps with debugging account setup issues by tracking which accounts
11+
/// are requested and providing clear error messages when there are insufficient accounts.
12+
pub struct AccountIterator<'info, T: AccountInfoTrait> {
13+
accounts: &'info [T],
14+
position: usize,
15+
#[allow(unused)]
16+
owner: [u8; 32],
17+
}
18+
19+
impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> {
20+
/// Create a new AccountIterator from a slice of AccountInfo.
21+
#[inline(always)]
22+
pub fn new(accounts: &'info [T]) -> Self {
23+
Self {
24+
accounts,
25+
position: 0,
26+
owner: [0; 32],
27+
}
28+
}
29+
30+
#[inline(always)]
31+
pub fn new_with_owner(accounts: &'info [T], owner: [u8; 32]) -> Self {
32+
Self {
33+
accounts,
34+
position: 0,
35+
owner,
36+
}
37+
}
38+
39+
/// Get the next account with a descriptive name.
40+
///
41+
/// # Arguments
42+
/// * `account_name` - A descriptive name for the account being requested (for debugging)
43+
///
44+
/// # Returns
45+
/// * `Ok(&T)` - The next account in the iterator
46+
/// * `Err(AccountError::NotEnoughAccountKeys)` - If no more accounts are available
47+
#[track_caller]
48+
#[inline(always)]
49+
pub fn next_account(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
50+
if self.position >= self.accounts.len() {
51+
let location = Location::caller();
52+
solana_msg::msg!(
53+
"ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}",
54+
account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column()
55+
);
56+
return Err(AccountError::NotEnoughAccountKeys);
57+
}
58+
59+
let account = &self.accounts[self.position];
60+
self.position += 1;
61+
62+
Ok(account)
63+
}
64+
65+
#[inline(always)]
66+
#[track_caller]
67+
pub fn next_option(
68+
&mut self,
69+
account_name: &str,
70+
is_some: bool,
71+
) -> Result<Option<&'info T>, AccountError> {
72+
if is_some {
73+
let account_info = self.next_account(account_name)?;
74+
Ok(Some(account_info))
75+
} else {
76+
Ok(None)
77+
}
78+
}
79+
80+
#[inline(always)]
81+
#[track_caller]
82+
pub fn next_option_mut(
83+
&mut self,
84+
account_name: &str,
85+
is_some: bool,
86+
) -> Result<Option<&'info T>, AccountError> {
87+
if is_some {
88+
let account_info = self.next_mut(account_name)?;
89+
Ok(Some(account_info))
90+
} else {
91+
Ok(None)
92+
}
93+
}
94+
95+
#[inline(always)]
96+
#[track_caller]
97+
pub fn next_signer_mut(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
98+
let account_info = self.next_signer(account_name)?;
99+
check_mut(account_info)
100+
.inspect_err(|e| self.print_on_error(e, account_name, Location::caller()))?;
101+
Ok(account_info)
102+
}
103+
104+
#[inline(always)]
105+
#[track_caller]
106+
pub fn next_signer(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
107+
let account_info = self.next_account(account_name)?;
108+
check_signer(account_info)
109+
.inspect_err(|e| self.print_on_error(e, account_name, Location::caller()))?;
110+
Ok(account_info)
111+
}
112+
113+
#[inline(always)]
114+
#[track_caller]
115+
pub fn next_signer_non_mut(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
116+
let account_info = self.next_signer(account_name)?;
117+
check_non_mut(account_info)
118+
.inspect_err(|e| self.print_on_error(e, account_name, Location::caller()))?;
119+
Ok(account_info)
120+
}
121+
122+
#[inline(always)]
123+
#[track_caller]
124+
pub fn next_non_mut(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
125+
let account_info = self.next_account(account_name)?;
126+
check_non_mut(account_info)
127+
.inspect_err(|e| self.print_on_error(e, account_name, Location::caller()))?;
128+
Ok(account_info)
129+
}
130+
131+
#[inline(always)]
132+
#[track_caller]
133+
pub fn next_mut(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
134+
let account_info = self.next_account(account_name)?;
135+
check_mut(account_info)
136+
.inspect_err(|e| self.print_on_error(e, account_name, Location::caller()))?;
137+
Ok(account_info)
138+
}
139+
140+
/// Get all remaining accounts in the iterator.
141+
#[inline(always)]
142+
#[track_caller]
143+
pub fn remaining(&self) -> Result<&'info [T], AccountError> {
144+
if self.position >= self.accounts.len() {
145+
let location = Location::caller();
146+
let account_name = "remaining accounts";
147+
solana_msg::msg!(
148+
"ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}",
149+
account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column()
150+
);
151+
return Err(AccountError::NotEnoughAccountKeys);
152+
}
153+
Ok(&self.accounts[self.position..])
154+
}
155+
156+
/// Get all remaining accounts in the iterator.
157+
#[inline(always)]
158+
#[track_caller]
159+
pub fn remaining_unchecked(&self) -> Result<&'info [T], AccountError> {
160+
if self.position >= self.accounts.len() {
161+
Ok(&[])
162+
} else {
163+
Ok(&self.accounts[self.position..])
164+
}
165+
}
166+
167+
/// Get the current position in the iterator.
168+
#[inline(always)]
169+
pub fn position(&self) -> usize {
170+
self.position
171+
}
172+
173+
/// Get the total number of accounts.
174+
#[inline(always)]
175+
pub fn len(&self) -> usize {
176+
self.accounts.len()
177+
}
178+
179+
/// Check if the iterator is empty.
180+
#[inline(always)]
181+
pub fn is_empty(&self) -> bool {
182+
self.accounts.is_empty()
183+
}
184+
185+
#[inline(always)]
186+
pub fn iterator_is_empty(&self) -> bool {
187+
self.len() == self.position()
188+
}
189+
190+
#[cold]
191+
#[inline(never)]
192+
fn print_on_error(&self, error: &AccountError, account_name: &str, location: &Location) {
193+
solana_msg::msg!(
194+
"ERROR: {}. for account '{}' at index {} {}:{}:{}",
195+
error,
196+
account_name,
197+
self.position.saturating_sub(1),
198+
location.file(),
199+
location.line(),
200+
location.column()
201+
);
202+
}
203+
}

program-libs/account-checks/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub enum AccountError {
3030
ProgramNotExecutable,
3131
#[error("Account not zeroed.")]
3232
AccountNotZeroed,
33+
#[error("Not enough account keys provided.")]
34+
NotEnoughAccountKeys,
3335
#[error("Pinocchio program error with code: {0}")]
3436
PinocchioProgramError(u32),
3537
}
@@ -52,6 +54,7 @@ impl From<AccountError> for u32 {
5254
AccountError::InvalidProgramId => 12017,
5355
AccountError::ProgramNotExecutable => 12018,
5456
AccountError::AccountNotZeroed => 12019,
57+
AccountError::NotEnoughAccountKeys => 12020,
5558
AccountError::PinocchioProgramError(code) => code,
5659
}
5760
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
pub mod account_info;
2+
pub mod account_iterator;
23
pub mod checks;
34
pub mod discriminator;
45
pub mod error;
6+
pub mod packed_accounts;
57

68
pub use account_info::account_info_trait::AccountInfoTrait;
9+
pub use account_iterator::AccountIterator;
10+
pub use error::AccountError;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::panic::Location;
2+
3+
use crate::{AccountError, AccountInfoTrait};
4+
5+
/// Dynamic accounts slice for index-based access
6+
/// Contains mint, owner, delegate, merkle tree, and queue accounts
7+
pub struct ProgramPackedAccounts<'info, A: AccountInfoTrait> {
8+
pub accounts: &'info [A],
9+
}
10+
11+
impl<A: AccountInfoTrait> ProgramPackedAccounts<'_, A> {
12+
/// Get account by index with bounds checking
13+
#[track_caller]
14+
#[inline(always)]
15+
pub fn get(&self, index: usize, name: &str) -> Result<&A, AccountError> {
16+
if index >= self.accounts.len() {
17+
let location = Location::caller();
18+
solana_msg::msg!(
19+
"ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}",
20+
name, index, self.accounts.len(), location.file(), location.line(), location.column()
21+
);
22+
return Err(AccountError::NotEnoughAccountKeys);
23+
}
24+
Ok(&self.accounts[index])
25+
}
26+
27+
/// Get account by u8 index with bounds checking
28+
#[track_caller]
29+
#[inline(always)]
30+
pub fn get_u8(&self, index: u8, name: &str) -> Result<&A, AccountError> {
31+
self.get(index as usize, name)
32+
}
33+
}

program-libs/compressed-account/src/compressed_account.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22

33
use light_hasher::{Hasher, Poseidon};
4+
use light_zero_copy::{ZeroCopy, ZeroCopyMut};
45

56
use crate::{
67
address::pack_account,
@@ -11,7 +12,8 @@ use crate::{
1112
AnchorDeserialize, AnchorSerialize, CompressedAccountError, Pubkey, TreeType,
1213
};
1314

14-
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
15+
#[repr(C)]
16+
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)]
1517
pub struct PackedCompressedAccountWithMerkleContext {
1618
pub compressed_account: CompressedAccount,
1719
pub merkle_context: PackedMerkleContext,
@@ -133,7 +135,8 @@ pub struct ReadOnlyCompressedAccount {
133135
pub root_index: u16,
134136
}
135137

136-
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
138+
#[repr(C)]
139+
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)]
137140
pub struct PackedReadOnlyCompressedAccount {
138141
pub account_hash: [u8; 32],
139142
pub merkle_context: PackedMerkleContext,
@@ -149,7 +152,18 @@ pub struct MerkleContext {
149152
pub tree_type: TreeType,
150153
}
151154

152-
#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)]
155+
#[repr(C)]
156+
#[derive(
157+
Debug,
158+
Clone,
159+
Copy,
160+
AnchorSerialize,
161+
AnchorDeserialize,
162+
PartialEq,
163+
Default,
164+
ZeroCopy,
165+
ZeroCopyMut,
166+
)]
153167
pub struct PackedMerkleContext {
154168
pub merkle_tree_pubkey_index: u8,
155169
pub queue_pubkey_index: u8,
@@ -217,7 +231,8 @@ pub fn pack_merkle_context(
217231
.collect::<Vec<_>>()
218232
}
219233

220-
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
234+
#[repr(C)]
235+
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)]
221236
pub struct CompressedAccount {
222237
pub owner: Pubkey,
223238
pub lamports: u64,
@@ -234,7 +249,8 @@ pub struct InCompressedAccount {
234249
pub address: Option<[u8; 32]>,
235250
}
236251

237-
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
252+
#[repr(C)]
253+
#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)]
238254
pub struct CompressedAccountData {
239255
pub discriminator: [u8; 8],
240256
pub data: Vec<u8>,
@@ -283,7 +299,6 @@ pub fn hash_with_hashed_values(
283299

284300
vec.push(lamports_bytes.as_slice());
285301
}
286-
287302
if let Some(address) = address {
288303
vec.push(address);
289304
}

0 commit comments

Comments
 (0)