Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion programs/memo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ rust-version = { workspace = true }
crate-type = ["rlib"]

[dependencies]
pinocchio = { version = "0.8.3", path = "../../sdk/pinocchio" }
pinocchio = { version = "0.8.4", path = "../../sdk/pinocchio" }
pinocchio-pubkey = { workspace = true }
2 changes: 1 addition & 1 deletion sdk/pinocchio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pinocchio"
description = "Create Solana programs with no dependencies attached"
version = "0.8.3"
version = "0.8.4"
edition = { workspace = true }
license = { workspace = true }
readme = "./README.md"
Expand Down
69 changes: 64 additions & 5 deletions sdk/pinocchio/src/account_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ use crate::{program_error::ProgramError, pubkey::Pubkey, ProgramResult};
/// single top-level instruction.
pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;

/// Represents masks for borrow state of an account.
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum BorrowState {
/// Mask to check whether an account is already borrowed.
///
/// This will test both data and lamports borrow state.
Borrowed = 0b_1111_1111,

/// Mask to check whether an account is already mutably borrowed.
///
/// This will test both data and lamports mutable borrow state.
MutablyBorrowed = 0b_1000_1000,
}

/// Raw account data.
///
/// This data is wrapped in an `AccountInfo` struct, which provides safe access
Expand Down Expand Up @@ -172,6 +187,15 @@ impl AccountInfo {
core::ptr::write_volatile(&(*self.raw).owner as *const _ as *mut Pubkey, *new_owner);
}

/// Return true if the account borrow state is set to the given state.
///
/// This will test both data and lamports borrow state.
#[inline(always)]
pub fn is_borrowed(&self, state: BorrowState) -> bool {
let borrow_state = unsafe { (*self.raw).borrow_state };
borrow_state & (state as u8) != 0
}

/// Returns a read-only reference to the lamports in the account.
///
/// # Safety
Expand Down Expand Up @@ -222,7 +246,7 @@ impl AccountInfo {
/// field is already mutable borrowed or if 7 borrows already exist.
pub fn try_borrow_lamports(&self) -> Result<Ref<u64>, ProgramError> {
// check if the account lamports are already borrowed
self.check_borrow_lamports()?;
self.can_borrow_lamports()?;

let borrow_state = unsafe { &mut (*self.raw).borrow_state };
// increment the immutable borrow count
Expand All @@ -241,7 +265,7 @@ impl AccountInfo {
/// is already borrowed in any form.
pub fn try_borrow_mut_lamports(&self) -> Result<RefMut<u64>, ProgramError> {
// check if the account lamports are already borrowed
self.check_borrow_mut_lamports()?;
self.can_borrow_mut_lamports()?;

let borrow_state = unsafe { &mut (*self.raw).borrow_state };
// set the mutable lamport borrow flag
Expand All @@ -258,8 +282,16 @@ impl AccountInfo {

/// Checks if it is possible to get a read-only reference to the lamport field,
/// failing if the field is already mutable borrowed or if 7 borrows already exist.
#[deprecated(since = "0.8.4", note = "Use `can_borrow_lamports` instead")]
#[inline(always)]
pub fn check_borrow_lamports(&self) -> Result<(), ProgramError> {
self.can_borrow_lamports()
}

/// Checks if it is possible to get a read-only reference to the lamport field,
/// failing if the field is already mutable borrowed or if 7 borrows already exist.
#[inline(always)]
pub fn can_borrow_lamports(&self) -> Result<(), ProgramError> {
let borrow_state = unsafe { (*self.raw).borrow_state };

// check if mutable borrow is already taken
Expand All @@ -277,8 +309,16 @@ impl AccountInfo {

/// Checks if it is possible to get a mutable reference to the lamport field,
/// failing if the field is already borrowed in any form.
#[deprecated(since = "0.8.4", note = "Use `can_borrow_mut_lamports` instead")]
#[inline(always)]
pub fn check_borrow_mut_lamports(&self) -> Result<(), ProgramError> {
self.can_borrow_mut_lamports()
}

/// Checks if it is possible to get a mutable reference to the lamport field,
/// failing if the field is already borrowed in any form.
#[inline(always)]
pub fn can_borrow_mut_lamports(&self) -> Result<(), ProgramError> {
let borrow_state = unsafe { (*self.raw).borrow_state };

// check if any borrow (mutable or immutable) is already taken for lamports
Expand All @@ -293,7 +333,7 @@ impl AccountInfo {
/// is already mutable borrowed or if 7 borrows already exist.
pub fn try_borrow_data(&self) -> Result<Ref<[u8]>, ProgramError> {
// check if the account data is already borrowed
self.check_borrow_data()?;
self.can_borrow_data()?;

let borrow_state = unsafe { &mut (*self.raw).borrow_state };
// increment the immutable data borrow count
Expand All @@ -312,7 +352,7 @@ impl AccountInfo {
/// is already borrowed in any form.
pub fn try_borrow_mut_data(&self) -> Result<RefMut<[u8]>, ProgramError> {
// check if the account data is already borrowed
self.check_borrow_mut_data()?;
self.can_borrow_mut_data()?;

let borrow_state = unsafe { &mut (*self.raw).borrow_state };
// set the mutable data borrow flag
Expand All @@ -329,8 +369,16 @@ impl AccountInfo {

/// Checks if it is possible to get a read-only reference to the data field, failing
/// if the field is already mutable borrowed or if 7 borrows already exist.
#[deprecated(since = "0.8.4", note = "Use `can_borrow_data` instead")]
#[inline(always)]
pub fn check_borrow_data(&self) -> Result<(), ProgramError> {
self.can_borrow_data()
}

/// Checks if it is possible to get a read-only reference to the data field, failing
/// if the field is already mutable borrowed or if 7 borrows already exist.
#[inline(always)]
pub fn can_borrow_data(&self) -> Result<(), ProgramError> {
let borrow_state = unsafe { (*self.raw).borrow_state };

// check if mutable data borrow is already taken (most significant bit
Expand All @@ -349,8 +397,16 @@ impl AccountInfo {

/// Checks if it is possible to get a mutable reference to the data field, failing
/// if the field is already borrowed in any form.
#[deprecated(since = "0.8.4", note = "Use `can_borrow_mut_data` instead")]
#[inline(always)]
pub fn check_borrow_mut_data(&self) -> Result<(), ProgramError> {
self.can_borrow_mut_data()
}

/// Checks if it is possible to get a mutable reference to the data field, failing
/// if the field is already borrowed in any form.
#[inline(always)]
pub fn can_borrow_mut_data(&self) -> Result<(), ProgramError> {
let borrow_state = unsafe { (*self.raw).borrow_state };

// check if any borrow (mutable or immutable) is already taken for data
Expand Down Expand Up @@ -452,8 +508,11 @@ impl AccountInfo {
pub fn close(&self) -> ProgramResult {
// make sure the account is not borrowed since we are about to
// resize the data to zero
self.check_borrow_mut_data()?;
if self.is_borrowed(BorrowState::Borrowed) {
return Err(ProgramError::AccountBorrowFailed);
}

// SAFETY: The are no active borrows on the account data or lamports.
unsafe {
self.close_unchecked();
}
Expand Down
116 changes: 61 additions & 55 deletions sdk/pinocchio/src/cpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use core::{mem::MaybeUninit, ops::Deref};

use crate::{
account_info::AccountInfo,
instruction::{Account, AccountMeta, Instruction, Signer},
account_info::{AccountInfo, BorrowState},
instruction::{Account, Instruction, Signer},
program_error::ProgramError,
pubkey::Pubkey,
ProgramResult,
Expand All @@ -13,44 +13,6 @@ use crate::{
/// Maximum number of accounts that can be passed to a cross-program invocation.
pub const MAX_CPI_ACCOUNTS: usize = 64;

/// An `Instruction` as expected by `sol_invoke_signed_c`.
///
/// DO NOT EXPOSE THIS STRUCT:
///
/// To ensure pointers are valid upon use, the scope of this struct should
/// only be limited to the stack where sol_invoke_signed_c happens and then
/// discarded immediately after.
#[repr(C)]
#[derive(Debug, PartialEq, Clone)]
struct CInstruction<'a> {
/// Public key of the program.
program_id: *const Pubkey,

/// Accounts expected by the program instruction.
accounts: *const AccountMeta<'a>,

/// Number of accounts expected by the program instruction.
accounts_len: u64,

/// Data expected by the program instruction.
data: *const u8,

/// Length of the data expected by the program instruction.
data_len: u64,
}

impl<'a> From<&Instruction<'a, '_, '_, '_>> for CInstruction<'a> {
fn from(instruction: &Instruction<'a, '_, '_, '_>) -> Self {
CInstruction {
program_id: instruction.program_id,
accounts: instruction.accounts.as_ptr(),
accounts_len: instruction.accounts.len() as u64,
data: instruction.data.as_ptr(),
data_len: instruction.data.len() as u64,
}
}
}

/// Invoke a cross-program instruction.
///
/// # Important
Expand Down Expand Up @@ -82,6 +44,7 @@ pub fn slice_invoke(instruction: &Instruction, account_infos: &[&AccountInfo]) -
///
/// The accounts on the `account_infos` slice must be in the same order as the
/// `accounts` field of the `instruction`.
#[inline]
pub fn invoke_signed<const ACCOUNTS: usize>(
instruction: &Instruction,
account_infos: &[&AccountInfo; ACCOUNTS],
Expand All @@ -102,12 +65,14 @@ pub fn invoke_signed<const ACCOUNTS: usize>(
return Err(ProgramError::InvalidArgument);
}

if account_meta.is_writable {
account_info.check_borrow_mut_data()?;
account_info.check_borrow_mut_lamports()?;
let state = if account_meta.is_writable {
BorrowState::Borrowed
} else {
account_info.check_borrow_data()?;
account_info.check_borrow_lamports()?;
BorrowState::MutablyBorrowed
};

if account_info.is_borrowed(state) {
return Err(ProgramError::AccountBorrowFailed);
}

accounts[index].write(Account::from(account_infos[index]));
Expand All @@ -131,6 +96,7 @@ pub fn invoke_signed<const ACCOUNTS: usize>(
///
/// The accounts on the `account_infos` slice must be in the same order as the
/// `accounts` field of the `instruction`.
#[inline]
pub fn slice_invoke_signed(
instruction: &Instruction,
account_infos: &[&AccountInfo],
Expand All @@ -153,13 +119,16 @@ pub fn slice_invoke_signed(
return Err(ProgramError::InvalidArgument);
}

if account_meta.is_writable {
account_info.check_borrow_mut_data()?;
account_info.check_borrow_mut_lamports()?;
let state = if account_meta.is_writable {
BorrowState::Borrowed
} else {
account_info.check_borrow_data()?;
account_info.check_borrow_lamports()?;
BorrowState::MutablyBorrowed
};

if account_info.is_borrowed(state) {
return Err(ProgramError::AccountBorrowFailed);
}

// SAFETY: The number of accounts has been validated to be less than
// `MAX_CPI_ACCOUNTS`.
unsafe {
Expand Down Expand Up @@ -210,17 +179,52 @@ pub unsafe fn invoke_unchecked(instruction: &Instruction, accounts: &[Account])
/// borrowed within the calling program, and that data is written to by the
/// callee, then Rust's aliasing rules will be violated and cause undefined
/// behavior.
#[inline(always)]
pub unsafe fn invoke_signed_unchecked(
instruction: &Instruction,
accounts: &[Account],
signers_seeds: &[Signer],
) {
#[cfg(target_os = "solana")]
{
let instruction = CInstruction::from(instruction);
use crate::instruction::AccountMeta;

/// An `Instruction` as expected by `sol_invoke_signed_c`.
///
/// DO NOT EXPOSE THIS STRUCT:
///
/// To ensure pointers are valid upon use, the scope of this struct should
/// only be limited to the stack where sol_invoke_signed_c happens and then
/// discarded immediately after.
#[repr(C)]
struct CInstruction<'a> {
/// Public key of the program.
program_id: *const Pubkey,

/// Accounts expected by the program instruction.
accounts: *const AccountMeta<'a>,

/// Number of accounts expected by the program instruction.
accounts_len: u64,

/// Data expected by the program instruction.
data: *const u8,

/// Length of the data expected by the program instruction.
data_len: u64,
}

let cpi_instruction = CInstruction {
program_id: instruction.program_id,
accounts: instruction.accounts.as_ptr(),
accounts_len: instruction.accounts.len() as u64,
data: instruction.data.as_ptr(),
data_len: instruction.data.len() as u64,
};

unsafe {
crate::syscalls::sol_invoke_signed_c(
&instruction as *const _ as *const u8,
&cpi_instruction as *const _ as *const u8,
accounts as *const _ as *const u8,
accounts.len() as u64,
signers_seeds as *const _ as *const u8,
Expand All @@ -243,6 +247,7 @@ pub const MAX_RETURN_DATA: usize = 1024;
///
/// The maximum size of return data is [`MAX_RETURN_DATA`]. Return data is
/// retrieved by the caller with [`get_return_data`].
#[inline(always)]
pub fn set_return_data(data: &[u8]) {
#[cfg(target_os = "solana")]
unsafe {
Expand Down Expand Up @@ -282,26 +287,27 @@ pub fn set_return_data(data: &[u8]) {
/// For more about return data see the [documentation for the return data proposal][rdp].
///
/// [rdp]: https://docs.solanalabs.com/proposals/return-data
#[inline]
pub fn get_return_data() -> Option<ReturnData> {
#[cfg(target_os = "solana")]
{
const UNINIT_BYTE: core::mem::MaybeUninit<u8> = core::mem::MaybeUninit::<u8>::uninit();
let mut data = [UNINIT_BYTE; MAX_RETURN_DATA];
let mut program_id = Pubkey::default();
let mut program_id = MaybeUninit::<Pubkey>::uninit();

let size = unsafe {
crate::syscalls::sol_get_return_data(
data.as_mut_ptr() as *mut u8,
data.len() as u64,
&mut program_id,
program_id.as_mut_ptr() as *mut Pubkey,
)
};

if size == 0 {
None
} else {
Some(ReturnData {
program_id,
program_id: unsafe { program_id.assume_init() },
data,
size: core::cmp::min(size as usize, MAX_RETURN_DATA),
})
Expand Down
Loading