Skip to content

Commit ff23f06

Browse files
committed
Address review comments (anza-xyz#78)
* [wip]: Address review comments * [wip]: Fix pointer reference * [wip]: Add logger buffer size tests * Remove unused * More logger tests * Rename program to cpi * Remove dynamic allocation * Fixed signed tests * Fix review comments * Fix unsigned test case * Add is_owner_by helper
1 parent 09dcf32 commit ff23f06

File tree

2 files changed

+348
-1
lines changed

2 files changed

+348
-1
lines changed

instruction-view/src/cpi.rs

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
//! Cross-program invocation helpers.
2+
3+
use core::{mem::MaybeUninit, ops::Deref};
4+
5+
use crate::{
6+
account_info::AccountInfo,
7+
instruction::{Account, AccountMeta, Instruction, Signer},
8+
program_error::ProgramError,
9+
pubkey::Pubkey,
10+
ProgramResult,
11+
};
12+
13+
/// Maximum number of accounts that can be passed to a cross-program invocation.
14+
const MAX_CPI_ACCOUNTS: usize = 64;
15+
16+
/// An `Instruction` as expected by `sol_invoke_signed_c`.
17+
///
18+
/// DO NOT EXPOSE THIS STRUCT:
19+
///
20+
/// To ensure pointers are valid upon use, the scope of this struct should
21+
/// only be limited to the stack where sol_invoke_signed_c happens and then
22+
/// discarded immediately after.
23+
#[repr(C)]
24+
#[derive(Debug, PartialEq, Clone)]
25+
struct CInstruction<'a> {
26+
/// Public key of the program.
27+
program_id: *const Pubkey,
28+
29+
/// Accounts expected by the program instruction.
30+
accounts: *const AccountMeta<'a>,
31+
32+
/// Number of accounts expected by the program instruction.
33+
accounts_len: u64,
34+
35+
/// Data expected by the program instruction.
36+
data: *const u8,
37+
38+
/// Length of the data expected by the program instruction.
39+
data_len: u64,
40+
}
41+
42+
impl<'a> From<&Instruction<'a, '_, '_, '_>> for CInstruction<'a> {
43+
fn from(instruction: &Instruction<'a, '_, '_, '_>) -> Self {
44+
CInstruction {
45+
program_id: instruction.program_id,
46+
accounts: instruction.accounts.as_ptr(),
47+
accounts_len: instruction.accounts.len() as u64,
48+
data: instruction.data.as_ptr(),
49+
data_len: instruction.data.len() as u64,
50+
}
51+
}
52+
}
53+
54+
/// Invoke a cross-program instruction.
55+
///
56+
/// # Important
57+
///
58+
/// The accounts on the `account_infos` slice must be in the same order as the
59+
/// `accounts` field of the `instruction`.
60+
#[inline(always)]
61+
pub fn invoke<const ACCOUNTS: usize>(
62+
instruction: &Instruction,
63+
account_infos: &[&AccountInfo; ACCOUNTS],
64+
) -> ProgramResult {
65+
invoke_signed(instruction, account_infos, &[])
66+
}
67+
68+
/// Invoke a cross-program instruction from a slice of `AccountInfo`s.
69+
///
70+
/// # Important
71+
///
72+
/// The accounts on the `account_infos` slice must be in the same order as the
73+
/// `accounts` field of the `instruction`.
74+
#[inline(always)]
75+
pub fn slice_invoke(instruction: &Instruction, account_infos: &[&AccountInfo]) -> ProgramResult {
76+
slice_invoke_signed(instruction, account_infos, &[])
77+
}
78+
79+
/// Invoke a cross-program instruction with signatures.
80+
///
81+
/// # Important
82+
///
83+
/// The accounts on the `account_infos` slice must be in the same order as the
84+
/// `accounts` field of the `instruction`.
85+
pub fn invoke_signed<const ACCOUNTS: usize>(
86+
instruction: &Instruction,
87+
account_infos: &[&AccountInfo; ACCOUNTS],
88+
signers_seeds: &[Signer],
89+
) -> ProgramResult {
90+
if instruction.accounts.len() < ACCOUNTS {
91+
return Err(ProgramError::NotEnoughAccountKeys);
92+
}
93+
94+
const UNINIT: MaybeUninit<Account> = MaybeUninit::<Account>::uninit();
95+
let mut accounts = [UNINIT; ACCOUNTS];
96+
97+
for index in 0..ACCOUNTS {
98+
let account_info = account_infos[index];
99+
let account_meta = &instruction.accounts[index];
100+
101+
if account_info.key() != account_meta.pubkey {
102+
return Err(ProgramError::InvalidArgument);
103+
}
104+
105+
if account_meta.is_writable {
106+
account_info.check_borrow_mut_data()?;
107+
account_info.check_borrow_mut_lamports()?;
108+
} else {
109+
account_info.check_borrow_data()?;
110+
account_info.check_borrow_lamports()?;
111+
}
112+
113+
accounts[index].write(Account::from(account_infos[index]));
114+
}
115+
116+
unsafe {
117+
invoke_signed_unchecked(
118+
instruction,
119+
core::slice::from_raw_parts(accounts.as_ptr() as _, ACCOUNTS),
120+
signers_seeds,
121+
);
122+
}
123+
124+
Ok(())
125+
}
126+
127+
/// Invoke a cross-program instruction with signatures from a slice of
128+
/// `AccountInfo`s.
129+
///
130+
/// # Important
131+
///
132+
/// The accounts on the `account_infos` slice must be in the same order as the
133+
/// `accounts` field of the `instruction`.
134+
pub fn slice_invoke_signed(
135+
instruction: &Instruction,
136+
account_infos: &[&AccountInfo],
137+
signers_seeds: &[Signer],
138+
) -> ProgramResult {
139+
if instruction.accounts.len() < account_infos.len() {
140+
return Err(ProgramError::NotEnoughAccountKeys);
141+
}
142+
143+
if account_infos.len() > MAX_CPI_ACCOUNTS {
144+
return Err(ProgramError::InvalidArgument);
145+
}
146+
147+
const UNINIT: MaybeUninit<Account> = MaybeUninit::<Account>::uninit();
148+
let mut accounts = [UNINIT; MAX_CPI_ACCOUNTS];
149+
let mut len = 0;
150+
151+
for (account_info, account_meta) in account_infos.iter().zip(instruction.accounts.iter()) {
152+
if account_info.key() != account_meta.pubkey {
153+
return Err(ProgramError::InvalidArgument);
154+
}
155+
156+
if account_meta.is_writable {
157+
account_info.check_borrow_mut_data()?;
158+
account_info.check_borrow_mut_lamports()?;
159+
} else {
160+
account_info.check_borrow_data()?;
161+
account_info.check_borrow_lamports()?;
162+
}
163+
// SAFETY: The number of accounts has been validated to be less than
164+
// `MAX_CPI_ACCOUNTS`.
165+
unsafe {
166+
accounts
167+
.get_unchecked_mut(len)
168+
.write(Account::from(*account_info));
169+
}
170+
171+
len += 1;
172+
}
173+
// SAFETY: The accounts have been validated.
174+
unsafe {
175+
invoke_signed_unchecked(
176+
instruction,
177+
core::slice::from_raw_parts(accounts.as_ptr() as _, len),
178+
signers_seeds,
179+
);
180+
}
181+
182+
Ok(())
183+
}
184+
185+
/// Invoke a cross-program instruction but don't enforce Rust's aliasing rules.
186+
///
187+
/// This function does not check that [`Account`]s are properly borrowable.
188+
/// Those checks consume CPU cycles that this function avoids.
189+
///
190+
/// # Safety
191+
///
192+
/// If any of the writable accounts passed to the callee contain data that is
193+
/// borrowed within the calling program, and that data is written to by the
194+
/// callee, then Rust's aliasing rules will be violated and cause undefined
195+
/// behavior.
196+
#[inline(always)]
197+
pub unsafe fn invoke_unchecked(instruction: &Instruction, accounts: &[Account]) {
198+
invoke_signed_unchecked(instruction, accounts, &[])
199+
}
200+
201+
/// Invoke a cross-program instruction with signatures but don't enforce Rust's
202+
/// aliasing rules.
203+
///
204+
/// This function does not check that [`Account`]s are properly borrowable.
205+
/// Those checks consume CPU cycles that this function avoids.
206+
///
207+
/// # Safety
208+
///
209+
/// If any of the writable accounts passed to the callee contain data that is
210+
/// borrowed within the calling program, and that data is written to by the
211+
/// callee, then Rust's aliasing rules will be violated and cause undefined
212+
/// behavior.
213+
pub unsafe fn invoke_signed_unchecked(
214+
instruction: &Instruction,
215+
accounts: &[Account],
216+
signers_seeds: &[Signer],
217+
) {
218+
#[cfg(target_os = "solana")]
219+
{
220+
let instruction = CInstruction::from(instruction);
221+
unsafe {
222+
crate::syscalls::sol_invoke_signed_c(
223+
&instruction as *const _ as *const u8,
224+
accounts as *const _ as *const u8,
225+
accounts.len() as u64,
226+
signers_seeds as *const _ as *const u8,
227+
signers_seeds.len() as u64,
228+
)
229+
};
230+
}
231+
232+
#[cfg(not(target_os = "solana"))]
233+
core::hint::black_box((instruction, accounts, signers_seeds));
234+
}
235+
236+
/// Maximum size that can be set using [`set_return_data`].
237+
pub const MAX_RETURN_DATA: usize = 1024;
238+
239+
/// Set the running program's return data.
240+
///
241+
/// Return data is a dedicated per-transaction buffer for data passed
242+
/// from cross-program invoked programs back to their caller.
243+
///
244+
/// The maximum size of return data is [`MAX_RETURN_DATA`]. Return data is
245+
/// retrieved by the caller with [`get_return_data`].
246+
pub fn set_return_data(data: &[u8]) {
247+
#[cfg(target_os = "solana")]
248+
unsafe {
249+
crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64)
250+
};
251+
252+
#[cfg(not(target_os = "solana"))]
253+
core::hint::black_box(data);
254+
}
255+
256+
/// Get the return data from an invoked program.
257+
///
258+
/// For every transaction there is a single buffer with maximum length
259+
/// [`MAX_RETURN_DATA`], paired with a [`Pubkey`] representing the program ID of
260+
/// the program that most recently set the return data. Thus the return data is
261+
/// a global resource and care must be taken to ensure that it represents what
262+
/// is expected: called programs are free to set or not set the return data; and
263+
/// the return data may represent values set by programs multiple calls down the
264+
/// call stack, depending on the circumstances of transaction execution.
265+
///
266+
/// Return data is set by the callee with [`set_return_data`].
267+
///
268+
/// Return data is cleared before every CPI invocation &mdash; a program that
269+
/// has invoked no other programs can expect the return data to be `None`; if no
270+
/// return data was set by the previous CPI invocation, then this function
271+
/// returns `None`.
272+
///
273+
/// Return data is not cleared after returning from CPI invocations &mdash; a
274+
/// program that has called another program may retrieve return data that was
275+
/// not set by the called program, but instead set by a program further down the
276+
/// call stack; or, if a program calls itself recursively, it is possible that
277+
/// the return data was not set by the immediate call to that program, but by a
278+
/// subsequent recursive call to that program. Likewise, an external RPC caller
279+
/// may see return data that was not set by the program it is directly calling,
280+
/// but by a program that program called.
281+
///
282+
/// For more about return data see the [documentation for the return data proposal][rdp].
283+
///
284+
/// [rdp]: https://docs.solanalabs.com/proposals/return-data
285+
pub fn get_return_data() -> Option<ReturnData> {
286+
#[cfg(target_os = "solana")]
287+
{
288+
const UNINIT_BYTE: core::mem::MaybeUninit<u8> = core::mem::MaybeUninit::<u8>::uninit();
289+
let mut data = [UNINIT_BYTE; MAX_RETURN_DATA];
290+
let mut program_id = Pubkey::default();
291+
292+
let size = unsafe {
293+
crate::syscalls::sol_get_return_data(
294+
data.as_mut_ptr() as *mut u8,
295+
data.len() as u64,
296+
&mut program_id,
297+
)
298+
};
299+
300+
if size == 0 {
301+
None
302+
} else {
303+
Some(ReturnData {
304+
program_id,
305+
data,
306+
size: core::cmp::min(size as usize, MAX_RETURN_DATA),
307+
})
308+
}
309+
}
310+
311+
#[cfg(not(target_os = "solana"))]
312+
core::hint::black_box(None)
313+
}
314+
315+
/// Struct to hold the return data from an invoked program.
316+
pub struct ReturnData {
317+
/// Program that most recently set the return data.
318+
program_id: Pubkey,
319+
320+
/// Return data set by the program.
321+
data: [core::mem::MaybeUninit<u8>; MAX_RETURN_DATA],
322+
323+
/// Length of the return data.
324+
size: usize,
325+
}
326+
327+
impl ReturnData {
328+
/// Returns the program that most recently set the return data.
329+
pub fn program_id(&self) -> &Pubkey {
330+
&self.program_id
331+
}
332+
333+
/// Return the data set by the program.
334+
pub fn as_slice(&self) -> &[u8] {
335+
unsafe { core::slice::from_raw_parts(self.data.as_ptr() as _, self.size) }
336+
}
337+
}
338+
339+
impl Deref for ReturnData {
340+
type Target = [u8];
341+
342+
fn deref(&self) -> &Self::Target {
343+
self.as_slice()
344+
}
345+
}

instruction-view/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use core::{marker::PhantomData, ops::Deref};
55
use crate::{account_info::AccountInfo, pubkey::Pubkey};
66

77
/// Information about a CPI instruction.
8-
#[repr(C)]
98
#[derive(Debug, Clone)]
109
pub struct Instruction<'a, 'b, 'c, 'd>
1110
where
@@ -87,6 +86,9 @@ impl<'a> From<&'a AccountInfo> for Account<'a> {
8786
data_len: account.data_len() as u64,
8887
data: offset(account.raw, 88),
8988
owner: offset(account.raw, 40),
89+
// The `rent_epoch` field is not present in the `AccountInfo` struct,
90+
// since the value occurs after the variable data of the account in
91+
// the runtime input data.
9092
rent_epoch: 0,
9193
is_signer: account.is_signer(),
9294
is_writable: account.is_writable(),

0 commit comments

Comments
 (0)