diff --git a/interface/src/state/account.rs b/interface/src/state/account.rs index fbe6377..65e8c69 100644 --- a/interface/src/state/account.rs +++ b/interface/src/state/account.rs @@ -1,12 +1,13 @@ -use pinocchio::{ - account_info::{AccountInfo, Ref}, - program_error::ProgramError, - pubkey::Pubkey, -}; +use pinocchio::pubkey::Pubkey; -use crate::program::ID; +use super::{account_state::AccountState, COption, Initializable, RawType}; -use super::{account_state::AccountState, COption}; +/// Incinerator address. +const INCINERATOR_ID: Pubkey = + pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111"); + +/// System program id. +const SYSTEM_PROGRAM_ID: Pubkey = pinocchio_pubkey::pubkey!("11111111111111111111111111111111"); /// Internal representation of a token account data. #[repr(C)] @@ -44,89 +45,28 @@ pub struct Account { } impl Account { - pub const LEN: usize = core::mem::size_of::(); - - /// Return a `TokenAccount` from the given account info. - /// - /// 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> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountData); - } - Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe { - Self::from_bytes(data) - })) - } - - /// Return a `TokenAccount` from the given account info. - /// - /// This method performs owner and length validation on `AccountInfo`, but does not - /// perform the borrow check. - /// - /// # Safety - /// - /// The caller must ensure that it is safe to borrow the account data – e.g., there are - /// no mutable borrows of the account data. - #[inline] - pub unsafe fn from_account_info_unchecked( - account_info: &AccountInfo, - ) -> Result<&Account, ProgramError> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountData); - } - Ok(Self::from_bytes(account_info.borrow_data_unchecked())) - } - - /// Return a `TokenAccount` from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`. - #[inline(always)] - pub unsafe fn from_bytes(bytes: &[u8]) -> &Self { - &*(bytes.as_ptr() as *const Account) - } - - /// Return a mutable `Mint` reference from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `Mint`. #[inline(always)] - pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self { - &mut *(bytes.as_mut_ptr() as *mut Account) - } - - #[inline] pub fn set_amount(&mut self, amount: u64) { self.amount = amount.to_le_bytes(); } - #[inline] + #[inline(always)] pub fn amount(&self) -> u64 { u64::from_le_bytes(self.amount) } - #[inline] + #[inline(always)] pub fn clear_delegate(&mut self) { self.delegate.0[0] = 0; } - #[inline] + #[inline(always)] pub fn set_delegate(&mut self, delegate: &Pubkey) { self.delegate.0[0] = 1; self.delegate.1 = *delegate; } - #[inline] + #[inline(always)] pub fn delegate(&self) -> Option<&Pubkey> { if self.delegate.0[0] == 1 { Some(&self.delegate.1) @@ -135,17 +75,17 @@ impl Account { } } - #[inline] + #[inline(always)] pub fn set_native(&mut self, value: bool) { self.is_native[0] = value as u8; } - #[inline] + #[inline(always)] pub fn is_native(&self) -> bool { self.is_native[0] == 1 } - #[inline] + #[inline(always)] pub fn native_amount(&self) -> Option { if self.is_native() { Some(u64::from_le_bytes(self.native_amount)) @@ -154,28 +94,28 @@ impl Account { } } - #[inline] + #[inline(always)] pub fn set_delegated_amount(&mut self, amount: u64) { self.delegated_amount = amount.to_le_bytes(); } - #[inline] + #[inline(always)] pub fn delegated_amount(&self) -> u64 { u64::from_le_bytes(self.delegated_amount) } - #[inline] + #[inline(always)] pub fn clear_close_authority(&mut self) { self.close_authority.0[0] = 0; } - #[inline] + #[inline(always)] pub fn set_close_authority(&mut self, value: &Pubkey) { self.close_authority.0[0] = 1; self.close_authority.1 = *value; } - #[inline] + #[inline(always)] pub fn close_authority(&self) -> Option<&Pubkey> { if self.close_authority.0[0] == 1 { Some(&self.close_authority.1) @@ -185,12 +125,23 @@ impl Account { } #[inline(always)] - pub fn is_initialized(&self) -> bool { - self.state != AccountState::Uninitialized + pub fn is_frozen(&self) -> bool { + self.state == AccountState::Frozen } #[inline(always)] - pub fn is_frozen(&self) -> bool { - self.state == AccountState::Frozen + pub fn is_owned_by_system_program_or_incinerator(&self) -> bool { + SYSTEM_PROGRAM_ID == self.owner || INCINERATOR_ID == self.owner + } +} + +impl RawType for Account { + const LEN: usize = core::mem::size_of::(); +} + +impl Initializable for Account { + #[inline(always)] + fn is_initialized(&self) -> bool { + self.state != AccountState::Uninitialized } } diff --git a/interface/src/state/mint.rs b/interface/src/state/mint.rs index 1ca3d90..3b4d41b 100644 --- a/interface/src/state/mint.rs +++ b/interface/src/state/mint.rs @@ -1,12 +1,6 @@ -use pinocchio::{ - account_info::{AccountInfo, Ref}, - program_error::ProgramError, - pubkey::Pubkey, -}; +use pinocchio::pubkey::Pubkey; -use crate::program::ID; - -use super::COption; +use super::{COption, Initializable, RawType}; /// Internal representation of a mint data. #[repr(C)] @@ -33,100 +27,33 @@ pub struct Mint { } impl Mint { - /// The length of the `Mint` account data. - pub const LEN: usize = core::mem::size_of::(); - - /// Return a `Mint` from the given account info. - /// - /// 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> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountOwner); - } - Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe { - Self::from_bytes(data) - })) - } - - /// Return a `Mint` from the given account info. - /// - /// This method performs owner and length validation on `AccountInfo`, but does not - /// perform the borrow check. - /// - /// # Safety - /// - /// The caller must ensure that it is safe to borrow the account data – e.g., there are - /// no mutable borrows of the account data. - #[inline] - pub unsafe fn from_account_info_unchecked( - account_info: &AccountInfo, - ) -> Result<&Self, ProgramError> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountOwner); - } - Ok(Self::from_bytes(account_info.borrow_data_unchecked())) - } - - /// Return a `Mint` reference from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `Mint`. - #[inline] - pub unsafe fn from_bytes(bytes: &[u8]) -> &Self { - &*(bytes.as_ptr() as *const Mint) - } - - /// Return a mutable `Mint` reference from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `Mint`. - #[inline] - pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self { - &mut *(bytes.as_mut_ptr() as *mut Mint) - } - - #[inline] + #[inline(always)] pub fn set_supply(&mut self, supply: u64) { self.supply = supply.to_le_bytes(); } - #[inline] + #[inline(always)] pub fn supply(&self) -> u64 { u64::from_le_bytes(self.supply) } - #[inline] + #[inline(always)] pub fn set_initialized(&mut self, value: bool) { self.is_initialized = value as u8; } - #[inline] - pub fn is_initialized(&self) -> bool { - self.is_initialized == 1 - } - - #[inline] + #[inline(always)] pub fn clear_mint_authority(&mut self) { self.mint_authority.0[0] = 0; } - #[inline] + #[inline(always)] pub fn set_mint_authority(&mut self, mint_authority: &Pubkey) { self.mint_authority.0[0] = 1; self.mint_authority.1 = *mint_authority; } - #[inline] + #[inline(always)] pub fn mint_authority(&self) -> Option<&Pubkey> { if self.mint_authority.0[0] == 1 { Some(&self.mint_authority.1) @@ -135,18 +62,18 @@ impl Mint { } } - #[inline] + #[inline(always)] pub fn clear_freeze_authority(&mut self) { self.freeze_authority.0[0] = 0; } - #[inline] + #[inline(always)] pub fn set_freeze_authority(&mut self, freeze_authority: &Pubkey) { self.freeze_authority.0[0] = 1; self.freeze_authority.1 = *freeze_authority; } - #[inline] + #[inline(always)] pub fn freeze_authority(&self) -> Option<&Pubkey> { if self.freeze_authority.0[0] == 1 { Some(&self.freeze_authority.1) @@ -155,3 +82,15 @@ impl Mint { } } } + +impl RawType for Mint { + /// The length of the `Mint` account data. + const LEN: usize = core::mem::size_of::(); +} + +impl Initializable for Mint { + #[inline(always)] + fn is_initialized(&self) -> bool { + self.is_initialized == 1 + } +} diff --git a/interface/src/state/mod.rs b/interface/src/state/mod.rs index 04c0342..b7c35df 100644 --- a/interface/src/state/mod.rs +++ b/interface/src/state/mod.rs @@ -1,3 +1,5 @@ +use pinocchio::program_error::ProgramError; + pub mod account; pub mod account_state; pub mod mint; @@ -5,3 +7,86 @@ pub mod multisig; /// Type alias for fields represented as `COption`. pub type COption = ([u8; 4], T); + +/// Marker trait for types that can cast from a raw pointer. +/// +/// It is up to the type implementing this trait to guarantee that the cast is safe, +/// i.e., that the fields of the type are well aligned and there are no padding bytes. +pub trait RawType { + /// The length of the type. + /// + /// This must be equal to the size of each individual field in the type. + const LEN: usize; +} + +/// Trait to represent a type that can be initialized. +pub trait Initializable { + /// Return `true` if the object is initialized. + fn is_initialized(&self) -> bool; +} + +/// Return a reference for an initialized `T` from the given bytes. +/// +/// # Safety +/// +/// The caller must ensure that `bytes` contains a valid representation of `T`. +#[inline(always)] +pub unsafe fn load(bytes: &[u8]) -> Result<&T, ProgramError> { + load_unchecked(bytes).and_then(|t: &T| { + // checks if the data is initialized + if t.is_initialized() { + Ok(t) + } else { + Err(ProgramError::UninitializedAccount) + } + }) +} + +/// Return a `T` reference from the given bytes. +/// +/// This function does not check if the data is initialized. +/// +/// # Safety +/// +/// The caller must ensure that `bytes` contains a valid representation of `T`. +#[inline(always)] +pub unsafe fn load_unchecked(bytes: &[u8]) -> Result<&T, ProgramError> { + if bytes.len() != T::LEN { + return Err(ProgramError::InvalidAccountData); + } + Ok(&*(bytes.as_ptr() as *const T)) +} + +/// Return a mutable reference for an initialized `T` from the given bytes. +/// +/// # Safety +/// +/// The caller must ensure that `bytes` contains a valid representation of `T`. +#[inline(always)] +pub unsafe fn load_mut( + bytes: &mut [u8], +) -> Result<&mut T, ProgramError> { + load_mut_unchecked(bytes).and_then(|t: &mut T| { + // checks if the data is initialized + if t.is_initialized() { + Ok(t) + } else { + Err(ProgramError::UninitializedAccount) + } + }) +} + +/// Return a mutable `T` reference from the given bytes. +/// +/// This function does not check if the data is initialized. +/// +/// # Safety +/// +/// The caller must ensure that `bytes` contains a valid representation of `T`. +#[inline(always)] +pub unsafe fn load_mut_unchecked(bytes: &mut [u8]) -> Result<&mut T, ProgramError> { + if bytes.len() != T::LEN { + return Err(ProgramError::InvalidAccountData); + } + Ok(&mut *(bytes.as_mut_ptr() as *mut T)) +} diff --git a/interface/src/state/multisig.rs b/interface/src/state/multisig.rs index 362c614..920da97 100644 --- a/interface/src/state/multisig.rs +++ b/interface/src/state/multisig.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::{AccountInfo, Ref}, - program_error::ProgramError, - pubkey::Pubkey, -}; +use pinocchio::pubkey::Pubkey; -use crate::program::ID; +use super::{Initializable, RawType}; /// Minimum number of multisignature signers (min N) pub const MIN_SIGNERS: usize = 1; @@ -29,68 +25,6 @@ pub struct Multisig { } impl Multisig { - /// The length of the `Multisig` account data. - pub const LEN: usize = core::mem::size_of::(); - - /// Return a `Multisig` from the given account info. - /// - /// 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> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountOwner); - } - Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe { - Self::from_bytes(data) - })) - } - - /// Return a `Multisig` from the given account info. - /// - /// This method performs owner and length validation on `AccountInfo`, but does not - /// perform the borrow check. - /// - /// # Safety - /// - /// The caller must ensure that it is safe to borrow the account data – e.g., there are - /// no mutable borrows of the account data. - #[inline] - pub unsafe fn from_account_info_unchecked( - account_info: &AccountInfo, - ) -> Result<&Self, ProgramError> { - if account_info.data_len() != Self::LEN { - return Err(ProgramError::InvalidAccountData); - } - if account_info.owner() != &ID { - return Err(ProgramError::InvalidAccountOwner); - } - Ok(Self::from_bytes(account_info.borrow_data_unchecked())) - } - - /// Return a `Multisig` reference from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `Multisig`. - #[inline] - pub unsafe fn from_bytes(bytes: &[u8]) -> &Self { - &*(bytes.as_ptr() as *const Multisig) - } - - /// Return a mutable `Multisig` reference from the given bytes. - /// - /// # Safety - /// - /// The caller must ensure that `bytes` contains a valid representation of `Multisig`. - #[inline] - pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self { - &mut *(bytes.as_mut_ptr() as *mut Multisig) - } - /// Utility function that checks index is between [`MIN_SIGNERS`] and [`MAX_SIGNERS`]. pub fn is_valid_signer_index(index: usize) -> bool { (MIN_SIGNERS..=MAX_SIGNERS).contains(&index) @@ -100,9 +34,16 @@ impl Multisig { pub fn set_initialized(&mut self, value: bool) { self.is_initialized = value as u8; } +} - #[inline] - pub fn is_initialized(&self) -> bool { +impl RawType for Multisig { + /// The length of the `Mint` account data. + const LEN: usize = core::mem::size_of::(); +} + +impl Initializable for Multisig { + #[inline(always)] + fn is_initialized(&self) -> bool { self.is_initialized == 1 } } diff --git a/program/src/processor/amount_to_ui_amount.rs b/program/src/processor/amount_to_ui_amount.rs index 1b2034a..828e713 100644 --- a/program/src/processor/amount_to_ui_amount.rs +++ b/program/src/processor/amount_to_ui_amount.rs @@ -3,7 +3,10 @@ use pinocchio::{ account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; use pinocchio_log::logger::{Argument, Logger}; -use token_interface::state::mint::Mint; +use token_interface::{ + error::TokenError, + state::{load, mint::Mint}, +}; use super::{check_account_owner, MAX_DIGITS_U64}; @@ -27,7 +30,9 @@ pub fn process_amount_to_ui_amount( let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; check_account_owner(mint_info)?; // SAFETY: there is a single borrow to the `Mint` account. - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let mint = unsafe { + load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? + }; let mut logger = Logger::::default(); logger.append_with_args(amount, &[Argument::Precision(mint.decimals)]); diff --git a/program/src/processor/close_account.rs b/program/src/processor/close_account.rs index 8f5e79c..ffd0263 100644 --- a/program/src/processor/close_account.rs +++ b/program/src/processor/close_account.rs @@ -1,7 +1,16 @@ -use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; -use token_interface::{error::TokenError, state::account::Account}; +use pinocchio::{ + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, +}; +use token_interface::{ + error::TokenError, + state::{account::Account, load_mut}, +}; -use super::{is_owned_by_system_program_or_incinerator, validate_owner, INCINERATOR_ID}; +use super::validate_owner; + +/// Incinerator address. +const INCINERATOR_ID: Pubkey = + pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111"); #[inline(always)] pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { @@ -10,12 +19,15 @@ pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { return Err(ProgramError::NotEnoughAccountKeys); }; - if source_account_info.key() == destination_account_info.key() { + // Comparing whether the AccountInfo's "point" to the same account or + // not - this is a faster comparison since it just checks the internal + // raw pointer. + if source_account_info == destination_account_info { return Err(ProgramError::InvalidAccountData); } let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; if !source_account.is_native() && source_account.amount() != 0 { return Err(TokenError::NonNativeHasBalance.into()); @@ -25,7 +37,7 @@ pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { .close_authority() .unwrap_or(&source_account.owner); - if !is_owned_by_system_program_or_incinerator(source_account_info.owner()) { + if !source_account.is_owned_by_system_program_or_incinerator() { validate_owner(authority, authority_info, remaining)?; } else if destination_account_info.key() != &INCINERATOR_ID { return Err(ProgramError::InvalidAccountData); diff --git a/program/src/processor/get_account_data_size.rs b/program/src/processor/get_account_data_size.rs index 5e96960..eb46a88 100644 --- a/program/src/processor/get_account_data_size.rs +++ b/program/src/processor/get_account_data_size.rs @@ -1,7 +1,10 @@ use pinocchio::{ account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; -use token_interface::state::{account::Account, mint::Mint}; +use token_interface::{ + error::TokenError, + state::{account::Account, load, mint::Mint, RawType}, +}; use super::check_account_owner; @@ -14,7 +17,9 @@ pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult // Make sure the mint is valid. check_account_owner(mint_info)?; - let _ = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let _ = unsafe { + load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint) + }; set_return_data(&Account::LEN.to_le_bytes()); diff --git a/program/src/processor/initialize_immutable_owner.rs b/program/src/processor/initialize_immutable_owner.rs index 766e5b5..cf69da4 100644 --- a/program/src/processor/initialize_immutable_owner.rs +++ b/program/src/processor/initialize_immutable_owner.rs @@ -1,12 +1,14 @@ use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult}; -use token_interface::{error::TokenError, state::account::Account}; +use token_interface::{ + error::TokenError, + state::{account::Account, load_unchecked, Initializable}, +}; #[inline(always)] pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult { let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let account = - unsafe { Account::from_bytes_mut(token_account_info.borrow_mut_data_unchecked()) }; + let account = unsafe { load_unchecked::(token_account_info.borrow_data_unchecked())? }; if account.is_initialized() { return Err(TokenError::AlreadyInUse.into()); diff --git a/program/src/processor/initialize_mint.rs b/program/src/processor/initialize_mint.rs index 752537d..8460ce0 100644 --- a/program/src/processor/initialize_mint.rs +++ b/program/src/processor/initialize_mint.rs @@ -6,7 +6,10 @@ use pinocchio::{ sysvars::{rent::Rent, Sysvar}, ProgramResult, }; -use token_interface::{error::TokenError, state::mint::Mint}; +use token_interface::{ + error::TokenError, + state::{load_mut_unchecked, mint::Mint, Initializable}, +}; #[inline(always)] pub fn process_initialize_mint( @@ -32,7 +35,7 @@ pub fn process_initialize_mint( (mint_info, None) }; - let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; + let mint = unsafe { load_mut_unchecked::(mint_info.borrow_mut_data_unchecked())? }; if mint.is_initialized() { return Err(TokenError::AlreadyInUse.into()); diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 8a0ee5e..46adb30 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -10,7 +10,11 @@ use pinocchio::{ }; use token_interface::{ error::TokenError, - state::multisig::{Multisig, MAX_SIGNERS}, + state::{ + load, + multisig::{Multisig, MAX_SIGNERS}, + RawType, + }, }; pub mod amount_to_ui_amount; @@ -67,24 +71,12 @@ pub use transfer::process_transfer; pub use transfer_checked::process_transfer_checked; pub use ui_amount_to_amount::process_ui_amount_to_amount; -/// Incinerator address. -const INCINERATOR_ID: Pubkey = - pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111"); - -/// System program id. -const SYSTEM_PROGRAM_ID: Pubkey = pinocchio_pubkey::pubkey!("11111111111111111111111111111111"); - /// An uninitialized byte. const UNINIT_BYTE: MaybeUninit = MaybeUninit::uninit(); /// Maximum number of digits in a `u64``. const MAX_DIGITS_U64: usize = 20; -#[inline(always)] -fn is_owned_by_system_program_or_incinerator(owner: &Pubkey) -> bool { - &SYSTEM_PROGRAM_ID == owner || &INCINERATOR_ID == owner -} - /// Checks that the account is owned by the expected program. #[inline(always)] fn check_account_owner(account_info: &AccountInfo) -> ProgramResult { @@ -107,7 +99,7 @@ fn validate_owner( } if owner_account_info.data_len() == Multisig::LEN && &crate::ID != owner_account_info.owner() { - let multisig = unsafe { Multisig::from_bytes(owner_account_info.borrow_data_unchecked()) }; + let multisig = unsafe { load::(owner_account_info.borrow_data_unchecked())? }; let mut num_signers = 0; let mut matched = [false; MAX_SIGNERS]; diff --git a/program/src/processor/revoke.rs b/program/src/processor/revoke.rs index 4ed5016..2d40b02 100644 --- a/program/src/processor/revoke.rs +++ b/program/src/processor/revoke.rs @@ -1,5 +1,8 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; -use token_interface::{error::TokenError, state::account::Account}; +use token_interface::{ + error::TokenError, + state::{account::Account, load_mut}, +}; use super::validate_owner; @@ -10,7 +13,7 @@ pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> Pro }; let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); diff --git a/program/src/processor/set_authority.rs b/program/src/processor/set_authority.rs index 4661342..2b9fd94 100644 --- a/program/src/processor/set_authority.rs +++ b/program/src/processor/set_authority.rs @@ -6,7 +6,7 @@ use pinocchio::{ use token_interface::{ error::TokenError, instruction::AuthorityType, - state::{account::Account, mint::Mint}, + state::{account::Account, load_mut, mint::Mint, RawType}, }; use super::validate_owner; @@ -27,7 +27,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) }; if account_info.data_len() == Account::LEN { - let account = unsafe { Account::from_bytes_mut(account_info.borrow_mut_data_unchecked()) }; + let account = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; if account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -65,7 +65,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) } } } else if account_info.data_len() == Mint::LEN { - let mint = unsafe { Mint::from_bytes_mut(account_info.borrow_mut_data_unchecked()) }; + let mint = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; match authority_type { AuthorityType::MintTokens => { diff --git a/program/src/processor/shared/approve.rs b/program/src/processor/shared/approve.rs index 5715a12..40fa7af 100644 --- a/program/src/processor/shared/approve.rs +++ b/program/src/processor/shared/approve.rs @@ -1,7 +1,7 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, mint::Mint}, + state::{account::Account, load, load_mut, mint::Mint}, }; use crate::processor::validate_owner; @@ -46,7 +46,7 @@ pub fn process_approve( // Validates source account. let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -57,7 +57,7 @@ pub fn process_approve( return Err(TokenError::MintMismatch.into()); } - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; if expected_decimals != mint.decimals { return Err(TokenError::MintDecimalsMismatch.into()); diff --git a/program/src/processor/shared/burn.rs b/program/src/processor/shared/burn.rs index d23872f..1c26785 100644 --- a/program/src/processor/shared/burn.rs +++ b/program/src/processor/shared/burn.rs @@ -1,12 +1,10 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, mint::Mint}, + state::{account::Account, load_mut, mint::Mint}, }; -use crate::processor::{ - check_account_owner, is_owned_by_system_program_or_incinerator, validate_owner, -}; +use crate::processor::{check_account_owner, validate_owner}; #[inline(always)] pub fn process_burn( @@ -19,7 +17,7 @@ pub fn process_burn( }; let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -35,7 +33,7 @@ pub fn process_burn( .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; - let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; + let mint = unsafe { load_mut::(mint_info.borrow_mut_data_unchecked())? }; if mint_info.key() != &source_account.mint { return Err(TokenError::MintMismatch.into()); @@ -47,7 +45,7 @@ pub fn process_burn( } } - if !is_owned_by_system_program_or_incinerator(&source_account.owner) { + if !source_account.is_owned_by_system_program_or_incinerator() { match source_account.delegate() { Some(delegate) if authority_info.key() == delegate => { validate_owner(delegate, authority_info, remaining)?; diff --git a/program/src/processor/shared/initialize_account.rs b/program/src/processor/shared/initialize_account.rs index fc66467..d751a72 100644 --- a/program/src/processor/shared/initialize_account.rs +++ b/program/src/processor/shared/initialize_account.rs @@ -1,4 +1,3 @@ -use core::mem::size_of; use pinocchio::{ account_info::AccountInfo, program_error::ProgramError, @@ -9,7 +8,10 @@ use pinocchio::{ use token_interface::{ error::TokenError, native_mint::is_native_mint, - state::{account::Account, account_state::AccountState, mint::Mint}, + state::{ + account::Account, account_state::AccountState, load, load_mut_unchecked, mint::Mint, + Initializable, + }, }; use crate::processor::check_account_owner; @@ -36,36 +38,37 @@ pub fn process_initialize_account( // Check rent-exempt status of the token account. - let is_exempt = if rent_sysvar_account { + let new_account_info_data_len = new_account_info.data_len(); + + let minimum_balance = if rent_sysvar_account { let rent_sysvar_info = remaning.first().ok_or(ProgramError::NotEnoughAccountKeys)?; let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; - rent.is_exempt(new_account_info.lamports(), size_of::()) + rent.minimum_balance(new_account_info_data_len) } else { - Rent::get()?.is_exempt(new_account_info.lamports(), size_of::()) + Rent::get()?.minimum_balance(new_account_info_data_len) }; - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } + let is_native_mint = is_native_mint(mint_info.key()); // Initialize the account. - let account = unsafe { Account::from_bytes_mut(new_account_info.borrow_mut_data_unchecked()) }; + let account = + unsafe { load_mut_unchecked::(new_account_info.borrow_mut_data_unchecked())? }; if account.is_initialized() { return Err(TokenError::AlreadyInUse.into()); } - let is_native_mint = is_native_mint(mint_info.key()); + if new_account_info.lamports() < minimum_balance { + return Err(TokenError::NotRentExempt.into()); + } if !is_native_mint { check_account_owner(mint_info)?; - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; - - if !mint.is_initialized() { - return Err(TokenError::InvalidMint.into()); - } + let _ = unsafe { + load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? + }; } account.state = AccountState::Initialized; @@ -73,15 +76,12 @@ pub fn process_initialize_account( account.owner = *owner; if is_native_mint { - let rent = Rent::get()?; - let rent_exempt_reserve = rent.minimum_balance(size_of::()); - account.set_native(true); unsafe { account.set_amount( new_account_info .borrow_lamports_unchecked() - .checked_sub(rent_exempt_reserve) + .checked_sub(minimum_balance) .ok_or(TokenError::Overflow)?, ); } diff --git a/program/src/processor/shared/initialize_multisig.rs b/program/src/processor/shared/initialize_multisig.rs index cae5084..8831c19 100644 --- a/program/src/processor/shared/initialize_multisig.rs +++ b/program/src/processor/shared/initialize_multisig.rs @@ -4,7 +4,10 @@ use pinocchio::{ sysvars::{rent::Rent, Sysvar}, ProgramResult, }; -use token_interface::{error::TokenError, state::multisig::Multisig}; +use token_interface::{ + error::TokenError, + state::{load_mut_unchecked, multisig::Multisig, Initializable}, +}; #[inline(always)] pub fn process_initialize_multisig( @@ -35,16 +38,17 @@ pub fn process_initialize_multisig( Rent::get()?.is_exempt(multisig_info.lamports(), multisig_info_data_len) }; - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } - - let multisig = unsafe { Multisig::from_bytes_mut(multisig_info.borrow_mut_data_unchecked()) }; + let multisig = + unsafe { load_mut_unchecked::(multisig_info.borrow_mut_data_unchecked())? }; if multisig.is_initialized() { return Err(TokenError::AlreadyInUse.into()); } + if !is_exempt { + return Err(TokenError::NotRentExempt.into()); + } + // Initialize the multisig account. multisig.m = m; diff --git a/program/src/processor/shared/mint_to.rs b/program/src/processor/shared/mint_to.rs index cc891bd..7edd89e 100644 --- a/program/src/processor/shared/mint_to.rs +++ b/program/src/processor/shared/mint_to.rs @@ -1,7 +1,7 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, mint::Mint}, + state::{account::Account, load_mut, mint::Mint}, }; use crate::processor::{check_account_owner, validate_owner}; @@ -19,7 +19,7 @@ pub fn process_mint_to( // Validates the destination account. let destination_account = - unsafe { Account::from_bytes_mut(destination_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(destination_account_info.borrow_mut_data_unchecked())? }; if destination_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -33,7 +33,7 @@ pub fn process_mint_to( return Err(TokenError::MintMismatch.into()); } - let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; + let mint = unsafe { load_mut::(mint_info.borrow_mut_data_unchecked())? }; if let Some(expected_decimals) = expected_decimals { if expected_decimals != mint.decimals { @@ -49,19 +49,19 @@ pub fn process_mint_to( if amount == 0 { check_account_owner(mint_info)?; check_account_owner(destination_account_info)?; - } - - let destination_amount = destination_account - .amount() - .checked_add(amount) - .ok_or(ProgramError::InvalidAccountData)?; - destination_account.set_amount(destination_amount); + } else { + let destination_amount = destination_account + .amount() + .checked_add(amount) + .ok_or(ProgramError::InvalidAccountData)?; + destination_account.set_amount(destination_amount); - let mint_supply = mint - .supply() - .checked_add(amount) - .ok_or(ProgramError::InvalidAccountData)?; - mint.set_supply(mint_supply); + let mint_supply = mint + .supply() + .checked_add(amount) + .ok_or(ProgramError::InvalidAccountData)?; + mint.set_supply(mint_supply); + } Ok(()) } diff --git a/program/src/processor/shared/toggle_account_state.rs b/program/src/processor/shared/toggle_account_state.rs index f52b619..bf05bfd 100644 --- a/program/src/processor/shared/toggle_account_state.rs +++ b/program/src/processor/shared/toggle_account_state.rs @@ -1,7 +1,7 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, account_state::AccountState, mint::Mint}, + state::{account::Account, account_state::AccountState, load, load_mut, mint::Mint}, }; use crate::processor::validate_owner; @@ -13,7 +13,7 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P }; let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { return Err(TokenError::InvalidState.into()); @@ -25,7 +25,7 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P return Err(TokenError::MintMismatch.into()); } - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; match mint.freeze_authority() { Some(authority) => validate_owner(authority, authority_info, remaining), diff --git a/program/src/processor/shared/transfer.rs b/program/src/processor/shared/transfer.rs index d3818b3..6d33d5f 100644 --- a/program/src/processor/shared/transfer.rs +++ b/program/src/processor/shared/transfer.rs @@ -1,7 +1,7 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, mint::Mint}, + state::{account::Account, load, load_mut, mint::Mint}, }; use crate::processor::{check_account_owner, validate_owner}; @@ -52,10 +52,10 @@ pub fn process_transfer( // Validates source and destination accounts. let source_account = - unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; let destination_account = - unsafe { Account::from_bytes_mut(destination_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(destination_account_info.borrow_mut_data_unchecked())? }; if source_account.is_frozen() || destination_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -80,7 +80,7 @@ pub fn process_transfer( return Err(TokenError::MintMismatch.into()); } - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; if decimals != mint.decimals { return Err(TokenError::MintDecimalsMismatch.into()); @@ -118,31 +118,29 @@ pub fn process_transfer( // to these account. check_account_owner(source_account_info)?; check_account_owner(destination_account_info)?; + } else { + // Moves the tokens. - return Ok(()); - } - - // Moves the tokens. - - source_account.set_amount(remaining_amount); - - let destination_amount = destination_account - .amount() - .checked_add(amount) - .ok_or(TokenError::Overflow)?; - destination_account.set_amount(destination_amount); - - if source_account.is_native() { - let source_lamports = unsafe { source_account_info.borrow_mut_lamports_unchecked() }; - *source_lamports = source_lamports - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; + source_account.set_amount(remaining_amount); - let destination_lamports = - unsafe { destination_account_info.borrow_mut_lamports_unchecked() }; - *destination_lamports = destination_lamports + let destination_amount = destination_account + .amount() .checked_add(amount) .ok_or(TokenError::Overflow)?; + destination_account.set_amount(destination_amount); + + if source_account.is_native() { + let source_lamports = unsafe { source_account_info.borrow_mut_lamports_unchecked() }; + *source_lamports = source_lamports + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + + let destination_lamports = + unsafe { destination_account_info.borrow_mut_lamports_unchecked() }; + *destination_lamports = destination_lamports + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + } } Ok(()) diff --git a/program/src/processor/sync_native.rs b/program/src/processor/sync_native.rs index fd90f2b..9bba708 100644 --- a/program/src/processor/sync_native.rs +++ b/program/src/processor/sync_native.rs @@ -1,5 +1,8 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; -use token_interface::{error::TokenError, state::account::Account}; +use token_interface::{ + error::TokenError, + state::{account::Account, load_mut}, +}; use super::check_account_owner; @@ -10,7 +13,7 @@ pub fn process_sync_native(accounts: &[AccountInfo]) -> ProgramResult { check_account_owner(native_account_info)?; let native_account = - unsafe { Account::from_bytes_mut(native_account_info.borrow_mut_data_unchecked()) }; + unsafe { load_mut::(native_account_info.borrow_mut_data_unchecked())? }; if let Option::Some(rent_exempt_reserve) = native_account.native_amount() { let new_amount = native_account_info diff --git a/program/src/processor/ui_amount_to_amount.rs b/program/src/processor/ui_amount_to_amount.rs index c16b837..752386e 100644 --- a/program/src/processor/ui_amount_to_amount.rs +++ b/program/src/processor/ui_amount_to_amount.rs @@ -2,7 +2,7 @@ use core::str::from_utf8; use pinocchio::{ account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; -use token_interface::state::mint::Mint; +use token_interface::state::{load, mint::Mint}; use super::{check_account_owner, try_ui_amount_into_amount}; @@ -17,7 +17,7 @@ pub fn process_ui_amount_to_amount( let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; check_account_owner(mint_info)?; // SAFETY: there is a single borrow to the `Mint` account. - let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; + let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; let amount = try_ui_amount_into_amount(ui_amount, mint.decimals)?; set_return_data(&amount.to_le_bytes()); diff --git a/program/tests/approve_checked.rs b/program/tests/approve_checked.rs index 63b3ad7..8320572 100644 --- a/program/tests/approve_checked.rs +++ b/program/tests/approve_checked.rs @@ -63,7 +63,7 @@ async fn approve_checked(token_program: Pubkey) { &owner.pubkey(), &[], 50, - 0, + 4, ) .unwrap(); approve_ix.program_id = token_program; diff --git a/program/tests/burn_checked.rs b/program/tests/burn_checked.rs index 0502653..d53e21b 100644 --- a/program/tests/burn_checked.rs +++ b/program/tests/burn_checked.rs @@ -60,7 +60,7 @@ async fn burn_checked(token_program: Pubkey) { &owner.pubkey(), &[], 50, - 0, + 4, ) .unwrap(); burn_ix.program_id = token_program; diff --git a/program/tests/mint_to_checked.rs b/program/tests/mint_to_checked.rs index 42fcb8f..c6ecf54 100644 --- a/program/tests/mint_to_checked.rs +++ b/program/tests/mint_to_checked.rs @@ -49,7 +49,7 @@ async fn mint_to_checked(token_program: Pubkey) { &mint_authority.pubkey(), &[], 100, - 0, + 4, ) .unwrap(); // Switches the program id to the token program. diff --git a/program/tests/transfer_checked.rs b/program/tests/transfer_checked.rs index 9616d22..b4c2865 100644 --- a/program/tests/transfer_checked.rs +++ b/program/tests/transfer_checked.rs @@ -66,7 +66,7 @@ async fn transfer_checked(token_program: Pubkey) { &owner.pubkey(), &[], 100, - 0, + 4, ) .unwrap(); transfer_ix.program_id = token_program;