diff --git a/.gitignore b/.gitignore index 050c6035..c37d8487 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.idea /node_modules /target diff --git a/Cargo.toml b/Cargo.toml index 06e87351..581d3b09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ edition = "2021" license = "Apache-2.0" repository = "https://github.com/anza-xyz/pinocchio" -rust-version = "1.79" +rust-version = "1.80" [workspace.dependencies] five8_const = "0.1.4" @@ -28,7 +28,7 @@ regex = "1" syn = "1.0" [workspace.metadata.cli] -solana = "2.2.0" +solana = "2.3.0" [workspace.metadata.toolchains] build = "1.84.1" diff --git a/programs/token-2022/src/instructions/initialize_multisig.rs b/programs/token-2022/src/instructions/initialize_multisig.rs index 531f87a7..ea2072c9 100644 --- a/programs/token-2022/src/instructions/initialize_multisig.rs +++ b/programs/token-2022/src/instructions/initialize_multisig.rs @@ -85,8 +85,7 @@ impl InitializeMultisig<'_, '_, '_> { }; // Account info array - const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit(); - let mut acc_infos = [UNINIT_INFO; 2 + MAX_MULTISIG_SIGNERS]; + let mut acc_infos = [const { MaybeUninit::uninit() }; 2 + MAX_MULTISIG_SIGNERS]; unsafe { // SAFETY: diff --git a/programs/token-2022/src/instructions/initialize_multisig_2.rs b/programs/token-2022/src/instructions/initialize_multisig_2.rs index f98f892f..485e853e 100644 --- a/programs/token-2022/src/instructions/initialize_multisig_2.rs +++ b/programs/token-2022/src/instructions/initialize_multisig_2.rs @@ -77,8 +77,7 @@ impl InitializeMultisig2<'_, '_, '_> { }; // Account info array - const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit(); - let mut acc_infos = [UNINIT_INFO; 1 + MAX_MULTISIG_SIGNERS]; + let mut acc_infos = [const { MaybeUninit::uninit() }; 1 + MAX_MULTISIG_SIGNERS]; unsafe { // SAFETY: diff --git a/programs/token-2022/src/state/mint.rs b/programs/token-2022/src/state/mint.rs index ba47c796..ed8d4b09 100644 --- a/programs/token-2022/src/state/mint.rs +++ b/programs/token-2022/src/state/mint.rs @@ -43,7 +43,7 @@ impl Mint { /// This method performs owner and length validation on `AccountInfo`, safe borrowing /// the account data. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { if account_info.data_len() < Self::BASE_LEN { return Err(ProgramError::InvalidAccountData); } @@ -71,7 +71,7 @@ impl Mint { if account_info.data_len() < Self::BASE_LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountOwner); } Ok(Self::from_bytes_unchecked( diff --git a/programs/token-2022/src/state/multisig.rs b/programs/token-2022/src/state/multisig.rs index 831c0e4b..c26a42cd 100644 --- a/programs/token-2022/src/state/multisig.rs +++ b/programs/token-2022/src/state/multisig.rs @@ -29,7 +29,9 @@ impl Multisig { /// This method performs owner and length validation on `AccountInfo`, safe borrowing /// the account data. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info( + account_info: &AccountInfo, + ) -> Result, ProgramError> { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } @@ -57,7 +59,7 @@ impl Multisig { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountOwner); } Ok(Self::from_bytes_unchecked( diff --git a/programs/token-2022/src/state/token.rs b/programs/token-2022/src/state/token.rs index 56f3afec..4ed4522b 100644 --- a/programs/token-2022/src/state/token.rs +++ b/programs/token-2022/src/state/token.rs @@ -58,7 +58,7 @@ impl TokenAccount { #[inline] pub fn from_account_info( account_info: &AccountInfo, - ) -> Result, ProgramError> { + ) -> Result, ProgramError> { if account_info.data_len() < Self::BASE_LEN { return Err(ProgramError::InvalidAccountData); } @@ -86,7 +86,7 @@ impl TokenAccount { if account_info.data_len() < Self::BASE_LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountData); } Ok(Self::from_bytes_unchecked( diff --git a/programs/token/src/instructions/initialize_multisig.rs b/programs/token/src/instructions/initialize_multisig.rs index 4eb62b82..a8653eb0 100644 --- a/programs/token/src/instructions/initialize_multisig.rs +++ b/programs/token/src/instructions/initialize_multisig.rs @@ -80,8 +80,7 @@ impl InitializeMultisig<'_, '_> { }; // Account info array - const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit(); - let mut acc_infos = [UNINIT_INFO; 2 + MAX_MULTISIG_SIGNERS]; + let mut acc_infos = [const { MaybeUninit::uninit() }; 2 + MAX_MULTISIG_SIGNERS]; unsafe { // SAFETY: diff --git a/programs/token/src/instructions/initialize_multisig_2.rs b/programs/token/src/instructions/initialize_multisig_2.rs index e56de3ac..2ecd6413 100644 --- a/programs/token/src/instructions/initialize_multisig_2.rs +++ b/programs/token/src/instructions/initialize_multisig_2.rs @@ -72,8 +72,7 @@ impl InitializeMultisig2<'_, '_> { }; // Account info array - const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit(); - let mut acc_infos = [UNINIT_INFO; 1 + MAX_MULTISIG_SIGNERS]; + let mut acc_infos = [const { MaybeUninit::uninit() }; 1 + MAX_MULTISIG_SIGNERS]; unsafe { // SAFETY: diff --git a/programs/token/src/state/mint.rs b/programs/token/src/state/mint.rs index 52cfc700..c59658ab 100644 --- a/programs/token/src/state/mint.rs +++ b/programs/token/src/state/mint.rs @@ -43,7 +43,7 @@ impl Mint { /// This method performs owner and length validation on `AccountInfo`, safe borrowing /// the account data. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } @@ -71,7 +71,7 @@ impl Mint { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountOwner); } Ok(Self::from_bytes_unchecked( diff --git a/programs/token/src/state/multisig.rs b/programs/token/src/state/multisig.rs index 831c0e4b..c26a42cd 100644 --- a/programs/token/src/state/multisig.rs +++ b/programs/token/src/state/multisig.rs @@ -29,7 +29,9 @@ impl Multisig { /// This method performs owner and length validation on `AccountInfo`, safe borrowing /// the account data. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info( + account_info: &AccountInfo, + ) -> Result, ProgramError> { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } @@ -57,7 +59,7 @@ impl Multisig { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountOwner); } Ok(Self::from_bytes_unchecked( diff --git a/programs/token/src/state/token.rs b/programs/token/src/state/token.rs index 44f48ce1..ddb1b159 100644 --- a/programs/token/src/state/token.rs +++ b/programs/token/src/state/token.rs @@ -58,7 +58,7 @@ impl TokenAccount { #[inline] pub fn from_account_info( account_info: &AccountInfo, - ) -> Result, ProgramError> { + ) -> Result, ProgramError> { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } @@ -86,7 +86,7 @@ impl TokenAccount { if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData); } - if account_info.owner() != &ID { + if !account_info.owner_is(&ID) { return Err(ProgramError::InvalidAccountData); } Ok(Self::from_bytes_unchecked( diff --git a/sdk/pinocchio/src/account_info.rs b/sdk/pinocchio/src/account_info.rs index ccdb0e45..03ffe24e 100644 --- a/sdk/pinocchio/src/account_info.rs +++ b/sdk/pinocchio/src/account_info.rs @@ -1,17 +1,17 @@ //! Data structures to represent account information. +#[cfg(target_os = "solana")] +use crate::syscalls::sol_memset_; +use crate::{program_error::ProgramError, pubkey::Pubkey, ProgramResult, NON_DUP_MARKER}; use core::{ + cell::{Cell, UnsafeCell}, marker::PhantomData, mem::ManuallyDrop, - ptr::{write, NonNull}, + ops::Deref, + ptr::{self, NonNull}, slice::{from_raw_parts, from_raw_parts_mut}, }; -#[cfg(target_os = "solana")] -use crate::syscalls::sol_memset_; - -use crate::{program_error::ProgramError, pubkey::Pubkey, ProgramResult}; - /// Maximum number of bytes a program may add to an account during a /// single top-level instruction. pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10; @@ -35,13 +35,9 @@ pub enum BorrowState { MutablyBorrowed = 0b_1000_1000, } -/// Raw account data. -/// -/// This data is wrapped in an `AccountInfo` struct, which provides safe access -/// to the data. #[repr(C)] -#[derive(Clone, Copy, Default)] -pub(crate) struct Account { +#[derive(Default, Debug)] +pub(crate) struct AccountStatic { /// Borrow state for lamports and account data. /// /// This reuses the memory reserved for the duplicate flag in the @@ -59,7 +55,7 @@ pub(crate) struct Account { /// - `7 6 5 4 3 2 1 0` /// - `. x x x . . . .`: number of immutable borrows that can still be /// allocated, for the lamports field. Ranges from 7 (`111`) to - /// 0 (`000`). + /// 0 (`000`). /// /// * data mutable borrow flag /// - `7 6 5 4 3 2 1 0` @@ -74,7 +70,7 @@ pub(crate) struct Account { /// Note that this values are shared across `AccountInfo`s over the /// same account, e.g., in case of duplicated accounts, they share /// the same borrow state. - pub(crate) borrow_state: u8, + pub(crate) borrow_state: Cell, /// Indicates whether the transaction was signed by this account. is_signer: u8, @@ -85,25 +81,74 @@ pub(crate) struct Account { /// Indicates whether this account represents a program. executable: u8, - /// Difference between the original data length and the current - /// data length. - /// - /// This is used to track the original data length of the account - /// when the account is resized. The runtime guarantees that this - /// value is zero at the start of the instruction. - resize_delta: i32, + /// The runtime guarantees that this value is zero at the start of the instruction. + _padding: [u8; 4], /// Public key of the account. key: Pubkey, /// Program that owns this account. Modifiable by programs. - owner: Pubkey, + owner: UnsafeCell, /// The lamports in the account. Modifiable by programs. - lamports: u64, + lamports: Cell, /// Length of the data. Modifiable by programs. - pub(crate) data_len: u64, + pub(crate) data_len: Cell, +} + +union PtrRepr { + const_ptr: *const Account, + components: (*const (), usize), +} + +/// Raw account data. +/// +/// This data is wrapped in an `AccountInfo` struct, which provides safe access +/// to the data. +#[repr(C)] +#[derive(Debug)] +pub(crate) struct Account { + account: AccountStatic, + data: UnsafeCell<[u8]>, +} +impl Deref for Account { + type Target = AccountStatic; + + fn deref(&self) -> &Self::Target { + &self.account + } +} +impl Account { + pub(crate) unsafe fn from_bytes_ptr<'a>(bytes: *mut u8) -> AccountFromPtr<'a> { + if *bytes != NON_DUP_MARKER { + AccountFromPtr::Cloned { index: *bytes } + } else { + let (account, offset) = Self::from_bytes_ptr_not_cloned(bytes); + AccountFromPtr::Account { account, offset } + } + } + + pub(crate) unsafe fn from_bytes_ptr_not_cloned<'a>(bytes: *mut u8) -> (&'a Self, usize) { + let account_static = &*bytes.cast::(); + let data_len = account_static.data_len.get() as usize; + let ptr = &*(PtrRepr { + components: ( + bytes.cast_const().cast(), + data_len + MAX_PERMITTED_DATA_INCREASE, + ), + } + .const_ptr); + ( + ptr, + size_of::() + MAX_PERMITTED_DATA_INCREASE + data_len, + ) + } +} + +pub(crate) enum AccountFromPtr<'a> { + Cloned { index: u8 }, + Account { account: &'a Account, offset: usize }, } /// Wrapper struct for an `Account`. @@ -112,37 +157,64 @@ pub(crate) struct Account { /// used to track borrows of the account data and lamports, given that an /// account can be "shared" across multiple `AccountInfo` instances. #[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, Debug)] pub struct AccountInfo { /// Raw (pointer to) account data. /// /// Note that this is a pointer can be shared across multiple `AccountInfo`. - pub(crate) raw: *mut Account, + pub(crate) raw: &'static Account, +} +impl PartialEq for AccountInfo { + fn eq(&self, other: &Self) -> bool { + ptr::eq(self.raw as *const _, other.raw as *const _) + } } +impl Eq for AccountInfo {} impl AccountInfo { /// Public key of the account. #[inline(always)] pub fn key(&self) -> &Pubkey { - unsafe { &(*self.raw).key } + &self.raw.key } /// Program that owns this account. #[inline(always)] - pub fn owner(&self) -> &Pubkey { - unsafe { &(*self.raw).owner } + pub fn owner(&self) -> Pubkey { + unsafe { *self.owner_ref() } + } + + /// Returns `true` if this account's owner is `other` + #[inline(always)] + pub fn owner_is(&self, other: &Pubkey) -> bool { + self.owner_with_fn(|x| x == other) + } + + /// Operate on a ref to the program that owns this account. + #[inline(always)] + pub fn owner_with_fn(&self, f: impl FnOnce(&Pubkey) -> T) -> T { + f(unsafe { self.owner_ref() }) + } + + /// Program that owns this account. + /// + /// # Safety + /// This reference should not be held when `assign` is called. + #[inline(always)] + pub unsafe fn owner_ref(&self) -> &Pubkey { + unsafe { &*self.raw.owner.get() } } /// Indicates whether the transaction was signed by this account. #[inline(always)] pub fn is_signer(&self) -> bool { - unsafe { (*self.raw).is_signer != 0 } + self.raw.is_signer != 0 } /// Indicates whether the account is writable. #[inline(always)] pub fn is_writable(&self) -> bool { - unsafe { (*self.raw).is_writable != 0 } + self.raw.is_writable != 0 } /// Indicates whether this account represents a program. @@ -150,29 +222,49 @@ impl AccountInfo { /// Program accounts are always read-only. #[inline(always)] pub fn executable(&self) -> bool { - unsafe { (*self.raw).executable != 0 } + self.raw.executable != 0 } /// Returns the size of the data in the account. #[inline(always)] pub fn data_len(&self) -> usize { - unsafe { (*self.raw).data_len as usize } + self.raw.data_len.get() as usize } /// Returns the delta between the original data length and the current /// data length. /// - /// This value will be different than zero if the account has been resized + /// This value will be different from zero if the account has been resized /// during the current instruction. #[inline(always)] pub fn resize_delta(&self) -> i32 { - unsafe { (*self.raw).resize_delta } + let current_size = self.data_len() as i32; + let data_max_size = unsafe { + PtrRepr { + const_ptr: self.raw, + } + .components + .1 + } as i32; + current_size - (data_max_size - MAX_PERMITTED_DATA_INCREASE as i32) } /// Returns the lamports in the account. #[inline(always)] pub fn lamports(&self) -> u64 { - unsafe { (*self.raw).lamports } + self.raw.lamports.get() + } + + /// Sets the lamports and returns the old value. + #[inline(always)] + pub fn set_lamports(&self, lamports: u64) -> u64 { + self.raw.lamports.replace(lamports) + } + + /// Gets the cell that stores the account's lamports. + #[inline(always)] + pub fn borrow_lamports(&self) -> &Cell { + &self.raw.lamports } /// Indicates whether the account data is empty. @@ -186,7 +278,7 @@ impl AccountInfo { /// Checks if the account is owned by the given program. #[inline(always)] pub fn is_owned_by(&self, program: &Pubkey) -> bool { - self.owner() == program + unsafe { self.owner_ref() == program } } /// Changes the owner of the account. @@ -196,8 +288,8 @@ impl AccountInfo { /// It is undefined behavior to use this method while there is an active reference /// to the `owner` returned by [`Self::owner`]. #[inline(always)] - pub unsafe fn assign(&self, new_owner: &Pubkey) { - write(&mut (*self.raw).owner, *new_owner); + pub fn assign(&self, new_owner: &Pubkey) { + unsafe { *self.raw.owner.get() = *new_owner } } /// Return true if the account borrow state is set to the given state. @@ -205,36 +297,13 @@ impl AccountInfo { /// 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 }; + let borrow_state = self.raw.borrow_state.get(); let mask = state as u8; // If borrow state has any of the state bits of the mask not set, // then the account is borrowed for that state. (borrow_state & mask) != mask } - /// Returns a read-only reference to the lamports in the account. - /// - /// # Safety - /// - /// This method is unsafe because it does not return a `Ref`, thus leaving the borrow - /// flag untouched. Useful when an instruction has verified non-duplicate accounts. - #[inline(always)] - pub unsafe fn borrow_lamports_unchecked(&self) -> &u64 { - &(*self.raw).lamports - } - - /// Returns a mutable reference to the lamports in the account. - /// - /// # Safety - /// - /// This method is unsafe because it does not return a `Ref`, thus leaving the borrow - /// flag untouched. Useful when an instruction has verified non-duplicate accounts. - #[allow(clippy::mut_from_ref)] - #[inline(always)] - pub unsafe fn borrow_mut_lamports_unchecked(&self) -> &mut u64 { - &mut (*self.raw).lamports - } - /// Returns a read-only reference to the data in the account. /// /// # Safety @@ -243,7 +312,7 @@ impl AccountInfo { /// flag untouched. Useful when an instruction has verified non-duplicate accounts. #[inline(always)] pub unsafe fn borrow_data_unchecked(&self) -> &[u8] { - core::slice::from_raw_parts(self.data_ptr(), self.data_len()) + from_raw_parts(self.raw.data.get().cast(), self.data_len()) } /// Returns a mutable reference to the data in the account. @@ -255,127 +324,25 @@ impl AccountInfo { #[allow(clippy::mut_from_ref)] #[inline(always)] pub unsafe fn borrow_mut_data_unchecked(&self) -> &mut [u8] { - core::slice::from_raw_parts_mut(self.data_ptr(), self.data_len()) - } - - /// Tries to get a read-only reference to the lamport field, failing if the - /// field is already mutable borrowed or if 7 borrows already exist. - pub fn try_borrow_lamports(&self) -> Result, ProgramError> { - // check if the account lamports are already borrowed - self.can_borrow_lamports()?; - - let borrow_state = self.raw as *mut u8; - // Use one immutable borrow for lamports by subtracting `1` from the - // lamports borrow counter bits; we are guaranteed that there is at - // least one immutable borrow available. - // - // SAFETY: The `borrow_state` is a mutable pointer to the borrow state - // of the account, which is guaranteed to be valid. - unsafe { *borrow_state -= 1 << LAMPORTS_BORROW_SHIFT }; - - // return the reference to lamports - Ok(Ref { - value: unsafe { NonNull::from(&(*self.raw).lamports) }, - state: unsafe { NonNull::new_unchecked(borrow_state) }, - borrow_shift: LAMPORTS_BORROW_SHIFT, - marker: PhantomData, - }) - } - - /// Tries to get a read only reference to the lamport field, failing if the field - /// is already borrowed in any form. - pub fn try_borrow_mut_lamports(&self) -> Result, ProgramError> { - // check if the account lamports are already borrowed - self.can_borrow_mut_lamports()?; - - let borrow_state = self.raw as *mut u8; - // Set the mutable lamports borrow bit to `0`; we are guaranteed - // that lamports are not already borrowed in any form. - // - // SAFETY: The `borrow_state` is a mutable pointer to the borrow state - // of the account, which is guaranteed to be valid. - unsafe { *borrow_state &= 0b_0111_1111 }; - - // return the mutable reference to lamports - Ok(RefMut { - value: unsafe { NonNull::from(&mut (*self.raw).lamports) }, - state: unsafe { NonNull::new_unchecked(borrow_state) }, - borrow_bitmask: LAMPORTS_MUTABLE_BORROW_BITMASK, - marker: PhantomData, - }) - } - - /// 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 whether the mutable lamports borrow bit is already in - // use (value `0`) or not. If it is `0`, then the borrow will fail. - if borrow_state & LAMPORTS_MUTABLE_BORROW_BITMASK == 0 { - return Err(ProgramError::AccountBorrowFailed); - } - - // Check whether we have reached the maximum immutable lamports borrow count - // or not, i.e., it fails when all immutable lamports borrow bits are `0`. - if borrow_state & 0b_0111_0000 == 0 { - return Err(ProgramError::AccountBorrowFailed); - } - - Ok(()) - } - - /// 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 whether any (mutable or immutable) lamports borrow bits are - // in use (value `0`) or not. - if borrow_state & 0b_1111_0000 != 0b_1111_0000 { - return Err(ProgramError::AccountBorrowFailed); - } - - Ok(()) + from_raw_parts_mut(self.raw.data.get().cast(), self.data_len()) } /// Tries to get a read-only reference to the data field, failing if the field /// is already mutable borrowed or if `7` borrows already exist. - pub fn try_borrow_data(&self) -> Result, ProgramError> { + pub fn try_borrow_data(&self) -> Result, ProgramError> { // check if the account data is already borrowed self.can_borrow_data()?; - let borrow_state = self.raw as *mut u8; + let borrow_state = self.raw.borrow_state.get(); // Use one immutable borrow for data by subtracting `1` from the data // borrow counter bits; we are guaranteed that there is at least one // immutable borrow available. - // - // SAFETY: The `borrow_state` is a mutable pointer to the borrow state - // of the account, which is guaranteed to be valid. - unsafe { *borrow_state -= 1 }; + self.raw.borrow_state.set(borrow_state - 1); // return the reference to data Ok(Ref { - value: unsafe { NonNull::from(from_raw_parts(self.data_ptr(), self.data_len())) }, - state: unsafe { NonNull::new_unchecked(borrow_state) }, + value: self.data_ptr(), + state: &self.raw.borrow_state, borrow_shift: DATA_BORROW_SHIFT, marker: PhantomData, }) @@ -383,22 +350,21 @@ impl AccountInfo { /// Tries to get a mutable reference to the data field, failing if the field /// is already borrowed in any form. - pub fn try_borrow_mut_data(&self) -> Result, ProgramError> { + pub fn try_borrow_mut_data(&self) -> Result, ProgramError> { // check if the account data is already borrowed self.can_borrow_mut_data()?; - let borrow_state = self.raw as *mut u8; + let borrow_state = self.raw.borrow_state.get(); // Set the mutable data borrow bit to `0`; we are guaranteed that account // data is not already borrowed in any form. - // - // SAFETY: The `borrow_state` is a mutable pointer to the borrow state - // of the account, which is guaranteed to be valid. - unsafe { *borrow_state &= 0b_1111_0111 }; + self.raw + .borrow_state + .set(borrow_state & !DATA_MUTABLE_BORROW_BITMASK); // return the mutable reference to data Ok(RefMut { - value: unsafe { NonNull::from(from_raw_parts_mut(self.data_ptr(), self.data_len())) }, - state: unsafe { NonNull::new_unchecked(borrow_state) }, + value: self.data_ptr(), + state: &self.raw.borrow_state, borrow_bitmask: DATA_MUTABLE_BORROW_BITMASK, marker: PhantomData, }) @@ -416,7 +382,7 @@ impl AccountInfo { /// 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 }; + let borrow_state = self.raw.borrow_state.get(); // Check whether the mutable data borrow bit is already in // use (value `0`) or not. If it is `0`, then the borrow will fail. @@ -426,7 +392,7 @@ impl AccountInfo { // Check whether we have reached the maximum immutable data borrow count // or not, i.e., it fails when all immutable data borrow bits are `0`. - if borrow_state & 0b_0000_0111 == 0 { + if borrow_state & IMMUTABLE_LICENCES_MASK == 0 { return Err(ProgramError::AccountBorrowFailed); } @@ -445,11 +411,13 @@ impl AccountInfo { /// 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 }; + let borrow_state = self.raw.borrow_state.get(); // Check whether any (mutable or immutable) data borrow bits are // in use (value `0`) or not. - if borrow_state & 0b_0000_1111 != 0b_0000_1111 { + if borrow_state & (IMMUTABLE_LICENCES_MASK | DATA_MUTABLE_BORROW_BITMASK) + != (IMMUTABLE_LICENCES_MASK | DATA_MUTABLE_BORROW_BITMASK) + { return Err(ProgramError::AccountBorrowFailed); } @@ -492,7 +460,7 @@ impl AccountInfo { /// in the `process_instruction` entrypoint of a program. #[inline] pub fn resize(&self, new_len: usize) -> Result<(), ProgramError> { - // Check wheather the account data is already borrowed. + // Check whether the account data is already borrowed. self.can_borrow_mut_data()?; // SAFETY: @@ -512,43 +480,39 @@ impl AccountInfo { #[inline(always)] pub unsafe fn resize_unchecked(&self, new_len: usize) -> Result<(), ProgramError> { // Account length is always `< i32::MAX`... - let current_len = self.data_len() as i32; + let current_len = self.data_len(); // ...so the new length must fit in an `i32`. - let new_len = i32::try_from(new_len).map_err(|_| ProgramError::InvalidRealloc)?; // Return early if length hasn't changed. if new_len == current_len { return Ok(()); } - let difference = new_len - current_len; - let accumulated_resize_delta = self.resize_delta() + difference; - // Return an error when the length increase from the original serialized data // length is too large and would result in an out of bounds allocation - if accumulated_resize_delta > MAX_PERMITTED_DATA_INCREASE as i32 { + if new_len > self.raw.data.get().len() { return Err(ProgramError::InvalidRealloc); } - unsafe { - (*self.raw).data_len = new_len as u64; - (*self.raw).resize_delta = accumulated_resize_delta; - } + let difference = new_len.saturating_sub(current_len); + + self.raw.data_len.set(new_len as u64); if difference > 0 { unsafe { #[cfg(target_os = "solana")] sol_memset_( - self.data_ptr().add(current_len as usize), + self.raw.data.get().cast::().add(current_len), 0, difference as u64, ); #[cfg(not(target_os = "solana"))] - core::ptr::write_bytes( - self.data_ptr().add(current_len as usize), - 0, - difference as usize, - ); + self.raw + .data + .get() + .cast::() + .add(current_len) + .write_bytes(0, difference) } } @@ -576,10 +540,6 @@ impl AccountInfo { // SAFETY: The are no active borrows on the account data or lamports. unsafe { - // Update the resize delta since closing an account will set its data length - // to zero (account length is always `< i32::MAX`). - (*self.raw).resize_delta = self.resize_delta() - self.data_len() as i32; - self.close_unchecked(); } @@ -621,7 +581,13 @@ impl AccountInfo { // // So we can zero out them directly. #[cfg(target_os = "solana")] - sol_memset_(self.data_ptr().sub(48), 0, 48); + sol_memset_(self.raw.data.get().cast::().sub(48), 0, 48); + #[cfg(not(target_os = "solana"))] + { + *self.raw.owner.get() = Pubkey::default(); + self.raw.lamports.set(0); + self.raw.data_len.set(0); + } } /// Returns the memory address of the account data. @@ -631,17 +597,14 @@ impl AccountInfo { /// the caller to uphold Rust's aliasing rules. It is undefined behavior to de-reference /// the pointer or write through it while any safe reference (e.g., from any of `borrow_data` /// or `borrow_mut_data` methods) to the same data is still alive. - pub fn data_ptr(&self) -> *mut u8 { - unsafe { (self.raw as *mut u8).add(core::mem::size_of::()) } + pub fn data_ptr(&self) -> NonNull<[u8]> { + NonNull::slice_from_raw_parts( + unsafe { NonNull::new_unchecked(self.raw.data.get().cast()) }, + self.data_len(), + ) } } -/// Number of bits of the [`Account::borrow_state`] flag to shift to get to -/// the borrow state bits for lamports. -/// - `7 6 5 4 3 2 1 0` -/// - `x x x x . . . .` -const LAMPORTS_BORROW_SHIFT: u8 = 4; - /// Number of bits of the [`Account::borrow_state`] flag to shift to get to /// the borrow state bits for account data. /// - `7 6 5 4 3 2 1 0` @@ -652,7 +615,7 @@ const DATA_BORROW_SHIFT: u8 = 0; #[derive(Debug)] pub struct Ref<'a, T: ?Sized> { value: NonNull, - state: NonNull, + state: &'a Cell, /// Indicates the type of borrow (lamports or data) by representing the /// shift amount. borrow_shift: u8, @@ -730,21 +693,20 @@ impl core::ops::Deref for Ref<'_, T> { impl Drop for Ref<'_, T> { // decrement the immutable borrow count fn drop(&mut self) { - unsafe { *self.state.as_mut() += 1 << self.borrow_shift }; + self.state.set(self.state.get() + (1 << self.borrow_shift)); } } -/// Mask representing the mutable borrow flag for lamports. -const LAMPORTS_MUTABLE_BORROW_BITMASK: u8 = 0b_1000_0000; - /// Mask representing the mutable borrow flag for data. const DATA_MUTABLE_BORROW_BITMASK: u8 = 0b_0000_1000; +const IMMUTABLE_LICENCES_MASK: u8 = 0b_0000_0111; + /// Mutable reference to account data or lamports with checked borrow rules. #[derive(Debug)] pub struct RefMut<'a, T: ?Sized> { value: NonNull, - state: NonNull, + state: &'a Cell, /// Indicates borrowed field (lamports or data) by storing the bitmask /// representing the mutable borrow. borrow_bitmask: u8, @@ -826,111 +788,76 @@ impl core::ops::DerefMut for RefMut<'_, T> { impl Drop for RefMut<'_, T> { fn drop(&mut self) { // unset the mutable borrow flag - unsafe { *self.state.as_mut() |= self.borrow_bitmask }; + self.state.set(self.state.get() | self.borrow_bitmask); } } #[cfg(test)] mod tests { - use core::mem::{size_of, MaybeUninit}; - use crate::NON_DUP_MARKER as NOT_BORROWED; + use core::mem::{size_of, MaybeUninit}; + use std::vec::Vec; use super::*; #[test] fn test_data_ref() { let data: [u8; 4] = [0, 1, 2, 3]; - let mut state = NOT_BORROWED - (1 << DATA_BORROW_SHIFT); + let state = Cell::new(NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); let ref_data = Ref { value: NonNull::from(&data), borrow_shift: DATA_BORROW_SHIFT, // borrow state must be a mutable reference - state: NonNull::from(&mut state), + state: &state, marker: PhantomData, }; let new_ref = Ref::map(ref_data, |data| &data[1]); - assert_eq!(state, NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); + assert_eq!(state.get(), NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); assert_eq!(*new_ref, 1); let Ok(new_ref) = Ref::filter_map(new_ref, |_| Some(&3)) else { unreachable!() }; - assert_eq!(state, NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); + assert_eq!(state.get(), NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); assert_eq!(*new_ref, 3); let Ok(new_ref) = Ref::try_map::<_, u8>(new_ref, |_| Ok(&4)) else { unreachable!() }; - assert_eq!(state, NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); + assert_eq!(state.get(), NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); assert_eq!(*new_ref, 4); let (new_ref, err) = Ref::try_map::(new_ref, |_| Err(5)).unwrap_err(); - assert_eq!(state, NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); + assert_eq!(state.get(), NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); assert_eq!(err, 5); // Unchanged assert_eq!(*new_ref, 4); let new_ref = Ref::filter_map(new_ref, |_| Option::<&u8>::None); - assert_eq!(state, NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); - assert!(new_ref.is_err()); - - drop(new_ref); - - assert_eq!(state, NOT_BORROWED); - } - - #[test] - fn test_lamports_ref() { - let lamports: u64 = 10000; - let mut state = NOT_BORROWED - (1 << LAMPORTS_BORROW_SHIFT); - - let ref_lamports = Ref { - value: NonNull::from(&lamports), - borrow_shift: LAMPORTS_BORROW_SHIFT, - // borrow state must be a mutable reference - state: NonNull::from(&mut state), - marker: PhantomData, - }; - - let new_ref = Ref::map(ref_lamports, |_| &1000); - - assert_eq!(state, NOT_BORROWED - (1 << LAMPORTS_BORROW_SHIFT)); - assert_eq!(*new_ref, 1000); - - let Ok(new_ref) = Ref::filter_map(new_ref, |_| Some(&2000)) else { - unreachable!() - }; - - assert_eq!(state, NOT_BORROWED - (1 << LAMPORTS_BORROW_SHIFT)); - assert_eq!(*new_ref, 2000); - - let new_ref = Ref::filter_map(new_ref, |_| Option::<&i32>::None); - - assert_eq!(state, NOT_BORROWED - (1 << LAMPORTS_BORROW_SHIFT)); + assert_eq!(state.get(), NOT_BORROWED - (1 << DATA_BORROW_SHIFT)); assert!(new_ref.is_err()); drop(new_ref); - assert_eq!(state, NOT_BORROWED); + assert_eq!(state.get(), NOT_BORROWED); } #[test] fn test_data_ref_mut() { let mut data: [u8; 4] = [0, 1, 2, 3]; - let mut state = 0b_1111_0111; + let state = Cell::new(0b_1111_0111); let ref_data = RefMut { value: NonNull::from(&mut data), borrow_bitmask: DATA_MUTABLE_BORROW_BITMASK, // borrow state must be a mutable reference - state: NonNull::from(&mut state), + state: &state, marker: PhantomData, }; @@ -940,68 +867,43 @@ mod tests { *new_ref = 4; - assert_eq!(state, 0b_1111_0111); + assert_eq!(state.get(), 0b_1111_0111); assert_eq!(*new_ref, 4); drop(new_ref); assert_eq!(data, [4, 1, 2, 3]); - assert_eq!(state, NOT_BORROWED); - } - - #[test] - fn test_lamports_ref_mut() { - let mut lamports: u64 = 10000; - let mut state = 0b_0111_1111; - - let ref_lamports = RefMut { - value: NonNull::from(&mut lamports), - borrow_bitmask: LAMPORTS_MUTABLE_BORROW_BITMASK, - // borrow state must be a mutable reference - state: NonNull::from(&mut state), - marker: PhantomData, - }; - - let new_ref = RefMut::map(ref_lamports, |lamports| { - *lamports = 200; - lamports - }); - - assert_eq!(state, 0b_0111_1111); - assert_eq!(*new_ref, 200); - - drop(new_ref); - - assert_eq!(lamports, 200); - assert_eq!(state, NOT_BORROWED); + assert_eq!(state.get(), NOT_BORROWED); } #[test] fn test_borrow_data() { // 8-bytes aligned account data. - let mut data = [0u64; size_of::() / size_of::() + 1]; // extra byte at end for account data - // Set the borrow state. + let mut data = + [0u64; (size_of::() + MAX_PERMITTED_DATA_INCREASE) / size_of::()]; + // Set the borrow state. data[0] = NOT_BORROWED as u64; - let account_info = AccountInfo { - raw: data.as_mut_ptr() as *mut Account, - }; + let raw = unsafe { Account::from_bytes_ptr_not_cloned(data.as_mut_ptr().cast()).0 }; + raw.data_len.set(1); + let account_info = AccountInfo { raw }; // Check that we can borrow data and lamports. assert!(account_info.can_borrow_data().is_ok()); assert!(account_info.can_borrow_mut_data().is_ok()); - assert!(account_info.can_borrow_lamports().is_ok()); - assert!(account_info.can_borrow_mut_lamports().is_ok()); // It should be sound to mutate the data through the data pointer while no other borrows exist let data_ptr = account_info.data_ptr(); + // This is opposite in nightly clippy, it's an error to not have the ref + #[allow(clippy::needless_borrow)] unsafe { - let data = core::slice::from_raw_parts_mut(data_ptr, 1); // Data is 1 byte long! - data[0] = 1; + assert_eq!((&*data_ptr.as_ptr()).len(), 1); + (*data_ptr.as_ptr())[0] = 1; } // Borrow immutable data (7 immutable borrows available). - const ACCOUNT_REF: MaybeUninit> = MaybeUninit::>::uninit(); - let mut refs = [ACCOUNT_REF; 7]; + let mut refs = (0..7) + .map(|_| MaybeUninit::>::uninit()) + .collect::>(); refs.iter_mut().for_each(|r| { let Ok(data_ref) = account_info.try_borrow_data() else { @@ -1015,9 +917,6 @@ mod tests { assert!(account_info.try_borrow_data().is_err()); assert!(account_info.can_borrow_mut_data().is_err()); assert!(account_info.try_borrow_mut_data().is_err()); - // Lamports should still be borrowable. - assert!(account_info.can_borrow_lamports().is_ok()); - assert!(account_info.can_borrow_mut_lamports().is_ok()); // Drop the immutable borrows. refs.iter_mut().for_each(|r| { @@ -1046,80 +945,16 @@ mod tests { assert!(account_info.can_borrow_data().is_ok()); assert!(account_info.can_borrow_mut_data().is_ok()); - let borrow_state = unsafe { (*account_info.raw).borrow_state }; - assert!(borrow_state == NOT_BORROWED); - } - - #[test] - fn test_borrow_lamports() { - // 8-bytes aligned account data. - let mut data = [0u64; size_of::() / size_of::()]; - // Set the borrow state. - data[0] = NOT_BORROWED as u64; - let account_info = AccountInfo { - raw: data.as_mut_ptr() as *mut Account, - }; - - // Check that we can borrow lamports and data. - assert!(account_info.can_borrow_lamports().is_ok()); - assert!(account_info.can_borrow_mut_lamports().is_ok()); - assert!(account_info.can_borrow_data().is_ok()); - assert!(account_info.can_borrow_mut_data().is_ok()); - - // Borrow immutable lamports (7 immutable borrows available). - const LAMPORTS_REF: MaybeUninit> = MaybeUninit::>::uninit(); - let mut refs = [LAMPORTS_REF; 7]; - - refs.iter_mut().for_each(|r| { - let Ok(lamports_ref) = account_info.try_borrow_lamports() else { - panic!("Failed to borrow lamports"); - }; - r.write(lamports_ref); - }); - - // Check that we cannot borrow the lamports anymore. - assert!(account_info.can_borrow_lamports().is_err()); - assert!(account_info.try_borrow_lamports().is_err()); - assert!(account_info.can_borrow_mut_lamports().is_err()); - assert!(account_info.try_borrow_mut_lamports().is_err()); - // Data should still be borrowable. - assert!(account_info.can_borrow_data().is_ok()); - assert!(account_info.can_borrow_mut_data().is_ok()); - - // Drop the immutable borrows. - refs.iter_mut().for_each(|r| { - let r = unsafe { r.assume_init_read() }; - drop(r); - }); - - // We should be able to borrow the lamports again. - assert!(account_info.can_borrow_lamports().is_ok()); - assert!(account_info.can_borrow_mut_lamports().is_ok()); - - // Borrow mutable lamports. - let ref_mut = account_info.try_borrow_mut_lamports().unwrap(); - - // Check that we cannot borrow the lamports anymore. - assert!(account_info.can_borrow_lamports().is_err()); - assert!(account_info.try_borrow_lamports().is_err()); - assert!(account_info.can_borrow_mut_lamports().is_err()); - assert!(account_info.try_borrow_mut_lamports().is_err()); - - drop(ref_mut); - - // We should be able to borrow the data again. - assert!(account_info.can_borrow_lamports().is_ok()); - assert!(account_info.can_borrow_mut_lamports().is_ok()); - - let borrow_state = unsafe { (*account_info.raw).borrow_state }; - assert!(borrow_state == NOT_BORROWED); + let borrow_state = account_info.raw.borrow_state.get(); + assert_eq!(borrow_state, NOT_BORROWED); } #[test] #[allow(deprecated)] fn test_realloc() { // 8-bytes aligned account data. - let mut data = [0u64; 100 * size_of::()]; + let mut data = + [0u64; 100 * size_of::() + MAX_PERMITTED_DATA_INCREASE / size_of::()]; // Set the borrow state. data[0] = NOT_BORROWED as u64; @@ -1128,10 +963,12 @@ mod tests { data[10] = 100; let account = AccountInfo { - raw: data.as_mut_ptr() as *const _ as *mut Account, + raw: unsafe { Account::from_bytes_ptr_not_cloned(data.as_mut_ptr().cast()).0 }, }; - assert_eq!(account.data_len(), 100); + let data_len = account.data_len(); + + assert_eq!(data_len, 100); assert_eq!(account.resize_delta(), 0); // We should be able to get the data pointer whenever as long as we don't use it while the data is borrowed @@ -1143,7 +980,7 @@ mod tests { let data_ptr_after = account.data_ptr(); // The data pointer should point to the same address regardless of the reallocation - assert_eq!(data_ptr_before, data_ptr_after); + assert_eq!(data_ptr_before.cast::(), data_ptr_after.cast::()); assert_eq!(account.data_len(), 200); assert_eq!(account.resize_delta(), 100); diff --git a/sdk/pinocchio/src/entrypoint/lazy.rs b/sdk/pinocchio/src/entrypoint/lazy.rs index 64965a90..a29af0af 100644 --- a/sdk/pinocchio/src/entrypoint/lazy.rs +++ b/sdk/pinocchio/src/entrypoint/lazy.rs @@ -1,12 +1,12 @@ //! Defines the lazy program entrypoint and the context to access the //! input buffer. +use crate::account_info::AccountFromPtr; use crate::{ account_info::{Account, AccountInfo}, - entrypoint::STATIC_ACCOUNT_DATA, program_error::ProgramError, pubkey::Pubkey, - BPF_ALIGN_OF_U128, NON_DUP_MARKER, + BPF_ALIGN_OF_U128, }; /// Declare the lazy program entrypoint. @@ -221,7 +221,7 @@ impl InstructionContext { pub unsafe fn instruction_data_unchecked(&self) -> &[u8] { let data_len = *(self.buffer as *const usize); // shadowing the input to avoid leaving it in an inconsistent position - let data = self.buffer.add(core::mem::size_of::()); + let data = self.buffer.add(size_of::()); core::slice::from_raw_parts(data, data_len) } @@ -247,7 +247,7 @@ impl InstructionContext { #[inline(always)] pub unsafe fn program_id_unchecked(&self) -> &Pubkey { let data_len = *(self.buffer as *const usize); - &*(self.buffer.add(core::mem::size_of::() + data_len) as *const Pubkey) + &*(self.buffer.add(size_of::() + data_len) as *const Pubkey) } /// Read an account from the input buffer. @@ -257,21 +257,20 @@ impl InstructionContext { #[allow(clippy::cast_ptr_alignment, clippy::missing_safety_doc)] #[inline(always)] unsafe fn read_account(&mut self) -> MaybeAccount { - let account: *mut Account = self.buffer as *mut Account; + let result = Account::from_bytes_ptr(self.buffer); // Adds an 8-bytes offset for: // - rent epoch in case of a non-duplicate account // - duplicate marker + 7 bytes of padding in case of a duplicate account - self.buffer = self.buffer.add(core::mem::size_of::()); + self.buffer = self.buffer.add(size_of::()); - if (*account).borrow_state == NON_DUP_MARKER { - self.buffer = self.buffer.add(STATIC_ACCOUNT_DATA); - self.buffer = self.buffer.add((*account).data_len as usize); - self.buffer = self.buffer.add(self.buffer.align_offset(BPF_ALIGN_OF_U128)); + match result { + AccountFromPtr::Cloned { index } => MaybeAccount::Duplicated(index), + AccountFromPtr::Account { account, offset } => { + self.buffer = self.buffer.add(offset); + self.buffer = self.buffer.add(self.buffer.align_offset(BPF_ALIGN_OF_U128)); - MaybeAccount::Account(AccountInfo { raw: account }) - } else { - // The caller will handle the mapping to the original account. - MaybeAccount::Duplicated((*account).borrow_state) + MaybeAccount::Account(AccountInfo { raw: account }) + } } } } diff --git a/sdk/pinocchio/src/entrypoint/mod.rs b/sdk/pinocchio/src/entrypoint/mod.rs index a2e7135b..72e03f18 100644 --- a/sdk/pinocchio/src/entrypoint/mod.rs +++ b/sdk/pinocchio/src/entrypoint/mod.rs @@ -16,10 +16,14 @@ use core::{ slice::from_raw_parts, }; +#[cfg(test)] +use crate::account_info::AccountStatic; +#[cfg(test)] +use crate::account_info::MAX_PERMITTED_DATA_INCREASE; use crate::{ - account_info::{Account, AccountInfo, MAX_PERMITTED_DATA_INCREASE}, + account_info::{Account, AccountFromPtr, AccountInfo}, pubkey::Pubkey, - BPF_ALIGN_OF_U128, MAX_TX_ACCOUNTS, NON_DUP_MARKER, + BPF_ALIGN_OF_U128, MAX_TX_ACCOUNTS, }; /// Start address of the memory region used for program heap. @@ -42,7 +46,8 @@ pub const SUCCESS: u64 = super::SUCCESS; /// The "static" size of an account in the input buffer. /// /// This is the size of the account header plus the maximum permitted data increase. -const STATIC_ACCOUNT_DATA: usize = size_of::() + MAX_PERMITTED_DATA_INCREASE; +#[cfg(test)] +const STATIC_ACCOUNT_DATA: usize = size_of::() + MAX_PERMITTED_DATA_INCREASE; /// Declare the program entrypoint and set up global handlers. /// @@ -222,20 +227,21 @@ macro_rules! process_n_accounts { $accounts = $accounts.add(1); // Read the next account. - let account: *mut Account = $input as *mut Account; + let result: AccountFromPtr<'static> = Account::from_bytes_ptr::<'static>($input); // Adds an 8-bytes offset for: // - rent epoch in case of a non-duplicated account // - duplicated marker + 7 bytes of padding in case of a duplicated account $input = $input.add(size_of::()); - if (*account).borrow_state != NON_DUP_MARKER { - clone_account_info($accounts, $accounts_slice, (*account).borrow_state); - } else { - $accounts.write(AccountInfo { raw: account }); - - $input = $input.add(STATIC_ACCOUNT_DATA); - $input = $input.add((*account).data_len as usize); - $input = align_pointer!($input); + match result { + AccountFromPtr::Cloned { index } => { + clone_account_info($accounts, $accounts_slice, index); + }, + AccountFromPtr::Account { account, offset } => { + $accounts.write(AccountInfo { raw: account }); + $input = $input.add(offset); + $input = align_pointer!($input); + } } }; } @@ -309,6 +315,7 @@ pub unsafe fn deserialize( MAX_ACCOUNTS <= MAX_TX_ACCOUNTS, "MAX_ACCOUNTS must be less than or equal to MAX_TX_ACCOUNTS" ); + assert!(MAX_ACCOUNTS > 0, "MAX_ACCOUNTS must be greater than 0"); } // Number of accounts to process. @@ -317,19 +324,19 @@ pub unsafe fn deserialize( input = input.add(size_of::()); if processed > 0 { - let mut accounts = accounts.as_mut_ptr() as *mut AccountInfo; - // Represents the beginning of the accounts slice. - let accounts_slice = accounts; - // The first account is always non-duplicated, so process // it directly as such. - let account: *mut Account = input as *mut Account; - accounts.write(AccountInfo { raw: account }); + let (account, account_offset) = Account::from_bytes_ptr_not_cloned::<'static>(input); + *accounts.get_unchecked_mut(0) = MaybeUninit::new(AccountInfo { raw: account }); - input = input.add(STATIC_ACCOUNT_DATA + size_of::()); - input = input.add((*account).data_len as usize); + input = input.add(size_of::()); + input = input.add(account_offset); input = align_pointer!(input); + let mut accounts_ptr = accounts.as_mut_ptr() as *mut AccountInfo; + // Represents the beginning of the accounts slice. + let accounts_slice = accounts_ptr; + if processed > 1 { // The number of accounts to process (`to_process_plus_one`) is limited to // `MAX_ACCOUNTS`, which is the capacity of the accounts array. When there are more @@ -355,23 +362,23 @@ pub unsafe fn deserialize( // specified number of accounts. while to_process_plus_one > 5 { // Process 5 accounts at a time. - process_accounts!(5 => (input, accounts, accounts_slice)); + process_accounts!(5 => (input, accounts_ptr, accounts_slice)); to_process_plus_one -= 5; } // There might be remaining accounts to process. match to_process_plus_one { 5 => { - process_accounts!(4 => (input, accounts, accounts_slice)); + process_accounts!(4 => (input, accounts_ptr, accounts_slice)); } 4 => { - process_accounts!(3 => (input, accounts, accounts_slice)); + process_accounts!(3 => (input, accounts_ptr, accounts_slice)); } 3 => { - process_accounts!(2 => (input, accounts, accounts_slice)); + process_accounts!(2 => (input, accounts_ptr, accounts_slice)); } 2 => { - process_accounts!(1 => (input, accounts, accounts_slice)); + process_accounts!(1 => (input, accounts_ptr, accounts_slice)); } 1 => (), _ => { @@ -394,15 +401,14 @@ pub unsafe fn deserialize( to_skip -= 1; // Read the next account. - let account: *mut Account = input as *mut Account; + let result = Account::from_bytes_ptr(input); // Adds an 8-bytes offset for: // - rent epoch in case of a non-duplicated account // - duplicated marker + 7 bytes of padding in case of a duplicated account input = input.add(size_of::()); - if (*account).borrow_state == NON_DUP_MARKER { - input = input.add(STATIC_ACCOUNT_DATA); - input = input.add((*account).data_len as usize); + if let AccountFromPtr::Account { offset, .. } = result { + input = input.add(offset); input = align_pointer!(input); } } @@ -684,20 +690,17 @@ unsafe impl GlobalAlloc for NoAllocator { mod tests { extern crate std; + use super::*; + use crate::NON_DUP_MARKER; use core::{alloc::Layout, ptr::copy_nonoverlapping}; use std::{ alloc::{alloc, dealloc}, vec, }; - use super::*; - /// The mock program ID used for testing. const MOCK_PROGRAM_ID: Pubkey = [5u8; 32]; - /// An uninitialized account info. - const UNINIT: MaybeUninit = MaybeUninit::::uninit(); - /// Struct representing a memory region with a specific alignment. struct AlignedMemory { ptr: *mut u8, @@ -876,7 +879,7 @@ mod tests { for account in accounts[unique..].iter() { let account_info = unsafe { account.assume_init_ref() }; - assert_eq!(account_info.raw, duplicated.raw); + assert_eq!(account_info, duplicated); assert_eq!(account_info.data_len(), duplicated.data_len()); let borrowed = account_info.try_borrow_mut_data().unwrap(); @@ -898,7 +901,7 @@ mod tests { // Input with 0 accounts. let mut input = unsafe { create_input(0, &ix_data) }; - let mut accounts = [UNINIT; 1]; + let mut accounts = [MaybeUninit::uninit(); 1]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; @@ -911,7 +914,7 @@ mod tests { // for 1. let mut input = unsafe { create_input(3, &ix_data) }; - let mut accounts = [UNINIT; 1]; + let mut accounts = [MaybeUninit::uninit(); 1]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; @@ -925,7 +928,7 @@ mod tests { // only space for 64. let mut input = unsafe { create_input(MAX_TX_ACCOUNTS, &ix_data) }; - let mut accounts = [UNINIT; 64]; + let mut accounts = [MaybeUninit::uninit(); 64]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; @@ -943,7 +946,7 @@ mod tests { // Input with 0 accounts. let mut input = unsafe { create_input_with_duplicates(0, &ix_data, 0) }; - let mut accounts = [UNINIT; 1]; + let mut accounts = [MaybeUninit::uninit(); 1]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; @@ -957,7 +960,7 @@ mod tests { // is unique. let mut input = unsafe { create_input_with_duplicates(3, &ix_data, 2) }; - let mut accounts = [UNINIT; 2]; + let mut accounts = [MaybeUninit::uninit(); 2]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; @@ -974,7 +977,7 @@ mod tests { let mut input = unsafe { create_input_with_duplicates(MAX_TX_ACCOUNTS, &ix_data, MAX_TX_ACCOUNTS - 32) }; - let mut accounts = [UNINIT; 64]; + let mut accounts = [MaybeUninit::uninit(); 64]; let (program_id, count, parsed_ix_data) = unsafe { deserialize(input.as_mut_ptr(), &mut accounts) }; diff --git a/sdk/pinocchio/src/instruction.rs b/sdk/pinocchio/src/instruction.rs index 7df365f8..fbb8c9a4 100644 --- a/sdk/pinocchio/src/instruction.rs +++ b/sdk/pinocchio/src/instruction.rs @@ -84,7 +84,7 @@ pub struct Account<'a> { /// /// If any of this requirements is not valid, this function leads to undefined behavior. #[inline(always)] -const unsafe fn field_at_offset(ptr: *const T, offset: usize) -> *const U { +const unsafe fn field_at_offset(ptr: *const T, offset: usize) -> *const U { // SAFETY: The caller ensures that the offset is valid for the type `T` and that // the resulting pointer is valid for type `U`. unsafe { (ptr as *const u8).add(offset) as *const U } diff --git a/sdk/pinocchio/src/lib.rs b/sdk/pinocchio/src/lib.rs index 6225cd39..ed67c5df 100644 --- a/sdk/pinocchio/src/lib.rs +++ b/sdk/pinocchio/src/lib.rs @@ -218,7 +218,7 @@ //! cargo build-sbf --features bpf-entrypoint //! ``` #![warn(missing_copy_implementations, missing_debug_implementations)] -#![no_std] +#![cfg_attr(not(test), no_std)] #[cfg(feature = "std")] extern crate std; diff --git a/sdk/pinocchio/src/log.rs b/sdk/pinocchio/src/log.rs index d978cbec..ceabcbef 100644 --- a/sdk/pinocchio/src/log.rs +++ b/sdk/pinocchio/src/log.rs @@ -150,7 +150,7 @@ pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) { msg!("- Account data length"); sol_log_64(0, 0, 0, 0, account.data_len() as u64); msg!("- Owner"); - pubkey::log(account.owner()); + account.owner_with_fn(pubkey::log); } msg!("Instruction data"); sol_log_slice(data); diff --git a/sdk/pinocchio/src/sysvars/clock.rs b/sdk/pinocchio/src/sysvars/clock.rs index f4d1933b..9136579d 100644 --- a/sdk/pinocchio/src/sysvars/clock.rs +++ b/sdk/pinocchio/src/sysvars/clock.rs @@ -85,7 +85,7 @@ impl Clock { /// /// This method performs a check on the account info key. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { if account_info.key() != &CLOCK_ID { return Err(ProgramError::InvalidArgument); } diff --git a/sdk/pinocchio/src/sysvars/instructions.rs b/sdk/pinocchio/src/sysvars/instructions.rs index a17ca923..610144b7 100644 --- a/sdk/pinocchio/src/sysvars/instructions.rs +++ b/sdk/pinocchio/src/sysvars/instructions.rs @@ -66,7 +66,7 @@ where pub unsafe fn deserialize_instruction_unchecked( &self, index: usize, - ) -> IntrospectedInstruction { + ) -> IntrospectedInstruction<'_> { let offset = *(self .data .as_ptr() @@ -83,7 +83,7 @@ where pub fn load_instruction_at( &self, index: usize, - ) -> Result { + ) -> Result, ProgramError> { if index >= self.num_instructions() as usize { return Err(ProgramError::InvalidInstructionData); } @@ -98,7 +98,7 @@ where pub fn get_instruction_relative( &self, index_relative_to_current: i64, - ) -> Result { + ) -> Result, ProgramError> { let current_index = self.load_current_index() as i64; let index = current_index.saturating_add(index_relative_to_current); @@ -241,7 +241,7 @@ impl IntrospectedAccountMeta { /// Convert the `IntrospectedAccountMeta` to an `AccountMeta`. #[inline(always)] - pub fn to_account_meta(&self) -> AccountMeta { + pub fn to_account_meta(&self) -> AccountMeta<'_> { AccountMeta::new(&self.key, self.is_writable(), self.is_signer()) } } diff --git a/sdk/pinocchio/src/sysvars/rent.rs b/sdk/pinocchio/src/sysvars/rent.rs index c50caa12..eb1c6819 100644 --- a/sdk/pinocchio/src/sysvars/rent.rs +++ b/sdk/pinocchio/src/sysvars/rent.rs @@ -72,7 +72,7 @@ impl Rent { /// /// This method performs a check on the account info key. #[inline] - pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { if account_info.key() != &RENT_ID { return Err(ProgramError::InvalidArgument); } diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test.rs index 80c8d280..058e6036 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test.rs @@ -10,6 +10,7 @@ use core::{ }; extern crate std; +use crate::account_info::MAX_PERMITTED_DATA_INCREASE; use std::io::Write; use std::vec::Vec; @@ -414,9 +415,9 @@ fn test_from_account_info_constructor() { let acct_ptr; unsafe { - let header_size = core::mem::size_of::(); - let total_size = header_size + data.len(); - let word_len = (total_size + 7) / 8; + let header_size = size_of::(); + let total_size = header_size + data.len() + MAX_PERMITTED_DATA_INCREASE; + let word_len = total_size.div_ceil(8); aligned_backing = std::vec![0u64; word_len]; let base_ptr = aligned_backing.as_mut_ptr() as *mut u8; @@ -438,7 +439,7 @@ fn test_from_account_info_constructor() { ptr::copy_nonoverlapping(data.as_ptr(), base_ptr.add(header_size), data.len()); - acct_ptr = base_ptr as *mut Account; + acct_ptr = Account::from_bytes_ptr_not_cloned(base_ptr.cast()).0; } let account_info = AccountInfo { raw: acct_ptr }; diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs index f8753e30..29a5fe62 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs @@ -4,7 +4,7 @@ use super::*; extern crate std; -use crate::account_info::{Account, AccountInfo}; +use crate::account_info::{Account, AccountInfo, AccountStatic, MAX_PERMITTED_DATA_INCREASE}; use crate::pubkey::Pubkey; use core::{mem, ptr}; use std::vec::Vec; @@ -122,8 +122,8 @@ pub unsafe fn make_account_info( borrow_state: u8, ) -> (AccountInfo, Vec) { let hdr_size = mem::size_of::(); - let total = hdr_size + data.len(); - let words = (total + 7) / 8; + let total = hdr_size + data.len() + MAX_PERMITTED_DATA_INCREASE; + let words = total.div_ceil(8); let mut backing: Vec = std::vec![0u64; words]; assert!( mem::align_of::() >= mem::align_of::(), @@ -154,7 +154,7 @@ pub unsafe fn make_account_info( ( AccountInfo { - raw: hdr_ptr as *mut Account, + raw: unsafe { Account::from_bytes_ptr_not_cloned(hdr_ptr.cast()).0 }, }, backing, ) @@ -165,17 +165,17 @@ pub unsafe fn make_account_info( fn test_account_layout_compatibility() { assert_eq!( mem::size_of::(), - mem::size_of::(), + mem::size_of::(), "Header size must match Account size" ); assert_eq!( mem::align_of::(), - mem::align_of::(), + mem::align_of::(), "Header alignment must match Account alignment" ); unsafe { - let test_header = AccountLayout { + let mut test_header = AccountLayout { borrow_state: 42, is_signer: 1, is_writable: 1, @@ -187,14 +187,16 @@ fn test_account_layout_compatibility() { data_len: 256, }; - let account_ptr = &test_header as *const AccountLayout as *const Account; + let account_ptr = &mut test_header as *mut AccountLayout as *mut AccountStatic; let account_ref = &*account_ptr; assert_eq!( - account_ref.borrow_state, 42, + account_ref.borrow_state.get(), + 42, "borrow_state field should be accessible and match" ); assert_eq!( - account_ref.data_len, 256, + account_ref.data_len.get(), + 256, "data_len field should be accessible and match" ); }