diff --git a/Cargo.lock b/Cargo.lock index 5e0fd7b..5325d01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5206,7 +5206,6 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "token-interface" version = "0.0.0" dependencies = [ - "bytemuck", "pinocchio", "pinocchio-pubkey", ] @@ -5216,7 +5215,6 @@ name = "token-program" version = "0.0.0" dependencies = [ "assert_matches", - "bytemuck", "pinocchio", "pinocchio-pubkey", "solana-program-test", diff --git a/README.md b/README.md index fab4746..57d38c0 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@

-> [!WARNING] -> The program is not yet fully-optimized. There are still opportunities to improve the compute units consumption. - ## Overview This repository contains a **proof-of-concept** of a reimplementation of the SPL Token program, one of the most used programs on Solana, using [`pinocchio`](https://github.com/febo/pinocchio). The purpose is to have an implementation that optimizes the compute units, while being fully compatible with the original implementation — i.e., support the exact same instruction and account layouts as SPL Token, byte for byte. @@ -23,27 +20,27 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL | Instruction | Completed | CU (`p-token`) | CU (`spl-token`) | |----------------------------|-----------|----------------|------------------| -| `InitializeMint` | ✅ | 389 | 2967 | -| `InitializeAccount` | ✅ | 409 | 4527 | -| `InitializeMultisig` | ✅ | 458 | 2973 | -| `Transfer` | ✅ | 194 | 4645 | -| `Approve` | ✅ | 151 | 2904 | -| `Revoke` | ✅ | 93 | 2677 | -| `SetAuthority` | ✅ | 171 | 3167 | -| `MintTo` | ✅ | 196 | 4538 | -| `Burn` | ✅ | 184 | 4753 | -| `CloseAccount` | ✅ | 163 | 2916 | -| `FreezeAccount` | ✅ | 131 | 4265 | -| `ThawAccount` | ✅ | 132 | 4267 | -| `TransferChecked` | ✅ | 207 | 6201 | -| `ApproveChecked` | ✅ | 166 | 4459 | -| `MintToChecked` | ✅ | 180 | 4546 | -| `BurnChecked` | ✅ | 166 | 4755 | -| `InitializeAccount2` | ✅ | 394 | 4388 | +| `InitializeMint` | ✅ | 343 | 2967 | +| `InitializeAccount` | ✅ | 416 | 4527 | +| `InitializeMultisig` | ✅ | 499 | 2973 | +| `Transfer` | ✅ | 140 | 4645 | +| `Approve` | ✅ | 133 | 2904 | +| `Revoke` | ✅ | 106 | 2677 | +| `SetAuthority` | ✅ | 142 | 3167 | +| `MintTo` | ✅ | 143 | 4538 | +| `Burn` | ✅ | 175 | 4753 | +| `CloseAccount` | ✅ | 147 | 2916 | +| `FreezeAccount` | ✅ | 141 | 4265 | +| `ThawAccount` | ✅ | 142 | 4267 | +| `TransferChecked` | ✅ | 211 | 6201 | +| `ApproveChecked` | ✅ | 169 | 4459 | +| `MintToChecked` | ✅ | 178 | 4546 | +| `BurnChecked` | ✅ | 181 | 4755 | +| `InitializeAccount2` | ✅ | 399 | 4388 | | `SyncNative` | ✅ | | | -| `InitializeAccount3` | ✅ | 523 | 4240 | -| `InitializeMultisig2` | ✅ | 563 | 2826 | -| `InitializeMint2` | ✅ | 500 | 2827 | +| `InitializeAccount3` | ✅ | 508 | 4240 | +| `InitializeMultisig2` | ✅ | 579 | 2826 | +| `InitializeMint2` | ✅ | 477 | 2827 | | `GetAccountDataSize` | ✅ | | | | `InitializeImmutableOwner` | ✅ | | | | `AmountToUiAmount` | ✅ | | | diff --git a/interface/Cargo.toml b/interface/Cargo.toml index 628cb2a..fb26855 100644 --- a/interface/Cargo.toml +++ b/interface/Cargo.toml @@ -8,6 +8,5 @@ repository = { workspace = true } publish = false [dependencies] -bytemuck = { workspace = true } pinocchio = { workspace = true } pinocchio-pubkey = { workspace = true } diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index c78e6bb..a829e9a 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -1,8 +1,8 @@ -//! Instruction types +//! Instruction types. -use pinocchio::pubkey::Pubkey; +use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; -use crate::state::PodCOption; +use crate::error::TokenError; /// Instructions supported by the token program. #[repr(C)] @@ -27,7 +27,7 @@ pub enum TokenInstruction<'a> { /// The authority/multisignature to mint tokens. mint_authority: Pubkey, /// The freeze authority/multisignature of the mint. - freeze_authority: PodCOption, + freeze_authority: Option, }, /// Initializes a new account to hold tokens. If this account is associated @@ -147,7 +147,7 @@ pub enum TokenInstruction<'a> { /// The type of authority to update. authority_type: AuthorityType, /// The new authority - new_authority: PodCOption, + new_authority: Option, }, /// Mints new tokens to an account. The native mint does not support @@ -416,7 +416,7 @@ pub enum TokenInstruction<'a> { /// The authority/multisignature to mint tokens. mint_authority: Pubkey, /// The freeze authority/multisignature of the mint. - freeze_authority: PodCOption, + freeze_authority: Option, }, /// Gets the required size of an account for the given mint as a @@ -482,7 +482,7 @@ pub enum TokenInstruction<'a> { // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility } -/// Specifies the authority type for SetAuthority instructions +/// Specifies the authority type for `SetAuthority` instructions #[repr(u8)] #[derive(Clone, Debug, PartialEq)] pub enum AuthorityType { @@ -506,13 +506,13 @@ impl AuthorityType { } } - pub fn from(index: u8) -> Self { + pub fn from(index: u8) -> Result { match index { - 0 => AuthorityType::MintTokens, - 1 => AuthorityType::FreezeAccount, - 2 => AuthorityType::AccountOwner, - 3 => AuthorityType::CloseAccount, - _ => panic!("invalid authority type: {index}"), + 0 => Ok(AuthorityType::MintTokens), + 1 => Ok(AuthorityType::FreezeAccount), + 2 => Ok(AuthorityType::AccountOwner), + 3 => Ok(AuthorityType::CloseAccount), + _ => Err(TokenError::InvalidInstruction.into()), } } } diff --git a/interface/src/state/account.rs b/interface/src/state/account.rs index e88b48d..fbe6377 100644 --- a/interface/src/state/account.rs +++ b/interface/src/state/account.rs @@ -1,11 +1,15 @@ -use bytemuck::{Pod, Zeroable}; -use pinocchio::pubkey::Pubkey; +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; -use super::{PodCOption, PodU64}; +use crate::program::ID; -/// Account data. +use super::{account_state::AccountState, COption}; + +/// Internal representation of a token account data. #[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] pub struct Account { /// The mint associated with this account pub mint: Pubkey, @@ -14,82 +18,179 @@ pub struct Account { pub owner: Pubkey, /// The amount of tokens this account holds. - pub amount: PodU64, + amount: [u8; 8], /// If `delegate` is `Some` then `delegated_amount` represents - /// the amount authorized by the delegate - pub delegate: PodCOption, + /// the amount authorized by the delegate. + delegate: COption, + + /// The account's state. + pub state: AccountState, - /// The account's state - pub state: u8, + /// Indicates whether this account represents a native token or not. + is_native: [u8; 4], /// If is_native.is_some, this is a native token, and the value logs the /// rent-exempt reserve. An Account is required to be rent-exempt, so /// the value is used by the Processor to ensure that wrapped SOL /// accounts do not drop below this threshold. - pub is_native: PodCOption, + native_amount: [u8; 8], - /// The amount delegated - pub delegated_amount: PodU64, + /// The amount delegated. + delegated_amount: [u8; 8], /// Optional authority to close the account. - pub close_authority: PodCOption, + close_authority: COption, } impl Account { - /// Size of the `Account` account. - pub const LEN: usize = core::mem::size_of::(); + 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 is_initialized(&self) -> bool { - self.state != AccountState::Uninitialized as u8 + 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 fn is_frozen(&self) -> bool { - self.state == AccountState::Frozen as u8 + 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] pub fn amount(&self) -> u64 { - self.amount.into() + u64::from_le_bytes(self.amount) } -} -/// Account state. -#[repr(u8)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub enum AccountState { - /// Account is not yet initialized - #[default] - Uninitialized, - - /// Account is initialized; the account owner and/or delegate may perform - /// permitted operations on this account - Initialized, - - /// Account has been frozen by the mint freeze authority. Neither the - /// account owner nor the delegate are able to perform operations on - /// this account. - Frozen, -} + #[inline] + pub fn clear_delegate(&mut self) { + self.delegate.0[0] = 0; + } -impl From for AccountState { - fn from(value: u8) -> Self { - match value { - 0 => AccountState::Uninitialized, - 1 => AccountState::Initialized, - 2 => AccountState::Frozen, - _ => panic!("invalid account state value: {value}"), + #[inline] + pub fn set_delegate(&mut self, delegate: &Pubkey) { + self.delegate.0[0] = 1; + self.delegate.1 = *delegate; + } + + #[inline] + pub fn delegate(&self) -> Option<&Pubkey> { + if self.delegate.0[0] == 1 { + Some(&self.delegate.1) + } else { + None } } -} -impl From for u8 { - fn from(value: AccountState) -> Self { - match value { - AccountState::Uninitialized => 0, - AccountState::Initialized => 1, - AccountState::Frozen => 2, + #[inline] + pub fn set_native(&mut self, value: bool) { + self.is_native[0] = value as u8; + } + + #[inline] + pub fn is_native(&self) -> bool { + self.is_native[0] == 1 + } + + #[inline] + pub fn native_amount(&self) -> Option { + if self.is_native() { + Some(u64::from_le_bytes(self.native_amount)) + } else { + None } } + + #[inline] + pub fn set_delegated_amount(&mut self, amount: u64) { + self.delegated_amount = amount.to_le_bytes(); + } + + #[inline] + pub fn delegated_amount(&self) -> u64 { + u64::from_le_bytes(self.delegated_amount) + } + + #[inline] + pub fn clear_close_authority(&mut self) { + self.close_authority.0[0] = 0; + } + + #[inline] + pub fn set_close_authority(&mut self, value: &Pubkey) { + self.close_authority.0[0] = 1; + self.close_authority.1 = *value; + } + + #[inline] + pub fn close_authority(&self) -> Option<&Pubkey> { + if self.close_authority.0[0] == 1 { + Some(&self.close_authority.1) + } else { + None + } + } + + #[inline(always)] + pub fn is_initialized(&self) -> bool { + self.state != AccountState::Uninitialized + } + + #[inline(always)] + pub fn is_frozen(&self) -> bool { + self.state == AccountState::Frozen + } } diff --git a/interface/src/state/account_state.rs b/interface/src/state/account_state.rs new file mode 100644 index 0000000..6747757 --- /dev/null +++ b/interface/src/state/account_state.rs @@ -0,0 +1,15 @@ +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AccountState { + /// Account is not yet initialized + Uninitialized, + + /// Account is initialized; the account owner and/or delegate may perform + /// permitted operations on this account + Initialized, + + /// Account has been frozen by the mint freeze authority. Neither the + /// account owner nor the delegate are able to perform operations on + /// this account. + Frozen, +} diff --git a/interface/src/state/mint.rs b/interface/src/state/mint.rs index 50896c6..1ca3d90 100644 --- a/interface/src/state/mint.rs +++ b/interface/src/state/mint.rs @@ -1,32 +1,157 @@ -use bytemuck::{Pod, Zeroable}; -use pinocchio::pubkey::Pubkey; +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; -use super::{PodBool, PodCOption, PodU64}; +use crate::program::ID; -/// Mint data. +use super::COption; + +/// Internal representation of a mint data. #[repr(C)] -#[derive(Clone, Copy, Default, Pod, Zeroable)] pub struct Mint { /// Optional authority used to mint new tokens. The mint authority may only /// be provided during mint creation. If no mint authority is present /// then the mint has a fixed supply and no further tokens may be /// minted. - pub mint_authority: PodCOption, + pub mint_authority: COption, /// Total supply of tokens. - pub supply: PodU64, + supply: [u8; 8], /// Number of base 10 digits to the right of the decimal place. pub decimals: u8, - /// Is `true` if this structure has been initialized - pub is_initialized: PodBool, + /// Is `true` if this structure has been initialized. + is_initialized: u8, + // Indicates whether the freeze authority is present or not. + //freeze_authority_option: [u8; 4], /// Optional authority to freeze token accounts. - pub freeze_authority: PodCOption, + pub freeze_authority: COption, } impl Mint { - /// Size of the `Mint` account. - pub const LEN: usize = core::mem::size_of::(); + /// 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] + pub fn set_supply(&mut self, supply: u64) { + self.supply = supply.to_le_bytes(); + } + + #[inline] + pub fn supply(&self) -> u64 { + u64::from_le_bytes(self.supply) + } + + #[inline] + 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] + pub fn clear_mint_authority(&mut self) { + self.mint_authority.0[0] = 0; + } + + #[inline] + pub fn set_mint_authority(&mut self, mint_authority: &Pubkey) { + self.mint_authority.0[0] = 1; + self.mint_authority.1 = *mint_authority; + } + + #[inline] + pub fn mint_authority(&self) -> Option<&Pubkey> { + if self.mint_authority.0[0] == 1 { + Some(&self.mint_authority.1) + } else { + None + } + } + + #[inline] + pub fn clear_freeze_authority(&mut self) { + self.freeze_authority.0[0] = 0; + } + + #[inline] + pub fn set_freeze_authority(&mut self, freeze_authority: &Pubkey) { + self.freeze_authority.0[0] = 1; + self.freeze_authority.1 = *freeze_authority; + } + + #[inline] + pub fn freeze_authority(&self) -> Option<&Pubkey> { + if self.freeze_authority.0[0] == 1 { + Some(&self.freeze_authority.1) + } else { + None + } + } } diff --git a/interface/src/state/mod.rs b/interface/src/state/mod.rs index a0f8d9b..04c0342 100644 --- a/interface/src/state/mod.rs +++ b/interface/src/state/mod.rs @@ -1,162 +1,7 @@ -use std::mem::align_of; - -use bytemuck::{Pod, Zeroable}; - pub mod account; +pub mod account_state; pub mod mint; pub mod multisig; -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct PodCOption { - /// Indicates if the option is `Some` or `None`. - tag: [u8; 4], - - /// The value of the option. - value: T, -} - -impl From> for PodCOption { - fn from(value: Option) -> Self { - if align_of::() != 1 { - panic!("PodCOption only supports Pod types with alignment 1"); - } - - match value { - Some(value) => Self { - tag: [1, 0, 0, 0], - value, - }, - None => Self { - tag: [0, 0, 0, 0], - value: T::default(), - }, - } - } -} - -impl PodCOption { - pub const NONE: [u8; 4] = [0, 0, 0, 0]; - - pub const SOME: [u8; 4] = [1, 0, 0, 0]; - - pub fn some(value: T) -> Self { - Self { - tag: [1, 0, 0, 0], - value, - } - } - - /// Returns `true` if the option is a `None` value. - #[inline] - pub fn is_none(&self) -> bool { - self.tag == Self::NONE - } - - /// Returns `true` if the option is a `Some` value. - #[inline] - pub fn is_some(&self) -> bool { - !self.is_none() - } - - /// Returns the contained value as an `Option`. - #[inline] - pub fn get(self) -> Option { - if self.is_none() { - None - } else { - Some(self.value) - } - } - - /// Returns the contained value as an `Option`. - #[inline] - pub fn as_ref(&self) -> Option<&T> { - if self.is_none() { - None - } else { - Some(&self.value) - } - } - - /// Returns the contained value as a mutable `Option`. - #[inline] - pub fn as_mut(&mut self) -> Option<&mut T> { - if self.is_none() { - None - } else { - Some(&mut self.value) - } - } - - #[inline] - pub fn set(&mut self, value: T) { - self.tag = Self::SOME; - self.value = value; - } - - #[inline] - pub fn clear(&mut self) { - self.tag = Self::NONE; - // we don't need to zero the value since the tag - // indicates it is a `None` value - } -} - -/// ## Safety -/// -/// `PodCOption` requires a `Pod` type `T` with alignment of 1. -unsafe impl Pod for PodCOption {} - -/// ## Safety -/// -/// `PodCOption` requires a `Pod` type `T` with alignment of 1. -unsafe impl Zeroable for PodCOption {} - -#[repr(C)] -#[derive(Copy, Clone, Default, Pod, Zeroable)] -pub struct PodBool(u8); - -impl From for PodBool { - fn from(b: bool) -> Self { - Self(b.into()) - } -} - -impl From<&bool> for PodBool { - fn from(b: &bool) -> Self { - Self((*b).into()) - } -} - -impl From<&PodBool> for bool { - fn from(b: &PodBool) -> Self { - b.0 != 0 - } -} - -impl From for bool { - fn from(b: PodBool) -> Self { - b.0 != 0 - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] -#[repr(C)] -pub struct PodU64(pub [u8; 8]); - -impl PodU64 { - pub const fn from_primitive(n: u64) -> Self { - Self(n.to_le_bytes()) - } -} -impl From for PodU64 { - fn from(n: u64) -> Self { - Self::from_primitive(n) - } -} -impl From for u64 { - fn from(pod: PodU64) -> Self { - Self::from_le_bytes(pod.0) - } -} +/// Type alias for fields represented as `COption`. +pub type COption = ([u8; 4], T); diff --git a/interface/src/state/multisig.rs b/interface/src/state/multisig.rs index d34d2e6..362c614 100644 --- a/interface/src/state/multisig.rs +++ b/interface/src/state/multisig.rs @@ -1,32 +1,108 @@ -use bytemuck::{Pod, Zeroable}; -use pinocchio::pubkey::Pubkey; +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; -use super::PodBool; +use crate::program::ID; /// Minimum number of multisignature signers (min N) pub const MIN_SIGNERS: usize = 1; + /// Maximum number of multisignature signers (max N) pub const MAX_SIGNERS: usize = 11; /// Multisignature data. #[repr(C)] -#[derive(Clone, Copy, Default, Pod, Zeroable)] pub struct Multisig { - /// Number of signers required + /// Number of signers required. pub m: u8, - /// Number of valid signers + + /// Number of valid signers. pub n: u8, + /// Is `true` if this structure has been initialized - pub is_initialized: PodBool, + is_initialized: u8, + /// Signer public keys pub signers: [Pubkey; MAX_SIGNERS], } 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) } + + #[inline] + 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 + } } diff --git a/program/Cargo.toml b/program/Cargo.toml index 1e2af78..a2608d7 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -18,7 +18,6 @@ logging = [] test-sbf = [] [dependencies] -bytemuck = { workspace = true } pinocchio = { workspace = true } pinocchio-pubkey = { workspace = true } token-interface = { version = "^0", path = "../interface" } diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index 021227c..f72845e 100644 --- a/program/src/entrypoint.rs +++ b/program/src/entrypoint.rs @@ -1,276 +1,229 @@ -use core::str; - use pinocchio::{ account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, ProgramResult, }; -use crate::processor::{ - amount_to_ui_amount::process_amount_to_ui_amount, - approve::process_approve, - approve_checked::{process_approve_checked, ApproveChecked}, - burn::process_burn, - burn_checked::{process_burn_checked, BurnChecked}, - close_account::process_close_account, - freeze_account::process_freeze_account, - get_account_data_size::process_get_account_data_size, - initialize_account::process_initialize_account, - initialize_account2::process_initialize_account2, - initialize_account3::process_initialize_account3, - initialize_immutable_owner::process_initialize_immutable_owner, - initialize_mint::process_initialize_mint, - initialize_mint2::process_initialize_mint2, - initialize_multisig::process_initialize_multisig, - initialize_multisig2::process_initialize_multisig2, - mint_to::process_mint_to, - mint_to_checked::{process_mint_to_checked, MintToChecked}, - revoke::process_revoke, - set_authority::{process_set_authority, SetAuthority}, - shared::initialize_mint::InitializeMint, - sync_native::process_sync_native, - thaw_account::process_thaw_account, - transfer::process_transfer, - transfer_checked::{process_transfer_checked, TransferChecked}, - ui_amount_to_amount::process_ui_amount_to_amount, -}; +use crate::processor::*; entrypoint!(process_instruction); +/// Process an instruction. +/// +/// The processor of the token program is divided into two parts to reduce the overhead +/// of having a large `match` statement. The first part of the processor handles the +/// most common instructions, while the second part handles the remaining instructions. +/// The rationale is to reduce the overhead of making multiple comparisons for popular +/// instructions. +/// +/// Instructions on the first part of the processor: +/// +/// - `0`: `InitializeMint` +/// - `3`: `Transfer` +/// - `7`: `MintTo` +/// - `9`: `CloseAccount` +/// - `18`: `InitializeAccount3` +/// - `20`: `InitializeMint2` #[inline(always)] pub fn process_instruction( - program_id: &Pubkey, + _program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - match instruction_data.split_first() { + let (discriminator, instruction_data) = instruction_data + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; + + match *discriminator { // 0 - InitializeMint - Some((&0, data)) => { + 0 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMint"); - let instruction = InitializeMint::try_from_bytes(data)?; + process_initialize_mint(accounts, instruction_data, true) + } - process_initialize_mint(accounts, &instruction) + // 3 - Transfer + 3 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: Transfer"); + + process_transfer(accounts, instruction_data) } - // 1 - InitializeAccount - Some((&1, _)) => { + // 7 - MintTo + 7 => { #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount"); + pinocchio::msg!("Instruction: MintTo"); - process_initialize_account(program_id, accounts) + process_mint_to(accounts, instruction_data) } - // 2 - InitializeMultisig - Some((&2, data)) => { + // 9 - CloseAccount + 9 => { #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeMultisig"); + pinocchio::msg!("Instruction: CloseAccount"); + + process_close_account(accounts) + } + // 18 - InitializeAccount3 + 18 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeAccount3"); - let m = data.first().ok_or(ProgramError::InvalidInstructionData)?; + process_initialize_account3(accounts, instruction_data) + } + // 20 - InitializeMint2 + 20 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeMint2"); - process_initialize_multisig(accounts, *m, true) + process_initialize_mint2(accounts, instruction_data) } - // 3 - Transfer - Some((&3, data)) => { + _ => process_remaining_instruction(accounts, instruction_data, *discriminator), + } +} + +/// Process the remaining instructions. +/// +/// This function is called by the `process_instruction` function if the discriminator +/// does not match any of the common instructions. This function is used to reduce the +/// overhead of having a large `match` statement in the `process_instruction` function. +fn process_remaining_instruction( + accounts: &[AccountInfo], + instruction_data: &[u8], + discriminator: u8, +) -> ProgramResult { + match discriminator { + // 1 - InitializeAccount + 1 => { #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: Transfer"); + pinocchio::msg!("Instruction: InitializeAccount"); - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); + process_initialize_account(accounts) + } + // 2 - InitializeMultisig + 2 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeMultisig"); - process_transfer(program_id, accounts, amount) + process_initialize_multisig(accounts, instruction_data) } // 4 - Approve - Some((&4, data)) => { + 4 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Approve"); - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); - - process_approve(program_id, accounts, amount) + process_approve(accounts, instruction_data) } // 5 - Revoke - Some((&5, _)) => { + 5 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Revoke"); - process_revoke(program_id, accounts) + process_revoke(accounts, instruction_data) } // 6 - SetAuthority - Some((&6, data)) => { + 6 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SetAuthority"); - let instruction = SetAuthority::try_from_bytes(data)?; - process_set_authority( - program_id, - accounts, - instruction.authority_type, - instruction.new_authority, - ) - } - // 7 - MintTo - Some((&7, data)) => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: MintTo"); - - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); - - process_mint_to(program_id, accounts, amount) + process_set_authority(accounts, instruction_data) } // 8 - Burn - Some((&8, data)) => { + 8 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Burn"); - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); - - process_burn(program_id, accounts, amount) - } - // 9 - CloseAccount - Some((&9, _)) => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: CloseAccount"); - - process_close_account(program_id, accounts) + process_burn(accounts, instruction_data) } // 10 - FreezeAccount - Some((&10, _)) => { + 10 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: FreezeAccount"); - process_freeze_account(program_id, accounts) + process_freeze_account(accounts) } // 11 - ThawAccount - Some((&11, _)) => { + 11 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ThawAccount"); - process_thaw_account(program_id, accounts) + process_thaw_account(accounts) } // 12 - TransferChecked - Some((&12, data)) => { + 12 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: TransferChecked"); - let args = TransferChecked::try_from_bytes(data)?; - - process_transfer_checked(program_id, accounts, args.amount(), args.decimals()) + process_transfer_checked(accounts, instruction_data) } // 13 - ApproveChecked - Some((&13, data)) => { + 13 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ApproveChecked"); - let args = ApproveChecked::try_from_bytes(data)?; - - process_approve_checked(program_id, accounts, args.amount(), args.decimals()) + process_approve_checked(accounts, instruction_data) } // 14 - MintToChecked - Some((&14, data)) => { + 14 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: MintToChecked"); - let args = MintToChecked::try_from_bytes(data)?; - - process_mint_to_checked(program_id, accounts, args.amount(), args.decimals()) + process_mint_to_checked(accounts, instruction_data) } // 15 - BurnChecked - Some((&15, data)) => { + 15 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: BurnChecked"); - let args = BurnChecked::try_from_bytes(data)?; - - process_burn_checked(program_id, accounts, args.amount(), args.decimals()) + process_burn_checked(accounts, instruction_data) } // 16 - InitializeAccount2 - Some((&16, data)) => { + 16 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeAccount2"); - let owner = unsafe { &*(data.as_ptr() as *const Pubkey) }; - - process_initialize_account2(program_id, accounts, owner) + process_initialize_account2(accounts, instruction_data) } // 17 - SyncNative - Some((&17, _)) => { + 17 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SyncNative"); - process_sync_native(program_id, accounts) - } - // 18 - InitializeAccount3 - Some((&18, data)) => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount3"); - - let owner = unsafe { &*(data.as_ptr() as *const Pubkey) }; - - process_initialize_account3(program_id, accounts, owner) + process_sync_native(accounts) } // 19 - InitializeMultisig2 - Some((&19, data)) => { + 19 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMultisig2"); - let m = data.first().ok_or(ProgramError::InvalidInstructionData)?; - - process_initialize_multisig2(accounts, *m) - } - // 20 - InitializeMint2 - Some((&20, data)) => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeMint2"); - - let instruction = InitializeMint::try_from_bytes(data)?; - - process_initialize_mint2(accounts, &instruction) + process_initialize_multisig2(accounts, instruction_data) } // 21 - GetAccountDataSize - Some((&21, _)) => { + 21 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: GetAccountDataSize"); - process_get_account_data_size(program_id, accounts) + process_get_account_data_size(accounts) } // 22 - InitializeImmutableOwner - Some((&22, _)) => { + 22 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeImmutableOwner"); process_initialize_immutable_owner(accounts) } // 23 - AmountToUiAmount - Some((&23, data)) => { + 23 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: AmountToUiAmount"); - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); - - process_amount_to_ui_amount(program_id, accounts, amount) + process_amount_to_ui_amount(accounts, instruction_data) } // 24 - UiAmountToAmount - Some((&24, data)) => { + 24 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: UiAmountToAmount"); - let ui_amount = - str::from_utf8(data).map_err(|_error| ProgramError::InvalidInstructionData)?; - - process_ui_amount_to_amount(program_id, accounts, ui_amount) + process_ui_amount_to_amount(accounts, instruction_data) } _ => Err(ProgramError::InvalidInstructionData), } diff --git a/program/src/lib.rs b/program/src/lib.rs index 3a174fb..8b786ad 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,4 +1,4 @@ -//! A lighter Token program for SVM. +//! An ERC20-like Token program for the Solana blockchain. mod entrypoint; mod processor; diff --git a/program/src/processor/amount_to_ui_amount.rs b/program/src/processor/amount_to_ui_amount.rs index 432c256..3328fdb 100644 --- a/program/src/processor/amount_to_ui_amount.rs +++ b/program/src/processor/amount_to_ui_amount.rs @@ -1,23 +1,25 @@ use pinocchio::{ - account_info::AccountInfo, program::set_return_data, program_error::ProgramError, - pubkey::Pubkey, ProgramResult, + account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; -use token_interface::{error::TokenError, state::mint::Mint}; +use token_interface::state::mint::Mint; use super::{amount_to_ui_amount_string_trimmed, check_account_owner}; -#[inline(never)] +#[inline(always)] pub fn process_amount_to_ui_amount( - program_id: &Pubkey, accounts: &[AccountInfo], - amount: u64, + instruction_data: &[u8], ) -> ProgramResult { + let amount = u64::from_le_bytes( + instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - check_account_owner(program_id, mint_info)?; + check_account_owner(mint_info)?; - let mint = - bytemuck::try_from_bytes_mut::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| TokenError::InvalidMint)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; let ui_amount = amount_to_ui_amount_string_trimmed(amount, mint.decimals); set_return_data(&ui_amount.into_bytes()); diff --git a/program/src/processor/approve.rs b/program/src/processor/approve.rs index 2f8029d..10c61ed 100644 --- a/program/src/processor/approve.rs +++ b/program/src/processor/approve.rs @@ -1,12 +1,14 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_approve( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, -) -> ProgramResult { - shared::approve::process_approve(program_id, accounts, amount, None) +#[inline(always)] +pub fn process_approve(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let amount = u64::from_le_bytes( + instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::approve::process_approve(accounts, amount, None) } diff --git a/program/src/processor/approve_checked.rs b/program/src/processor/approve_checked.rs index e871b26..580a93d 100644 --- a/program/src/processor/approve_checked.rs +++ b/program/src/processor/approve_checked.rs @@ -1,47 +1,19 @@ -use std::marker::PhantomData; - -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_approve_checked( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, - decimals: u8, -) -> ProgramResult { - shared::approve::process_approve(program_id, accounts, amount, Some(decimals)) -} - -pub struct ApproveChecked<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl ApproveChecked<'_> { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 9 { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(ApproveChecked { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - pub fn amount(&self) -> u64 { - unsafe { - let amount = self.raw as *const u64; - amount.read_unaligned() - } - } - - pub fn decimals(&self) -> u8 { - unsafe { *self.raw.add(8) } - } +#[inline(always)] +pub fn process_approve_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + let amount = u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::approve::process_approve( + accounts, + amount, + Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?), + ) } diff --git a/program/src/processor/burn.rs b/program/src/processor/burn.rs index 42614e1..6ceed84 100644 --- a/program/src/processor/burn.rs +++ b/program/src/processor/burn.rs @@ -1,8 +1,14 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_burn(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult { - shared::burn::process_burn(program_id, accounts, amount, None) +#[inline(always)] +pub fn process_burn(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let amount = u64::from_le_bytes( + instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::burn::process_burn(accounts, amount, None) } diff --git a/program/src/processor/burn_checked.rs b/program/src/processor/burn_checked.rs index a32b753..f9ef06c 100644 --- a/program/src/processor/burn_checked.rs +++ b/program/src/processor/burn_checked.rs @@ -1,47 +1,23 @@ -use std::marker::PhantomData; - -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_burn_checked( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, - decimals: u8, -) -> ProgramResult { - shared::burn::process_burn(program_id, accounts, amount, Some(decimals)) -} - -pub struct BurnChecked<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl BurnChecked<'_> { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 9 { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(BurnChecked { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - pub fn amount(&self) -> u64 { - unsafe { - let amount = self.raw as *const u64; - amount.read_unaligned() - } - } - - pub fn decimals(&self) -> u8 { - unsafe { *self.raw.add(8) } - } +#[inline(always)] +pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + let amount = u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::burn::process_burn( + accounts, + amount, + Some( + *decimals + .first() + .ok_or(ProgramError::InvalidInstructionData)?, + ), + ) } diff --git a/program/src/processor/close_account.rs b/program/src/processor/close_account.rs index 62850ab..31432e7 100644 --- a/program/src/processor/close_account.rs +++ b/program/src/processor/close_account.rs @@ -1,12 +1,10 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{error::TokenError, state::account::Account}; use super::{is_owned_by_system_program_or_incinerator, validate_owner, INCINERATOR_ID}; -#[inline(never)] -pub fn process_close_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { +#[inline(always)] +pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -17,20 +15,18 @@ pub fn process_close_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> P } let source_account = - bytemuck::try_from_bytes::(unsafe { source_account_info.borrow_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; - if source_account.is_native.is_none() && source_account.amount() != 0 { + if !source_account.is_native() && source_account.amount() != 0 { return Err(TokenError::NonNativeHasBalance.into()); } let authority = source_account - .close_authority - .get() - .unwrap_or(source_account.owner); + .close_authority() + .unwrap_or(&source_account.owner); if !is_owned_by_system_program_or_incinerator(source_account_info.owner()) { - validate_owner(program_id, &authority, authority_info, remaining)?; + validate_owner(authority, authority_info, remaining)?; } else if destination_account_info.key() != &INCINERATOR_ID { return Err(ProgramError::InvalidAccountData); } diff --git a/program/src/processor/freeze_account.rs b/program/src/processor/freeze_account.rs index 4348ccf..5637611 100644 --- a/program/src/processor/freeze_account.rs +++ b/program/src/processor/freeze_account.rs @@ -1,8 +1,8 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, ProgramResult}; use super::shared::toggle_account_state::process_toggle_account_state; -#[inline(never)] -pub fn process_freeze_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - process_toggle_account_state(program_id, accounts, true) +#[inline(always)] +pub fn process_freeze_account(accounts: &[AccountInfo]) -> ProgramResult { + process_toggle_account_state(accounts, true) } diff --git a/program/src/processor/get_account_data_size.rs b/program/src/processor/get_account_data_size.rs index ff6e3d1..5e96960 100644 --- a/program/src/processor/get_account_data_size.rs +++ b/program/src/processor/get_account_data_size.rs @@ -1,24 +1,20 @@ use pinocchio::{ - account_info::AccountInfo, program::set_return_data, program_error::ProgramError, - pubkey::Pubkey, ProgramResult, + account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; use token_interface::state::{account::Account, mint::Mint}; use super::check_account_owner; -#[inline(never)] -pub fn process_get_account_data_size( - program_id: &Pubkey, - accounts: &[AccountInfo], -) -> ProgramResult { +#[inline(always)] +pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult { let [mint_info, _remaning @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - check_account_owner(program_id, mint_info)?; + // Make sure the mint is valid. + check_account_owner(mint_info)?; - let _ = bytemuck::try_from_bytes::(unsafe { mint_info.borrow_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let _ = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; set_return_data(&Account::LEN.to_le_bytes()); diff --git a/program/src/processor/initialize_account.rs b/program/src/processor/initialize_account.rs index a046f6a..2c12509 100644 --- a/program/src/processor/initialize_account.rs +++ b/program/src/processor/initialize_account.rs @@ -1,8 +1,8 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_initialize_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - shared::initialize_account::process_initialize_account(program_id, accounts, None, true) +#[inline(always)] +pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult { + shared::initialize_account::process_initialize_account(accounts, None, true) } diff --git a/program/src/processor/initialize_account2.rs b/program/src/processor/initialize_account2.rs index ab2f65d..9200ee0 100644 --- a/program/src/processor/initialize_account2.rs +++ b/program/src/processor/initialize_account2.rs @@ -2,11 +2,11 @@ use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; use super::shared; -#[inline(never)] +#[inline(always)] pub fn process_initialize_account2( - program_id: &Pubkey, accounts: &[AccountInfo], - owner: &Pubkey, + instruction_data: &[u8], ) -> ProgramResult { - shared::initialize_account::process_initialize_account(program_id, accounts, Some(owner), true) + let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) }; + shared::initialize_account::process_initialize_account(accounts, Some(owner), true) } diff --git a/program/src/processor/initialize_account3.rs b/program/src/processor/initialize_account3.rs index 4287389..4eaa7a5 100644 --- a/program/src/processor/initialize_account3.rs +++ b/program/src/processor/initialize_account3.rs @@ -2,11 +2,11 @@ use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; use super::shared; -#[inline(never)] +#[inline(always)] pub fn process_initialize_account3( - program_id: &Pubkey, accounts: &[AccountInfo], - owner: &Pubkey, + instruction_data: &[u8], ) -> ProgramResult { - shared::initialize_account::process_initialize_account(program_id, accounts, Some(owner), false) + let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) }; + shared::initialize_account::process_initialize_account(accounts, Some(owner), false) } diff --git a/program/src/processor/initialize_immutable_owner.rs b/program/src/processor/initialize_immutable_owner.rs index cdbc688..766e5b5 100644 --- a/program/src/processor/initialize_immutable_owner.rs +++ b/program/src/processor/initialize_immutable_owner.rs @@ -1,14 +1,12 @@ use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult}; use token_interface::{error::TokenError, state::account::Account}; -#[inline(never)] +#[inline(always)] pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult { let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let account = bytemuck::try_from_bytes_mut::(unsafe { - token_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let account = + unsafe { Account::from_bytes_mut(token_account_info.borrow_mut_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 4d6de79..752537d 100644 --- a/program/src/processor/initialize_mint.rs +++ b/program/src/processor/initialize_mint.rs @@ -1,8 +1,111 @@ -use pinocchio::{account_info::AccountInfo, ProgramResult}; +use core::{marker::PhantomData, mem::size_of}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::Pubkey, + sysvars::{rent::Rent, Sysvar}, + ProgramResult, +}; +use token_interface::{error::TokenError, state::mint::Mint}; -use super::shared::{self, initialize_mint::InitializeMint}; +#[inline(always)] +pub fn process_initialize_mint( + accounts: &[AccountInfo], + instruction_data: &[u8], + rent_sysvar_account: bool, +) -> ProgramResult { + // Validates the instruction data. -#[inline(never)] -pub fn process_initialize_mint(accounts: &[AccountInfo], args: &InitializeMint) -> ProgramResult { - shared::initialize_mint::process_initialize_mint(accounts, args, true) + let args = InitializeMint::try_from_bytes(instruction_data)?; + + // Validates the accounts. + + let (mint_info, rent_sysvar_info) = if rent_sysvar_account { + let [mint_info, rent_sysvar_info, _remaining @ ..] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + (mint_info, Some(rent_sysvar_info)) + } else { + let [mint_info, _remaining @ ..] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + (mint_info, None) + }; + + let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; + + if mint.is_initialized() { + return Err(TokenError::AlreadyInUse.into()); + } + + // Check rent-exempt status of the mint account. + + let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { + let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; + rent.is_exempt(mint_info.lamports(), size_of::()) + } else { + Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) + }; + + if !is_exempt { + return Err(TokenError::NotRentExempt.into()); + } + + // Initialize the mint. + + mint.set_initialized(true); + mint.set_mint_authority(args.mint_authority()); + mint.decimals = args.decimals(); + + if let Some(freeze_authority) = args.freeze_authority() { + mint.set_freeze_authority(freeze_authority); + } + + Ok(()) +} + +/// Instruction data for the `InitializeMint` instruction. +pub struct InitializeMint<'a> { + raw: *const u8, + + _data: PhantomData<&'a [u8]>, +} + +impl InitializeMint<'_> { + #[inline] + pub fn try_from_bytes(bytes: &[u8]) -> Result { + // The minimum expected size of the instruction data. + // - decimals (1 byte) + // - mint_authority (32 bytes) + // - option + freeze_authority (1 byte + 32 bytes) + if bytes.len() < 34 { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(InitializeMint { + raw: bytes.as_ptr(), + _data: PhantomData, + }) + } + + #[inline] + pub fn decimals(&self) -> u8 { + unsafe { *self.raw } + } + + #[inline] + pub fn mint_authority(&self) -> &Pubkey { + unsafe { &*(self.raw.add(1) as *const Pubkey) } + } + + #[inline] + pub fn freeze_authority(&self) -> Option<&Pubkey> { + unsafe { + if *self.raw.add(33) == 0 { + Option::None + } else { + Option::Some(&*(self.raw.add(34) as *const Pubkey)) + } + } + } } diff --git a/program/src/processor/initialize_mint2.rs b/program/src/processor/initialize_mint2.rs index 5387566..0f1f07d 100644 --- a/program/src/processor/initialize_mint2.rs +++ b/program/src/processor/initialize_mint2.rs @@ -1,8 +1,11 @@ use pinocchio::{account_info::AccountInfo, ProgramResult}; -use super::shared::{self, initialize_mint::InitializeMint}; +use super::initialize_mint::process_initialize_mint; -#[inline(never)] -pub fn process_initialize_mint2(accounts: &[AccountInfo], args: &InitializeMint) -> ProgramResult { - shared::initialize_mint::process_initialize_mint(accounts, args, false) +#[inline(always)] +pub fn process_initialize_mint2( + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + process_initialize_mint(accounts, instruction_data, false) } diff --git a/program/src/processor/initialize_multisig.rs b/program/src/processor/initialize_multisig.rs index 7fddab5..a5f888b 100644 --- a/program/src/processor/initialize_multisig.rs +++ b/program/src/processor/initialize_multisig.rs @@ -1,64 +1,15 @@ -use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; -use token_interface::{error::TokenError, state::multisig::Multisig}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; -#[inline(never)] +use super::shared; + +#[inline(always)] pub fn process_initialize_multisig( accounts: &[AccountInfo], - m: u8, - rent_sysvar_account: bool, + instruction_data: &[u8], ) -> ProgramResult { - let (multisig_info, rent_sysvar_info, remaining) = if rent_sysvar_account { - let [multisig_info, rent_sysvar_info, remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (multisig_info, Some(rent_sysvar_info), remaining) - } else { - let [multisig_info, remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (multisig_info, None, remaining) - }; - - let multisig_info_data_len = multisig_info.data_len(); - - let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; - rent.is_exempt(multisig_info.lamports(), multisig_info_data_len) - } else { - Rent::get()?.is_exempt(multisig_info.lamports(), multisig_info_data_len) - }; - - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } - - let multisig = bytemuck::try_from_bytes_mut::(unsafe { - multisig_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; - - if multisig.is_initialized.into() { - return Err(TokenError::AlreadyInUse.into()); - } - - multisig.m = m; - multisig.n = remaining.len() as u8; - - if !Multisig::is_valid_signer_index(multisig.n as usize) { - return Err(TokenError::InvalidNumberOfProvidedSigners.into()); - } - if !Multisig::is_valid_signer_index(multisig.m as usize) { - return Err(TokenError::InvalidNumberOfRequiredSigners.into()); - } - for (i, signer_info) in remaining.iter().enumerate() { - multisig.signers[i] = *signer_info.key(); - } - multisig.is_initialized = true.into(); + let m = instruction_data + .first() + .ok_or(ProgramError::InvalidInstructionData)?; - Ok(()) + shared::initialize_multisig::process_initialize_multisig(accounts, *m, true) } diff --git a/program/src/processor/initialize_multisig2.rs b/program/src/processor/initialize_multisig2.rs index 9dfbf96..138a91b 100644 --- a/program/src/processor/initialize_multisig2.rs +++ b/program/src/processor/initialize_multisig2.rs @@ -1,8 +1,14 @@ -use pinocchio::{account_info::AccountInfo, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_initialize_multisig2(accounts: &[AccountInfo], m: u8) -> ProgramResult { - shared::initialize_multisig::process_initialize_multisig(accounts, m, false) +#[inline(always)] +pub fn process_initialize_multisig2( + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let m = instruction_data + .first() + .ok_or(ProgramError::InvalidInstructionData)?; + shared::initialize_multisig::process_initialize_multisig(accounts, *m, false) } diff --git a/program/src/processor/mint_to.rs b/program/src/processor/mint_to.rs index 3dadbc1..59db533 100644 --- a/program/src/processor/mint_to.rs +++ b/program/src/processor/mint_to.rs @@ -1,12 +1,14 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_mint_to( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, -) -> ProgramResult { - shared::mint_to::process_mint_to(program_id, accounts, amount, None) +#[inline(always)] +pub fn process_mint_to(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let amount = u64::from_le_bytes( + instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::mint_to::process_mint_to(accounts, amount, None) } diff --git a/program/src/processor/mint_to_checked.rs b/program/src/processor/mint_to_checked.rs index 25684d4..6fb3ae9 100644 --- a/program/src/processor/mint_to_checked.rs +++ b/program/src/processor/mint_to_checked.rs @@ -1,47 +1,20 @@ -use std::marker::PhantomData; - -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_mint_to_checked( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, - decimals: u8, -) -> ProgramResult { - shared::mint_to::process_mint_to(program_id, accounts, amount, Some(decimals)) -} - -pub struct MintToChecked<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl MintToChecked<'_> { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 9 { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(MintToChecked { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - pub fn amount(&self) -> u64 { - unsafe { - let amount = self.raw as *const u64; - amount.read_unaligned() - } - } - - pub fn decimals(&self) -> u8 { - unsafe { *self.raw.add(8) } - } +#[inline(always)] +pub fn process_mint_to_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + + let amount = u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::mint_to::process_mint_to( + accounts, + amount, + Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?), + ) } diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 9527cc7..a8dd6c4 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -34,6 +34,32 @@ pub mod ui_amount_to_amount; // Shared processors. pub mod shared; +pub use amount_to_ui_amount::process_amount_to_ui_amount; +pub use approve::process_approve; +pub use approve_checked::process_approve_checked; +pub use burn::process_burn; +pub use burn_checked::process_burn_checked; +pub use close_account::process_close_account; +pub use freeze_account::process_freeze_account; +pub use get_account_data_size::process_get_account_data_size; +pub use initialize_account::process_initialize_account; +pub use initialize_account2::process_initialize_account2; +pub use initialize_account3::process_initialize_account3; +pub use initialize_immutable_owner::process_initialize_immutable_owner; +pub use initialize_mint::process_initialize_mint; +pub use initialize_mint2::process_initialize_mint2; +pub use initialize_multisig::process_initialize_multisig; +pub use initialize_multisig2::process_initialize_multisig2; +pub use mint_to::process_mint_to; +pub use mint_to_checked::process_mint_to_checked; +pub use revoke::process_revoke; +pub use set_authority::process_set_authority; +pub use sync_native::process_sync_native; +pub use thaw_account::process_thaw_account; +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"); @@ -42,14 +68,14 @@ const INCINERATOR_ID: Pubkey = const SYSTEM_PROGRAM_ID: Pubkey = pinocchio_pubkey::pubkey!("11111111111111111111111111111111"); #[inline(always)] -pub fn is_owned_by_system_program_or_incinerator(owner: &Pubkey) -> bool { - SYSTEM_PROGRAM_ID == *owner || INCINERATOR_ID == *owner +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)] -pub fn check_account_owner(program_id: &Pubkey, account_info: &AccountInfo) -> ProgramResult { - if program_id != account_info.owner() { +fn check_account_owner(account_info: &AccountInfo) -> ProgramResult { + if &crate::ID != account_info.owner() { Err(ProgramError::IncorrectProgramId) } else { Ok(()) @@ -58,8 +84,7 @@ pub fn check_account_owner(program_id: &Pubkey, account_info: &AccountInfo) -> P /// Validates owner(s) are present #[inline(always)] -pub fn validate_owner( - program_id: &Pubkey, +fn validate_owner( expected_owner: &Pubkey, owner_account_info: &AccountInfo, signers: &[AccountInfo], @@ -68,9 +93,8 @@ pub fn validate_owner( return Err(TokenError::OwnerMismatch.into()); } - if owner_account_info.data_len() == Multisig::LEN && program_id != owner_account_info.owner() { - let multisig_data = owner_account_info.try_borrow_data()?; - let multisig = bytemuck::from_bytes::(&multisig_data); + 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 mut num_signers = 0; let mut matched = [false; MAX_SIGNERS]; @@ -99,7 +123,7 @@ pub fn validate_owner( /// Convert a raw amount to its UI representation using the given decimals field /// Excess zeroes or unneeded decimal point are trimmed. #[inline(always)] -pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { +fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { let mut s = amount_to_ui_amount_string(amount, decimals); if decimals > 0 { let zeros_trimmed = s.trim_end_matches('0'); @@ -111,7 +135,7 @@ pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { /// Convert a raw amount to its UI representation (using the decimals field /// defined in its mint) #[inline(always)] -pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { +fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { let decimals = decimals as usize; if decimals > 0 { // Left-pad zeros to decimals + 1, so we at least have an integer zero @@ -126,7 +150,7 @@ pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { /// Try to convert a UI representation of a token amount to its raw amount using /// the given decimals field -pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result { +fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result { let decimals = decimals as usize; let mut parts = ui_amount.split('.'); // splitting a string, even an empty one, will always yield an iterator of at diff --git a/program/src/processor/revoke.rs b/program/src/processor/revoke.rs index c77d8b2..4ed5016 100644 --- a/program/src/processor/revoke.rs +++ b/program/src/processor/revoke.rs @@ -1,29 +1,25 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{error::TokenError, state::account::Account}; use super::validate_owner; -#[inline(never)] -pub fn process_revoke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { +#[inline(always)] +pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> ProgramResult { let [source_account_info, owner_info, remaning @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - let source_account = bytemuck::try_from_bytes_mut::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let source_account = + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); } - validate_owner(program_id, &source_account.owner, owner_info, remaning)?; + validate_owner(&source_account.owner, owner_info, remaning)?; - source_account.delegate.clear(); - source_account.delegated_amount = 0.into(); + source_account.clear_delegate(); + source_account.set_delegated_amount(0); Ok(()) } diff --git a/program/src/processor/set_authority.rs b/program/src/processor/set_authority.rs index f0ed452..4661342 100644 --- a/program/src/processor/set_authority.rs +++ b/program/src/processor/set_authority.rs @@ -1,33 +1,33 @@ +use core::marker::PhantomData; + use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - pubkey::{Pubkey, PUBKEY_BYTES}, - ProgramResult, + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, }; use token_interface::{ error::TokenError, instruction::AuthorityType, - state::{account::Account, mint::Mint, PodCOption}, + state::{account::Account, mint::Mint}, }; use super::validate_owner; -#[inline(never)] -pub fn process_set_authority( - program_id: &Pubkey, - accounts: &[AccountInfo], - authority_type: AuthorityType, - new_authority: Option<&Pubkey>, -) -> ProgramResult { +#[inline(always)] +pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + // Validates the instruction data. + + let args = SetAuthority::try_from_bytes(instruction_data)?; + + let authority_type = args.authority_type()?; + let new_authority = args.new_authority(); + + // Validates the accounts. + let [account_info, authority_info, remaning @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; if account_info.data_len() == Account::LEN { - let account = bytemuck::try_from_bytes_mut::(unsafe { - account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let account = unsafe { Account::from_bytes_mut(account_info.borrow_mut_data_unchecked()) }; if account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -35,7 +35,7 @@ pub fn process_set_authority( match authority_type { AuthorityType::AccountOwner => { - validate_owner(program_id, &account.owner, authority_info, remaning)?; + validate_owner(&account.owner, authority_info, remaning)?; if let Some(authority) = new_authority { account.owner = *authority; @@ -43,50 +43,58 @@ pub fn process_set_authority( return Err(TokenError::InvalidInstruction.into()); } - account.delegate.clear(); - account.delegated_amount = 0.into(); + account.clear_delegate(); + account.set_delegated_amount(0); - if account.is_native.is_some() { - account.close_authority.clear(); + if account.is_native() { + account.clear_close_authority(); } } AuthorityType::CloseAccount => { - let authority = account.close_authority.as_ref().unwrap_or(&account.owner); - validate_owner(program_id, authority, authority_info, remaning)?; - account.close_authority = PodCOption::from(new_authority.copied()); + let authority = account.close_authority().unwrap_or(&account.owner); + validate_owner(authority, authority_info, remaning)?; + + if let Some(authority) = new_authority { + account.set_close_authority(authority); + } else { + account.clear_close_authority(); + } } _ => { return Err(TokenError::AuthorityTypeNotSupported.into()); } } } else if account_info.data_len() == Mint::LEN { - let mint = bytemuck::try_from_bytes_mut::(unsafe { - account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes_mut(account_info.borrow_mut_data_unchecked()) }; match authority_type { AuthorityType::MintTokens => { // Once a mint's supply is fixed, it cannot be undone by setting a new - // mint_authority - let mint_authority = mint - .mint_authority - .as_ref() - .ok_or(TokenError::FixedSupply)?; - - validate_owner(program_id, mint_authority, authority_info, remaning)?; - mint.mint_authority = PodCOption::from(new_authority.copied()); + // mint_authority. + let mint_authority = mint.mint_authority().ok_or(TokenError::FixedSupply)?; + + validate_owner(mint_authority, authority_info, remaning)?; + + if let Some(authority) = new_authority { + mint.set_mint_authority(authority); + } else { + mint.clear_mint_authority(); + } } AuthorityType::FreezeAccount => { // Once a mint's freeze authority is disabled, it cannot be re-enabled by - // setting a new freeze_authority + // setting a new freeze_authority. let freeze_authority = mint - .freeze_authority - .as_ref() + .freeze_authority() .ok_or(TokenError::MintCannotFreeze)?; - validate_owner(program_id, freeze_authority, authority_info, remaning)?; - mint.freeze_authority = PodCOption::from(new_authority.copied()); + validate_owner(freeze_authority, authority_info, remaning)?; + + if let Some(authority) = new_authority { + mint.set_freeze_authority(authority); + } else { + mint.clear_freeze_authority(); + } } _ => { return Err(TokenError::AuthorityTypeNotSupported.into()); @@ -99,35 +107,41 @@ pub fn process_set_authority( Ok(()) } -/// Instruction data for the `InitializeMint` instruction. -pub struct SetAuthority<'a> { - pub authority_type: AuthorityType, +struct SetAuthority<'a> { + raw: *const u8, - /// New authority. - pub new_authority: Option<&'a Pubkey>, + _data: PhantomData<&'a [u8]>, } -impl<'a> SetAuthority<'a> { - pub fn try_from_bytes(data: &'a [u8]) -> Result { - // We expect the data to be at least the size of the u8 (authority_type) - // plus one byte for the authority option. - if data.len() <= 2 { +impl SetAuthority<'_> { + #[inline(always)] + pub fn try_from_bytes(bytes: &[u8]) -> Result { + // The minimum expected size of the instruction data. + // - authority_type (1 byte) + // - option + new_authority (1 byte + 32 bytes) + if bytes.len() < 2 { return Err(ProgramError::InvalidInstructionData); } - let (authority_type, remaining) = data.split_at(1); + Ok(SetAuthority { + raw: bytes.as_ptr(), + _data: PhantomData, + }) + } - let new_authority = match remaining.split_first() { - Some((&0, _)) => None, - Some((&1, pubkey)) if pubkey.len() == PUBKEY_BYTES => { - Some(bytemuck::from_bytes::(pubkey)) - } - _ => return Err(ProgramError::InvalidInstructionData), - }; + #[inline(always)] + pub fn authority_type(&self) -> Result { + unsafe { AuthorityType::from(*self.raw) } + } - Ok(Self { - authority_type: AuthorityType::from(authority_type[0]), - new_authority, - }) + #[inline(always)] + pub fn new_authority(&self) -> Option<&Pubkey> { + unsafe { + if *self.raw.add(1) == 0 { + Option::None + } else { + Option::Some(&*(self.raw.add(2) as *const Pubkey)) + } + } } } diff --git a/program/src/processor/shared/approve.rs b/program/src/processor/shared/approve.rs index 303d943..5715a12 100644 --- a/program/src/processor/shared/approve.rs +++ b/program/src/processor/shared/approve.rs @@ -1,20 +1,20 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, mint::Mint, PodCOption}, + state::{account::Account, mint::Mint}, }; use crate::processor::validate_owner; #[inline(always)] pub fn process_approve( - program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, expected_decimals: Option, ) -> ProgramResult { + // Accounts expected depend on whether we have the mint `decimals` or not; when we have the + // mint `decimals`, we expect the mint account to be present. + let (source_account_info, expected_mint_info, delegate_info, owner_info, remaining) = if let Some(expected_decimals) = expected_decimals { let [source_account_info, expected_mint_info, delegate_info, owner_info, remaning @ ..] = @@ -43,10 +43,10 @@ pub fn process_approve( ) }; - let source_account = bytemuck::try_from_bytes_mut::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + // Validates source account. + + let source_account = + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -57,18 +57,19 @@ pub fn process_approve( return Err(TokenError::MintMismatch.into()); } - let mint = bytemuck::try_from_bytes::(unsafe { mint_info.borrow_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; if expected_decimals != mint.decimals { return Err(TokenError::MintDecimalsMismatch.into()); } } - validate_owner(program_id, &source_account.owner, owner_info, remaining)?; + validate_owner(&source_account.owner, owner_info, remaining)?; + + // Sets the delegate and delegated amount. - source_account.delegate = PodCOption::some(*delegate_info.key()); - source_account.delegated_amount = amount.into(); + source_account.set_delegate(delegate_info.key()); + source_account.set_delegated_amount(amount); Ok(()) } diff --git a/program/src/processor/shared/burn.rs b/program/src/processor/shared/burn.rs index c46d0e3..d23872f 100644 --- a/program/src/processor/shared/burn.rs +++ b/program/src/processor/shared/burn.rs @@ -1,6 +1,4 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, state::{account::Account, mint::Mint}, @@ -12,7 +10,6 @@ use crate::processor::{ #[inline(always)] pub fn process_burn( - program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, expected_decimals: Option, @@ -21,29 +18,24 @@ pub fn process_burn( return Err(ProgramError::NotEnoughAccountKeys); }; - // Safety: There are no conflicting borrows – the source account is only borrowed once. - let source_account = bytemuck::try_from_bytes_mut::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let source_account = + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); } - if source_account.is_native.is_some() { + if source_account.is_native() { return Err(TokenError::NativeNotSupported.into()); } // Ensure the source account has the sufficient amount. This is done before // the value is updated on the account. - let updated_source_amount = u64::from(source_account.amount) + let updated_source_amount = source_account + .amount() .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; - // Safety: There are no conflicting borrows – the mint account is only borrowed once. - let mint = - bytemuck::try_from_bytes_mut::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; if mint_info.key() != &source_account.mint { return Err(TokenError::MintMismatch.into()); @@ -56,36 +48,40 @@ pub fn process_burn( } if !is_owned_by_system_program_or_incinerator(&source_account.owner) { - match source_account.delegate.as_ref() { + match source_account.delegate() { Some(delegate) if authority_info.key() == delegate => { - validate_owner(program_id, delegate, authority_info, remaining)?; + validate_owner(delegate, authority_info, remaining)?; - let delegated_amount = u64::from(source_account.delegated_amount) + let delegated_amount = source_account + .delegated_amount() .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; - source_account.delegated_amount = delegated_amount.into(); + source_account.set_delegated_amount(delegated_amount); if delegated_amount == 0 { - source_account.delegate.clear(); + source_account.clear_delegate(); } } _ => { - validate_owner(program_id, &source_account.owner, authority_info, remaining)?; + validate_owner(&source_account.owner, authority_info, remaining)?; } } } - if amount == 0 { - check_account_owner(program_id, source_account_info)?; - check_account_owner(program_id, mint_info)?; - } + // Updates the source account and mint supply. - source_account.amount = updated_source_amount.into(); + if amount == 0 { + check_account_owner(source_account_info)?; + check_account_owner(mint_info)?; + } else { + source_account.set_amount(updated_source_amount); - let mint_supply = u64::from(mint.supply) - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; - mint.supply = mint_supply.into(); + let mint_supply = mint + .supply() + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + mint.set_supply(mint_supply); + } Ok(()) } diff --git a/program/src/processor/shared/initialize_account.rs b/program/src/processor/shared/initialize_account.rs index 8176ed3..fc66467 100644 --- a/program/src/processor/shared/initialize_account.rs +++ b/program/src/processor/shared/initialize_account.rs @@ -1,3 +1,4 @@ +use core::mem::size_of; use pinocchio::{ account_info::AccountInfo, program_error::ProgramError, @@ -5,26 +6,22 @@ use pinocchio::{ sysvars::{rent::Rent, Sysvar}, ProgramResult, }; -use std::mem::size_of; use token_interface::{ error::TokenError, native_mint::is_native_mint, - state::{ - account::{Account, AccountState}, - mint::Mint, - PodCOption, - }, + state::{account::Account, account_state::AccountState, mint::Mint}, }; use crate::processor::check_account_owner; #[inline(always)] pub fn process_initialize_account( - program_id: &Pubkey, accounts: &[AccountInfo], owner: Option<&Pubkey>, rent_sysvar_account: bool, ) -> ProgramResult { + // Accounts expected depend on whether we have the `rent_sysvar` account or not. + let (new_account_info, mint_info, owner, remaning) = if let Some(owner) = owner { let [new_account_info, mint_info, remaning @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -51,9 +48,9 @@ pub fn process_initialize_account( return Err(TokenError::NotRentExempt.into()); } - let account_data = unsafe { new_account_info.borrow_mut_data_unchecked() }; - let account = bytemuck::try_from_bytes_mut::(account_data) - .map_err(|_error| ProgramError::InvalidAccountData)?; + // Initialize the account. + + let account = unsafe { Account::from_bytes_mut(new_account_info.borrow_mut_data_unchecked()) }; if account.is_initialized() { return Err(TokenError::AlreadyInUse.into()); @@ -62,40 +59,33 @@ pub fn process_initialize_account( let is_native_mint = is_native_mint(mint_info.key()); if !is_native_mint { - check_account_owner(program_id, mint_info)?; + check_account_owner(mint_info)?; - let mint_data = unsafe { mint_info.borrow_data_unchecked() }; - let mint = bytemuck::try_from_bytes::(mint_data) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; - if !bool::from(mint.is_initialized) { + if !mint.is_initialized() { return Err(TokenError::InvalidMint.into()); } } + account.state = AccountState::Initialized; account.mint = *mint_info.key(); account.owner = *owner; - account.close_authority.clear(); - account.delegate.clear(); - account.delegated_amount = 0u64.into(); - account.state = AccountState::Initialized as u8; if is_native_mint { let rent = Rent::get()?; let rent_exempt_reserve = rent.minimum_balance(size_of::()); - account.is_native = PodCOption::from(Some(rent_exempt_reserve.into())); + account.set_native(true); unsafe { - account.amount = new_account_info - .borrow_lamports_unchecked() - .checked_sub(rent_exempt_reserve) - .ok_or(TokenError::Overflow)? - .into() + account.set_amount( + new_account_info + .borrow_lamports_unchecked() + .checked_sub(rent_exempt_reserve) + .ok_or(TokenError::Overflow)?, + ); } - } else { - account.is_native.clear(); - account.amount = 0u64.into(); - }; + } Ok(()) } diff --git a/program/src/processor/shared/initialize_mint.rs b/program/src/processor/shared/initialize_mint.rs deleted file mode 100644 index 845b64b..0000000 --- a/program/src/processor/shared/initialize_mint.rs +++ /dev/null @@ -1,106 +0,0 @@ -use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - pubkey::Pubkey, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; -use std::{marker::PhantomData, mem::size_of}; -use token_interface::{ - error::TokenError, - state::{mint::Mint, PodCOption}, -}; - -#[inline(always)] -pub fn process_initialize_mint( - accounts: &[AccountInfo], - args: &InitializeMint, - rent_sysvar_account: bool, -) -> ProgramResult { - let (mint_info, rent_sysvar_info) = if rent_sysvar_account { - let [mint_info, rent_sysvar_info, _remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (mint_info, Some(rent_sysvar_info)) - } else { - let [mint_info, _remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (mint_info, None) - }; - - let mint = - bytemuck::try_from_bytes_mut::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; - - if mint.is_initialized.into() { - return Err(TokenError::AlreadyInUse.into()); - } - - // Check rent-exempt status of the mint account. - - let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; - rent.is_exempt(mint_info.lamports(), size_of::()) - } else { - Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) - }; - - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } - - // Initialize the mint. - - mint.mint_authority = PodCOption::from(Some(*args.mint_authority())); - mint.decimals = args.decimals(); - mint.is_initialized = true.into(); - - if let Some(freeze_authority) = args.freeze_authority() { - mint.freeze_authority = PodCOption::from(Some(*freeze_authority)); - } - - Ok(()) -} - -/// Instruction data for the `InitializeMint` instruction. -pub struct InitializeMint<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl InitializeMint<'_> { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - // The minimum expected size of the instruction data. - // - decimals (1 byte) - // - mint_authority (32 bytes) - // - option + freeze_authority (1 byte + 32 bytes) - if bytes.len() < 34 { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(InitializeMint { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - pub fn decimals(&self) -> u8 { - unsafe { *self.raw } - } - - pub fn mint_authority(&self) -> &Pubkey { - unsafe { &*(self.raw.add(1) as *const Pubkey) } - } - - pub fn freeze_authority(&self) -> Option<&Pubkey> { - unsafe { - if *self.raw.add(33) == 0 { - Option::None - } else { - Option::Some(&*(self.raw.add(34) as *const Pubkey)) - } - } - } -} diff --git a/program/src/processor/shared/initialize_multisig.rs b/program/src/processor/shared/initialize_multisig.rs index 8db95f1..cae5084 100644 --- a/program/src/processor/shared/initialize_multisig.rs +++ b/program/src/processor/shared/initialize_multisig.rs @@ -12,6 +12,8 @@ pub fn process_initialize_multisig( m: u8, rent_sysvar_account: bool, ) -> ProgramResult { + // Accounts expected depend on whether we have the `rent_sysvar` account or not. + let (multisig_info, rent_sysvar_info, remaining) = if rent_sysvar_account { let [multisig_info, rent_sysvar_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -37,15 +39,14 @@ pub fn process_initialize_multisig( return Err(TokenError::NotRentExempt.into()); } - let multisig = bytemuck::try_from_bytes_mut::(unsafe { - multisig_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let multisig = unsafe { Multisig::from_bytes_mut(multisig_info.borrow_mut_data_unchecked()) }; - if multisig.is_initialized.into() { + if multisig.is_initialized() { return Err(TokenError::AlreadyInUse.into()); } + // Initialize the multisig account. + multisig.m = m; multisig.n = remaining.len() as u8; @@ -55,10 +56,12 @@ pub fn process_initialize_multisig( if !Multisig::is_valid_signer_index(multisig.m as usize) { return Err(TokenError::InvalidNumberOfRequiredSigners.into()); } + for (i, signer_info) in remaining.iter().enumerate() { multisig.signers[i] = *signer_info.key(); } - multisig.is_initialized = true.into(); + + multisig.set_initialized(true); Ok(()) } diff --git a/program/src/processor/shared/mint_to.rs b/program/src/processor/shared/mint_to.rs index 62ca9d7..cc891bd 100644 --- a/program/src/processor/shared/mint_to.rs +++ b/program/src/processor/shared/mint_to.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - native_mint::is_native_mint, state::{account::Account, mint::Mint}, }; @@ -11,7 +8,6 @@ use crate::processor::{check_account_owner, validate_owner}; #[inline(always)] pub fn process_mint_to( - program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, expected_decimals: Option, @@ -20,17 +16,16 @@ pub fn process_mint_to( return Err(ProgramError::NotEnoughAccountKeys); }; - // destination account + // Validates the destination account. - let account_data = unsafe { destination_account_info.borrow_mut_data_unchecked() }; - let destination_account = bytemuck::try_from_bytes_mut::(account_data) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let destination_account = + unsafe { Account::from_bytes_mut(destination_account_info.borrow_mut_data_unchecked()) }; if destination_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); } - if is_native_mint(mint_info.key()) { + if destination_account.is_native() { return Err(TokenError::NativeNotSupported.into()); } @@ -38,9 +33,7 @@ pub fn process_mint_to( return Err(TokenError::MintMismatch.into()); } - let mint_data = unsafe { mint_info.borrow_mut_data_unchecked() }; - let mint = bytemuck::try_from_bytes_mut::(mint_data) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes_mut(mint_info.borrow_mut_data_unchecked()) }; if let Some(expected_decimals) = expected_decimals { if expected_decimals != mint.decimals { @@ -48,25 +41,27 @@ pub fn process_mint_to( } } - match mint.mint_authority.get() { - Some(mint_authority) => validate_owner(program_id, &mint_authority, owner_info, remaining)?, + match mint.mint_authority() { + Some(mint_authority) => validate_owner(mint_authority, owner_info, remaining)?, None => return Err(TokenError::FixedSupply.into()), } if amount == 0 { - check_account_owner(program_id, mint_info)?; - check_account_owner(program_id, destination_account_info)?; + check_account_owner(mint_info)?; + check_account_owner(destination_account_info)?; } - let destination_amount = u64::from(destination_account.amount) + let destination_amount = destination_account + .amount() .checked_add(amount) .ok_or(ProgramError::InvalidAccountData)?; - destination_account.amount = destination_amount.into(); + destination_account.set_amount(destination_amount); - let mint_supply = u64::from(mint.supply) + let mint_supply = mint + .supply() .checked_add(amount) .ok_or(ProgramError::InvalidAccountData)?; - mint.supply = mint_supply.into(); + mint.set_supply(mint_supply); Ok(()) } diff --git a/program/src/processor/shared/mod.rs b/program/src/processor/shared/mod.rs index 634ccab..e55f129 100644 --- a/program/src/processor/shared/mod.rs +++ b/program/src/processor/shared/mod.rs @@ -1,7 +1,11 @@ +//! Shared processor functions. +//! +//! This module contains the shared processor functions that are used by +//! the multiple instruction processors. + pub mod approve; pub mod burn; pub mod initialize_account; -pub mod initialize_mint; pub mod initialize_multisig; pub mod mint_to; pub mod toggle_account_state; diff --git a/program/src/processor/shared/toggle_account_state.rs b/program/src/processor/shared/toggle_account_state.rs index e55ea63..f52b619 100644 --- a/program/src/processor/shared/toggle_account_state.rs +++ b/program/src/processor/shared/toggle_account_state.rs @@ -1,54 +1,42 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{ - account::{Account, AccountState}, - mint::Mint, - }, + state::{account::Account, account_state::AccountState, mint::Mint}, }; use crate::processor::validate_owner; #[inline(always)] -pub fn process_toggle_account_state( - program_id: &Pubkey, - accounts: &[AccountInfo], - freeze: bool, -) -> ProgramResult { +pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> ProgramResult { let [source_account_info, mint_info, authority_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - let source_account = bytemuck::try_from_bytes_mut::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let source_account = + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { return Err(TokenError::InvalidState.into()); } - if source_account.is_native.is_some() { + if source_account.is_native() { return Err(TokenError::NativeNotSupported.into()); } if mint_info.key() != &source_account.mint { return Err(TokenError::MintMismatch.into()); } - let mint = bytemuck::try_from_bytes::(unsafe { mint_info.borrow_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; - match mint.freeze_authority.as_ref() { - Option::Some(authority) => validate_owner(program_id, authority, authority_info, remaining), - Option::None => Err(TokenError::MintCannotFreeze.into()), + match mint.freeze_authority() { + Some(authority) => validate_owner(authority, authority_info, remaining), + None => Err(TokenError::MintCannotFreeze.into()), }?; source_account.state = if freeze { AccountState::Frozen } else { AccountState::Initialized - } as u8; + }; Ok(()) } diff --git a/program/src/processor/shared/transfer.rs b/program/src/processor/shared/transfer.rs index 766011b..5624076 100644 --- a/program/src/processor/shared/transfer.rs +++ b/program/src/processor/shared/transfer.rs @@ -1,22 +1,18 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - native_mint::is_native_mint, - state::{account::Account, mint::Mint, PodCOption}, + state::{account::Account, mint::Mint}, }; use crate::processor::{check_account_owner, validate_owner}; #[inline(always)] pub fn process_transfer( - program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, expected_decimals: Option, ) -> ProgramResult { - // Accounts expected depends on whether we have the mint `decimals` or not; when we have the + // Accounts expected depend on whether we have the mint `decimals` or not; when we have the // mint `decimals`, we expect the mint account to be present. let ( @@ -55,24 +51,21 @@ pub fn process_transfer( // Validates source and destination accounts. - let source_account = bytemuck::try_from_bytes_mut::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let source_account = + unsafe { Account::from_bytes_mut(source_account_info.borrow_mut_data_unchecked()) }; - let destination_account = bytemuck::try_from_bytes_mut::(unsafe { - destination_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let destination_account = + unsafe { Account::from_bytes_mut(destination_account_info.borrow_mut_data_unchecked()) }; if source_account.is_frozen() || destination_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); } - // FEBO: Implicitly validates that the account has enough tokens by calculating the - // remaining amount. The amount is only updated on the account if the transfer + // Implicitly validates that the account has enough tokens by calculating the + // remaining amount - the amount is only updated on the account if the transfer // is successful. - let remaining_amount = u64::from(source_account.amount) + let remaining_amount = source_account + .amount() .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; @@ -87,72 +80,65 @@ pub fn process_transfer( return Err(TokenError::MintMismatch.into()); } - let mint = - bytemuck::try_from_bytes_mut::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; if decimals != mint.decimals { return Err(TokenError::MintDecimalsMismatch.into()); } } - let self_transfer = source_account_info.key() == destination_account_info.key(); + // Comparing whether the `AccountInfo`s "point" to the same acount - this + // is a faster comparison since it just checks the internal raw pointer. + let self_transfer = source_account_info == destination_account_info; // Validates the authority (delegate or owner). - if source_account.delegate.as_ref() == Some(authority_info.key()) { - validate_owner(program_id, authority_info.key(), authority_info, remaning)?; + if source_account.delegate() == Some(authority_info.key()) { + validate_owner(authority_info.key(), authority_info, remaning)?; - let delegated_amount = u64::from(source_account.delegated_amount) + let delegated_amount = source_account + .delegated_amount() .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; if !self_transfer { - source_account.delegated_amount = delegated_amount.into(); + source_account.set_delegated_amount(delegated_amount); if delegated_amount == 0 { - source_account.delegate = PodCOption::from(None); + source_account.clear_delegate(); } } } else { - validate_owner(program_id, &source_account.owner, authority_info, remaning)?; + validate_owner(&source_account.owner, authority_info, remaning)?; } if self_transfer || amount == 0 { - check_account_owner(program_id, source_account_info)?; - check_account_owner(program_id, destination_account_info)?; - - // No need to move tokens around. - return Ok(()); - } + // Validates the token accounts owner since we are not writing + // to these account. + check_account_owner(source_account_info)?; + check_account_owner(destination_account_info)?; - // FEBO: This was moved to the if statement above since we can skip the amount - // manipulation if it is a self-transfer or the amount is zero. - // - // This check MUST occur just before the amounts are manipulated - // to ensure self-transfers are fully validated - /* - if self_transfer { return Ok(()); } - */ // Moves the tokens. - source_account.amount = remaining_amount.into(); + source_account.set_amount(remaining_amount); - let destination_amount = u64::from(destination_account.amount) + let destination_amount = destination_account + .amount() .checked_add(amount) .ok_or(TokenError::Overflow)?; - destination_account.amount = destination_amount.into(); + destination_account.set_amount(destination_amount); - if is_native_mint(&source_account.mint) { - let mut source_lamports = source_account_info.try_borrow_mut_lamports()?; + 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 mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?; + let destination_lamports = + unsafe { destination_account_info.borrow_mut_lamports_unchecked() }; *destination_lamports = destination_lamports .checked_add(amount) .ok_or(TokenError::Overflow)?; diff --git a/program/src/processor/sync_native.rs b/program/src/processor/sync_native.rs index 25b2029..fd90f2b 100644 --- a/program/src/processor/sync_native.rs +++ b/program/src/processor/sync_native.rs @@ -1,31 +1,27 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{error::TokenError, state::account::Account}; use super::check_account_owner; -#[inline(never)] -pub fn process_sync_native(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { +#[inline(always)] +pub fn process_sync_native(accounts: &[AccountInfo]) -> ProgramResult { let native_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - check_account_owner(program_id, native_account_info)?; + check_account_owner(native_account_info)?; - let native_account = bytemuck::try_from_bytes_mut::(unsafe { - native_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; + let native_account = + unsafe { Account::from_bytes_mut(native_account_info.borrow_mut_data_unchecked()) }; - if let Option::Some(rent_exempt_reserve) = native_account.is_native.get() { + if let Option::Some(rent_exempt_reserve) = native_account.native_amount() { let new_amount = native_account_info .lamports() - .checked_sub(u64::from(rent_exempt_reserve)) + .checked_sub(rent_exempt_reserve) .ok_or(TokenError::Overflow)?; - if new_amount < native_account.amount.into() { + if new_amount < native_account.amount() { return Err(TokenError::InvalidState.into()); } - native_account.amount = new_amount.into(); + native_account.set_amount(new_amount); } else { return Err(TokenError::NonNativeNotSupported.into()); } diff --git a/program/src/processor/thaw_account.rs b/program/src/processor/thaw_account.rs index 00ea088..924d3b6 100644 --- a/program/src/processor/thaw_account.rs +++ b/program/src/processor/thaw_account.rs @@ -1,8 +1,8 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, ProgramResult}; use super::shared::toggle_account_state::process_toggle_account_state; -#[inline(never)] -pub fn process_thaw_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - process_toggle_account_state(program_id, accounts, false) +#[inline(always)] +pub fn process_thaw_account(accounts: &[AccountInfo]) -> ProgramResult { + process_toggle_account_state(accounts, false) } diff --git a/program/src/processor/transfer.rs b/program/src/processor/transfer.rs index 254134b..52af330 100644 --- a/program/src/processor/transfer.rs +++ b/program/src/processor/transfer.rs @@ -1,12 +1,14 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] -pub fn process_transfer( - program_id: &Pubkey, - accounts: &[AccountInfo], - amount: u64, -) -> ProgramResult { - shared::transfer::process_transfer(program_id, accounts, amount, None) +#[inline(always)] +pub fn process_transfer(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + let amount = u64::from_le_bytes( + instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::transfer::process_transfer(accounts, amount, None) } diff --git a/program/src/processor/transfer_checked.rs b/program/src/processor/transfer_checked.rs index 1ebcdcd..4c23ee5 100644 --- a/program/src/processor/transfer_checked.rs +++ b/program/src/processor/transfer_checked.rs @@ -1,47 +1,26 @@ -use std::marker::PhantomData; - -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use super::shared; -#[inline(never)] +#[inline(always)] pub fn process_transfer_checked( - program_id: &Pubkey, accounts: &[AccountInfo], - amount: u64, - decimals: u8, + instruction_data: &[u8], ) -> ProgramResult { - shared::transfer::process_transfer(program_id, accounts, amount, Some(decimals)) -} - -pub struct TransferChecked<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl TransferChecked<'_> { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 9 { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(TransferChecked { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - pub fn amount(&self) -> u64 { - unsafe { - let amount = self.raw as *const u64; - amount.read_unaligned() - } - } - - pub fn decimals(&self) -> u8 { - unsafe { *self.raw.add(8) } - } + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + let amount = u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ); + + shared::transfer::process_transfer( + accounts, + amount, + Some( + *decimals + .first() + .ok_or(ProgramError::InvalidInstructionData)?, + ), + ) } diff --git a/program/src/processor/ui_amount_to_amount.rs b/program/src/processor/ui_amount_to_amount.rs index 4f3dbca..5eb0bd7 100644 --- a/program/src/processor/ui_amount_to_amount.rs +++ b/program/src/processor/ui_amount_to_amount.rs @@ -1,23 +1,22 @@ use pinocchio::{ - account_info::AccountInfo, program::set_return_data, program_error::ProgramError, - pubkey::Pubkey, ProgramResult, + account_info::AccountInfo, program::set_return_data, program_error::ProgramError, ProgramResult, }; -use token_interface::{error::TokenError, state::mint::Mint}; +use token_interface::state::mint::Mint; use super::{check_account_owner, try_ui_amount_into_amount}; -#[inline(never)] +#[inline(always)] pub fn process_ui_amount_to_amount( - program_id: &Pubkey, accounts: &[AccountInfo], - ui_amount: &str, + instruction_data: &[u8], ) -> ProgramResult { + let ui_amount = core::str::from_utf8(instruction_data) + .map_err(|_error| ProgramError::InvalidInstructionData)?; + let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - check_account_owner(program_id, mint_info)?; + check_account_owner(mint_info)?; - let mint = - bytemuck::try_from_bytes_mut::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| TokenError::InvalidMint)?; + let mint = unsafe { Mint::from_bytes(mint_info.borrow_data_unchecked()) }; let amount = try_ui_amount_into_amount(ui_amount.to_string(), mint.decimals)?; set_return_data(&amount.to_le_bytes());