Kaynağa Gözat

p-token: Fix review comments (#32)

* Update workspace

* Rename interface crate

* Fix spelling

* [wip]: Fix review comments

* [wip]: More fixes

* A few more fixes

* Fix instruction data validation
Fernando Otero 7 ay önce
ebeveyn
işleme
3daf44899f

+ 31 - 4
Cargo.lock

@@ -1638,6 +1638,12 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.19"
@@ -5666,8 +5672,8 @@ dependencies = [
  "solana-vote",
  "solana-vote-program",
  "static_assertions",
- "strum",
- "strum_macros",
+ "strum 0.24.1",
+ "strum_macros 0.24.3",
  "symlink",
  "tar",
  "tempfile",
@@ -6784,6 +6790,8 @@ version = "0.0.0"
 dependencies = [
  "pinocchio",
  "pinocchio-pubkey",
+ "strum 0.27.1",
+ "strum_macros 0.27.1",
 ]
 
 [[package]]
@@ -6810,22 +6818,41 @@ version = "0.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
 dependencies = [
- "strum_macros",
+ "strum_macros 0.24.3",
 ]
 
+[[package]]
+name = "strum"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
+
 [[package]]
 name = "strum_macros"
 version = "0.24.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
 dependencies = [
- "heck",
+ "heck 0.4.1",
  "proc-macro2",
  "quote",
  "rustversion",
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.96",
+]
+
 [[package]]
 name = "subtle"
 version = "2.6.1"

+ 4 - 0
interface/Cargo.toml

@@ -14,3 +14,7 @@ crate-type = ["rlib"]
 [dependencies]
 pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }
 pinocchio-pubkey = { version = "0.2", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }
+
+[dev-dependencies]
+strum = "0.27"
+strum_macros = "0.27"

+ 159 - 121
interface/src/instruction.rs

@@ -1,13 +1,12 @@
 //! Instruction types.
 
-use pinocchio::{program_error::ProgramError, pubkey::Pubkey};
-
-use crate::error::TokenError;
+use pinocchio::program_error::ProgramError;
 
 /// Instructions supported by the token program.
-#[repr(C)]
+#[repr(u8)]
 #[derive(Clone, Debug, PartialEq)]
-pub enum TokenInstruction<'a> {
+#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
+pub enum TokenInstruction {
     /// Initializes a new mint and optionally deposits all the newly minted
     /// tokens in an account.
     ///
@@ -20,15 +19,14 @@ pub enum TokenInstruction<'a> {
     /// Accounts expected by this instruction:
     ///
     ///   0. `[writable]` The mint to initialize.
-    ///   1. `[]` Rent sysvar
-    InitializeMint {
-        /// Number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-        /// The authority/multisignature to mint tokens.
-        mint_authority: Pubkey,
-        /// The freeze authority/multisignature of the mint.
-        freeze_authority: Option<Pubkey>,
-    },
+    ///   1. `[]` Rent sysvar.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u8` The number of base 10 digits to the right of the decimal place.
+    ///   - `Pubkey` The authority/multisignature to mint tokens.
+    ///   - `Option<Pubkey>` The freeze authority/multisignature of the mint.
+    InitializeMint,
 
     /// Initializes a new account to hold tokens.  If this account is associated
     /// with the native mint then the token balance of the initialized account
@@ -47,7 +45,7 @@ pub enum TokenInstruction<'a> {
     ///   0. `[writable]`  The account to initialize.
     ///   1. `[]` The mint this account will be associated with.
     ///   2. `[]` The new account's owner/multisignature.
-    ///   3. `[]` Rent sysvar
+    ///   3. `[]` Rent sysvar.
     InitializeAccount,
 
     /// Initializes a multisignature account with N provided signers.
@@ -66,13 +64,13 @@ pub enum TokenInstruction<'a> {
     /// Accounts expected by this instruction:
     ///
     ///   0. `[writable]` The multisignature account to initialize.
-    ///   1. `[]` Rent sysvar
+    ///   1. `[]` Rent sysvar.
     ///   2. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`.
-    InitializeMultisig {
-        /// The number of signers (M) required to validate this multisignature
-        /// account.
-        m: u8,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u8` The number of signers (M) required to validate this multisignature account.
+    InitializeMultisig,
 
     /// Transfers tokens from one account to another either directly or via a
     /// delegate.  If this account is associated with the native mint then equal
@@ -91,10 +89,11 @@ pub enum TokenInstruction<'a> {
     ///   1. `[writable]` The destination account.
     ///   2. `[]` The source account's multisignature owner/delegate.
     ///   3. `..+M` `[signer]` M signer accounts.
-    Transfer {
-        /// The amount of tokens to transfer.
-        amount: u64,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens to transfer.
+    Transfer,
 
     /// Approves a delegate.  A delegate is given the authority over tokens on
     /// behalf of the source account's owner.
@@ -110,11 +109,12 @@ pub enum TokenInstruction<'a> {
     ///   0. `[writable]` The source account.
     ///   1. `[]` The delegate.
     ///   2. `[]` The source account's multisignature owner.
-    ///   3. `..+M` `[signer]` M signer accounts
-    Approve {
-        /// The amount of tokens the delegate is approved for.
-        amount: u64,
-    },
+    ///   3. `..+M` `[signer]` M signer accounts.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens the delegate is approved for.
+    Approve,
 
     /// Revokes the delegate's authority.
     ///
@@ -127,7 +127,7 @@ pub enum TokenInstruction<'a> {
     ///   * Multisignature owner
     ///   0. `[writable]` The source account.
     ///   1. `[]` The source account's multisignature owner.
-    ///   2. `..+M` `[signer]` M signer accounts
+    ///   2. `..+M` `[signer]` M signer accounts.
     Revoke,
 
     /// Sets a new authority of a mint or account.
@@ -141,13 +141,13 @@ pub enum TokenInstruction<'a> {
     ///   * Multisignature authority
     ///   0. `[writable]` The mint or account to change the authority of.
     ///   1. `[]` The mint's or account's current multisignature authority.
-    ///   2. `..+M` `[signer]` M signer accounts
-    SetAuthority {
-        /// The type of authority to update.
-        authority_type: AuthorityType,
-        /// The new authority
-        new_authority: Option<Pubkey>,
-    },
+    ///   2. `..+M` `[signer]` M signer accounts.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `AuthorityType` The type of authority to update.
+    ///   - `Option<Pubkey>` The new authority.
+    SetAuthority,
 
     /// Mints new tokens to an account.  The native mint does not support
     /// minting.
@@ -164,10 +164,11 @@ pub enum TokenInstruction<'a> {
     ///   1. `[writable]` The account to mint tokens to.
     ///   2. `[]` The mint's multisignature mint-tokens authority.
     ///   3. `..+M` `[signer]` M signer accounts.
-    MintTo {
-        /// The amount of new tokens to mint.
-        amount: u64,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of new tokens to mint.
+    MintTo,
 
     /// Burns tokens by removing them from an account.  `Burn` does not support
     /// accounts associated with the native mint, use `CloseAccount` instead.
@@ -184,10 +185,11 @@ pub enum TokenInstruction<'a> {
     ///   1. `[writable]` The token mint.
     ///   2. `[]` The account's multisignature owner/delegate.
     ///   3. `..+M` `[signer]` M signer accounts.
-    Burn {
-        /// The amount of tokens to burn.
-        amount: u64,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens to burn.
+    Burn,
 
     /// Close an account by transferring all its SOL to the destination account.
     /// Non-native accounts may only be closed if its token amount is zero.
@@ -262,12 +264,12 @@ pub enum TokenInstruction<'a> {
     ///   2. `[writable]` The destination account.
     ///   3. `[]` The source account's multisignature owner/delegate.
     ///   4. `..+M` `[signer]` M signer accounts.
-    TransferChecked {
-        /// The amount of tokens to transfer.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens to transfer.
+    ///   - `u8` Expected number of base 10 digits to the right of the decimal place.
+    TransferChecked,
 
     /// Approves a delegate.  A delegate is given the authority over tokens on
     /// behalf of the source account's owner.
@@ -289,13 +291,13 @@ pub enum TokenInstruction<'a> {
     ///   1. `[]` The token mint.
     ///   2. `[]` The delegate.
     ///   3. `[]` The source account's multisignature owner.
-    ///   4. `..+M` `[signer]` M signer accounts
-    ApproveChecked {
-        /// The amount of tokens the delegate is approved for.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
+    ///   4. `..+M` `[signer]` M signer accounts.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens the delegate is approved for.
+    ///   - `u8` Expected number of base 10 digits to the right of the decimal place.
+    ApproveChecked,
 
     /// Mints new tokens to an account.  The native mint does not support
     /// minting.
@@ -316,12 +318,12 @@ pub enum TokenInstruction<'a> {
     ///   1. `[writable]` The account to mint tokens to.
     ///   2. `[]` The mint's multisignature mint-tokens authority.
     ///   3. `..+M` `[signer]` M signer accounts.
-    MintToChecked {
-        /// The amount of new tokens to mint.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of new tokens to mint.
+    ///   - `u8` Expected number of base 10 digits to the right of the decimal place.
+    MintToChecked,
 
     /// Burns tokens by removing them from an account.  [`BurnChecked`] does not
     /// support accounts associated with the native mint, use `CloseAccount`
@@ -343,12 +345,12 @@ pub enum TokenInstruction<'a> {
     ///   1. `[writable]` The token mint.
     ///   2. `[]` The account's multisignature owner/delegate.
     ///   3. `..+M` `[signer]` M signer accounts.
-    BurnChecked {
-        /// The amount of tokens to burn.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens to burn.
+    ///   - `u8` Expected number of base 10 digits to the right of the decimal place.
+    BurnChecked,
 
     /// Like [`InitializeAccount`], but the owner pubkey is passed via instruction
     /// data rather than the accounts list. This variant may be preferable
@@ -359,11 +361,12 @@ pub enum TokenInstruction<'a> {
     ///
     ///   0. `[writable]`  The account to initialize.
     ///   1. `[]` The mint this account will be associated with.
-    ///   3. `[]` Rent sysvar
-    InitializeAccount2 {
-        /// The new account's owner/multisignature.
-        owner: Pubkey,
-    },
+    ///   2. `[]` Rent sysvar.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///  - `Pubkey` The new account's owner/multisignature.
+    InitializeAccount2,
 
     /// Given a wrapped / native token account (a token account containing SOL)
     /// updates its amount field based on the account's underlying `lamports`.
@@ -384,10 +387,11 @@ pub enum TokenInstruction<'a> {
     ///
     ///   0. `[writable]`  The account to initialize.
     ///   1. `[]` The mint this account will be associated with.
-    InitializeAccount3 {
-        /// The new account's owner/multisignature.
-        owner: Pubkey,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    /// - `Pubkey` The new account's owner/multisignature.
+    InitializeAccount3,
 
     /// Like [`InitializeMultisig`], but does not require the Rent sysvar to be
     /// provided
@@ -396,11 +400,11 @@ pub enum TokenInstruction<'a> {
     ///
     ///   0. `[writable]` The multisignature account to initialize.
     ///   1. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`.
-    InitializeMultisig2 {
-        /// The number of signers (M) required to validate this multisignature
-        /// account.
-        m: u8,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u8` The number of signers (M) required to validate this multisignature account.
+    InitializeMultisig2,
 
     /// Like [`InitializeMint`], but does not require the Rent sysvar to be
     /// provided
@@ -408,14 +412,13 @@ pub enum TokenInstruction<'a> {
     /// Accounts expected by this instruction:
     ///
     ///   0. `[writable]` The mint to initialize.
-    InitializeMint2 {
-        /// Number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-        /// The authority/multisignature to mint tokens.
-        mint_authority: Pubkey,
-        /// The freeze authority/multisignature of the mint.
-        freeze_authority: Option<Pubkey>,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u8` The number of base 10 digits to the right of the decimal place.
+    ///   - `Pubkey` The authority/multisignature to mint tokens.
+    ///   - `Option<Pubkey>` The freeze authority/multisignature of the mint.
+    InitializeMint2,
 
     /// Gets the required size of an account for the given mint as a
     /// little-endian `u64`.
@@ -425,8 +428,8 @@ pub enum TokenInstruction<'a> {
     ///
     /// Accounts expected by this instruction:
     ///
-    ///   0. `[]` The mint to calculate for
-    GetAccountDataSize, // typically, there's also data, but this program ignores it
+    ///   0. `[]` The mint to calculate for.
+    GetAccountDataSize,
 
     /// Initialize the Immutable Owner extension for the given token account
     ///
@@ -439,9 +442,6 @@ pub enum TokenInstruction<'a> {
     /// Accounts expected by this instruction:
     ///
     ///   0. `[writable]`  The account to initialize.
-    ///
-    /// Data expected by this instruction:
-    ///   None
     InitializeImmutableOwner,
 
     /// Convert an Amount of tokens to a `UiAmount` `string`, using the given
@@ -456,10 +456,11 @@ pub enum TokenInstruction<'a> {
     /// Accounts expected by this instruction:
     ///
     ///   0. `[]` The mint to calculate for
-    AmountToUiAmount {
-        /// The amount of tokens to reformat.
-        amount: u64,
-    },
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `u64` The amount of tokens to reformat.
+    AmountToUiAmount,
 
     /// Convert a `UiAmount` of tokens to a little-endian `u64` raw Amount, using
     /// the given mint. In this version of the program, the mint can only
@@ -470,19 +471,34 @@ pub enum TokenInstruction<'a> {
     ///
     /// Accounts expected by this instruction:
     ///
-    ///   0. `[]` The mint to calculate for
-    UiAmountToAmount {
-        /// The `ui_amount` of tokens to reformat.
-        ui_amount: &'a str,
-    },
+    ///   0. `[]` The mint to calculate for.
+    ///
+    /// Data expected by this instruction:
+    ///
+    ///   - `&str` The `ui_amount` of tokens to reformat.
+    UiAmountToAmount,
     // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
     // latter remains a superset of this instruction set. New variants also need to be added to
     // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
 }
 
+impl TryFrom<u8> for TokenInstruction {
+    type Error = ProgramError;
+
+    #[inline(always)]
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            // SAFETY: `value` is guaranteed to be in the range of the enum variants.
+            0..=24 => Ok(unsafe { core::mem::transmute::<u8, TokenInstruction>(value) }),
+            _ => Err(ProgramError::InvalidInstructionData),
+        }
+    }
+}
+
 /// Specifies the authority type for `SetAuthority` instructions
 #[repr(u8)]
 #[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
 pub enum AuthorityType {
     /// Authority to mint new tokens
     MintTokens,
@@ -494,23 +510,45 @@ pub enum AuthorityType {
     CloseAccount,
 }
 
-impl AuthorityType {
-    pub fn into(&self) -> u8 {
-        match self {
-            AuthorityType::MintTokens => 0,
-            AuthorityType::FreezeAccount => 1,
-            AuthorityType::AccountOwner => 2,
-            AuthorityType::CloseAccount => 3,
+impl TryFrom<u8> for AuthorityType {
+    type Error = ProgramError;
+
+    #[inline(always)]
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            // SAFETY: `value` is guaranteed to be in the range of the enum variants.
+            0..=3 => Ok(unsafe { core::mem::transmute::<u8, AuthorityType>(value) }),
+            _ => Err(ProgramError::InvalidInstructionData),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{AuthorityType, TokenInstruction};
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn test_token_instruction_from_u8_exhaustive() {
+        for variant in TokenInstruction::iter() {
+            let variant_u8 = variant.clone() as u8;
+            assert_eq!(
+                TokenInstruction::from_repr(variant_u8),
+                Some(TokenInstruction::try_from(variant_u8).unwrap())
+            );
+            assert_eq!(TokenInstruction::try_from(variant_u8).unwrap(), variant);
         }
     }
 
-    pub fn from(index: u8) -> Result<Self, ProgramError> {
-        match index {
-            0 => Ok(AuthorityType::MintTokens),
-            1 => Ok(AuthorityType::FreezeAccount),
-            2 => Ok(AuthorityType::AccountOwner),
-            3 => Ok(AuthorityType::CloseAccount),
-            _ => Err(TokenError::InvalidInstruction.into()),
+    #[test]
+    fn test_authority_type_from_u8_exhaustive() {
+        for variant in AuthorityType::iter() {
+            let variant_u8 = variant.clone() as u8;
+            assert_eq!(
+                AuthorityType::from_repr(variant_u8),
+                Some(AuthorityType::try_from(variant_u8).unwrap())
+            );
+            assert_eq!(AuthorityType::try_from(variant_u8).unwrap(), variant);
         }
     }
 }

+ 3 - 3
interface/src/state/account.rs

@@ -1,9 +1,9 @@
 use pinocchio::pubkey::Pubkey;
 
-use super::{account_state::AccountState, COption, Initializable, RawType};
+use super::{account_state::AccountState, COption, Initializable, Transmutable};
 
 /// Incinerator address.
-const INCINERATOR_ID: Pubkey =
+pub const INCINERATOR_ID: Pubkey =
     pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111");
 
 /// System program id.
@@ -140,7 +140,7 @@ impl Account {
     }
 }
 
-impl RawType for Account {
+impl Transmutable for Account {
     const LEN: usize = core::mem::size_of::<Account>();
 }
 

+ 4 - 4
interface/src/state/mint.rs

@@ -1,6 +1,6 @@
 use pinocchio::pubkey::Pubkey;
 
-use super::{COption, Initializable, RawType};
+use super::{COption, Initializable, Transmutable};
 
 /// Internal representation of a mint data.
 #[repr(C)]
@@ -38,8 +38,8 @@ impl Mint {
     }
 
     #[inline(always)]
-    pub fn set_initialized(&mut self, value: bool) {
-        self.is_initialized = value as u8;
+    pub fn set_initialized(&mut self) {
+        self.is_initialized = 1;
     }
 
     #[inline(always)]
@@ -83,7 +83,7 @@ impl Mint {
     }
 }
 
-impl RawType for Mint {
+impl Transmutable for Mint {
     /// The length of the `Mint` account data.
     const LEN: usize = core::mem::size_of::<Mint>();
 }

+ 9 - 7
interface/src/state/mod.rs

@@ -8,11 +8,11 @@ pub mod multisig;
 /// Type alias for fields represented as `COption`.
 pub type COption<T> = ([u8; 4], T);
 
-/// Marker trait for types that can cast from a raw pointer.
+/// Marker trait for types that can be cast from a raw pointer.
 ///
 /// It is up to the type implementing this trait to guarantee that the cast is safe,
-/// i.e., that the fields of the type are well aligned and there are no padding bytes.
-pub trait RawType {
+/// i.e., the fields of the type are well aligned and there are no padding bytes.
+pub trait Transmutable {
     /// The length of the type.
     ///
     /// This must be equal to the size of each individual field in the type.
@@ -31,7 +31,7 @@ pub trait Initializable {
 ///
 /// The caller must ensure that `bytes` contains a valid representation of `T`.
 #[inline(always)]
-pub unsafe fn load<T: Initializable + RawType>(bytes: &[u8]) -> Result<&T, ProgramError> {
+pub unsafe fn load<T: Initializable + Transmutable>(bytes: &[u8]) -> Result<&T, ProgramError> {
     load_unchecked(bytes).and_then(|t: &T| {
         // checks if the data is initialized
         if t.is_initialized() {
@@ -50,7 +50,7 @@ pub unsafe fn load<T: Initializable + RawType>(bytes: &[u8]) -> Result<&T, Progr
 ///
 /// The caller must ensure that `bytes` contains a valid representation of `T`.
 #[inline(always)]
-pub unsafe fn load_unchecked<T: RawType>(bytes: &[u8]) -> Result<&T, ProgramError> {
+pub unsafe fn load_unchecked<T: Transmutable>(bytes: &[u8]) -> Result<&T, ProgramError> {
     if bytes.len() != T::LEN {
         return Err(ProgramError::InvalidAccountData);
     }
@@ -63,7 +63,7 @@ pub unsafe fn load_unchecked<T: RawType>(bytes: &[u8]) -> Result<&T, ProgramErro
 ///
 /// The caller must ensure that `bytes` contains a valid representation of `T`.
 #[inline(always)]
-pub unsafe fn load_mut<T: Initializable + RawType>(
+pub unsafe fn load_mut<T: Initializable + Transmutable>(
     bytes: &mut [u8],
 ) -> Result<&mut T, ProgramError> {
     load_mut_unchecked(bytes).and_then(|t: &mut T| {
@@ -84,7 +84,9 @@ pub unsafe fn load_mut<T: Initializable + RawType>(
 ///
 /// The caller must ensure that `bytes` contains a valid representation of `T`.
 #[inline(always)]
-pub unsafe fn load_mut_unchecked<T: RawType>(bytes: &mut [u8]) -> Result<&mut T, ProgramError> {
+pub unsafe fn load_mut_unchecked<T: Transmutable>(
+    bytes: &mut [u8],
+) -> Result<&mut T, ProgramError> {
     if bytes.len() != T::LEN {
         return Err(ProgramError::InvalidAccountData);
     }

+ 6 - 6
interface/src/state/multisig.rs

@@ -1,12 +1,12 @@
 use pinocchio::pubkey::Pubkey;
 
-use super::{Initializable, RawType};
+use super::{Initializable, Transmutable};
 
 /// Minimum number of multisignature signers (min N)
-pub const MIN_SIGNERS: usize = 1;
+pub const MIN_SIGNERS: u8 = 1;
 
 /// Maximum number of multisignature signers (max N)
-pub const MAX_SIGNERS: usize = 11;
+pub const MAX_SIGNERS: u8 = 11;
 
 /// Multisignature data.
 #[repr(C)]
@@ -21,12 +21,12 @@ pub struct Multisig {
     is_initialized: u8,
 
     /// Signer public keys
-    pub signers: [Pubkey; MAX_SIGNERS],
+    pub signers: [Pubkey; MAX_SIGNERS as usize],
 }
 
 impl Multisig {
     /// Utility function that checks index is between [`MIN_SIGNERS`] and [`MAX_SIGNERS`].
-    pub fn is_valid_signer_index(index: usize) -> bool {
+    pub fn is_valid_signer_index(index: u8) -> bool {
         (MIN_SIGNERS..=MAX_SIGNERS).contains(&index)
     }
 
@@ -36,7 +36,7 @@ impl Multisig {
     }
 }
 
-impl RawType for Multisig {
+impl Transmutable for Multisig {
     /// The length of the `Mint` account data.
     const LEN: usize = core::mem::size_of::<Multisig>();
 }

+ 32 - 30
p-token/src/entrypoint.rs

@@ -2,6 +2,7 @@ use pinocchio::{
     account_info::AccountInfo, default_panic_handler, no_allocator, program_entrypoint,
     program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
+use spl_token_interface::instruction::TokenInstruction;
 
 use crate::processor::*;
 
@@ -36,52 +37,53 @@ pub fn process_instruction(
     let (discriminator, instruction_data) = instruction_data
         .split_first()
         .ok_or(ProgramError::InvalidInstructionData)?;
+    let instruction = TokenInstruction::try_from(*discriminator)?;
 
-    match *discriminator {
+    match instruction {
         // 0 - InitializeMint
-        0 => {
+        TokenInstruction::InitializeMint => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeMint");
 
-            process_initialize_mint(accounts, instruction_data, true)
+            process_initialize_mint(accounts, instruction_data)
         }
 
         // 3 - Transfer
-        3 => {
+        TokenInstruction::Transfer => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: Transfer");
 
             process_transfer(accounts, instruction_data)
         }
         // 7 - MintTo
-        7 => {
+        TokenInstruction::MintTo => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: MintTo");
 
             process_mint_to(accounts, instruction_data)
         }
         // 9 - CloseAccount
-        9 => {
+        TokenInstruction::CloseAccount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: CloseAccount");
 
             process_close_account(accounts)
         }
         // 18 - InitializeAccount3
-        18 => {
+        TokenInstruction::InitializeAccount3 => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeAccount3");
 
             process_initialize_account3(accounts, instruction_data)
         }
         // 20 - InitializeMint2
-        20 => {
+        TokenInstruction::InitializeMint2 => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeMint2");
 
             process_initialize_mint2(accounts, instruction_data)
         }
-        _ => process_remaining_instruction(accounts, instruction_data, *discriminator),
+        _ => process_remaining_instruction(accounts, instruction_data, instruction),
     }
 }
 
@@ -93,137 +95,137 @@ pub fn process_instruction(
 fn process_remaining_instruction(
     accounts: &[AccountInfo],
     instruction_data: &[u8],
-    discriminator: u8,
+    instruction: TokenInstruction,
 ) -> ProgramResult {
-    match discriminator {
+    match instruction {
         // 1 - InitializeAccount
-        1 => {
+        TokenInstruction::InitializeAccount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeAccount");
 
             process_initialize_account(accounts)
         }
         // 2 - InitializeMultisig
-        2 => {
+        TokenInstruction::InitializeMultisig => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeMultisig");
 
             process_initialize_multisig(accounts, instruction_data)
         }
         // 4 - Approve
-        4 => {
+        TokenInstruction::Approve => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: Approve");
 
             process_approve(accounts, instruction_data)
         }
         // 5 - Revoke
-        5 => {
+        TokenInstruction::Revoke => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: Revoke");
 
             process_revoke(accounts, instruction_data)
         }
         // 6 - SetAuthority
-        6 => {
+        TokenInstruction::SetAuthority => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: SetAuthority");
 
             process_set_authority(accounts, instruction_data)
         }
         // 8 - Burn
-        8 => {
+        TokenInstruction::Burn => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: Burn");
 
             process_burn(accounts, instruction_data)
         }
         // 10 - FreezeAccount
-        10 => {
+        TokenInstruction::FreezeAccount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: FreezeAccount");
 
             process_freeze_account(accounts)
         }
         // 11 - ThawAccount
-        11 => {
+        TokenInstruction::ThawAccount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: ThawAccount");
 
             process_thaw_account(accounts)
         }
         // 12 - TransferChecked
-        12 => {
+        TokenInstruction::TransferChecked => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: TransferChecked");
 
             process_transfer_checked(accounts, instruction_data)
         }
         // 13 - ApproveChecked
-        13 => {
+        TokenInstruction::ApproveChecked => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: ApproveChecked");
 
             process_approve_checked(accounts, instruction_data)
         }
         // 14 - MintToChecked
-        14 => {
+        TokenInstruction::MintToChecked => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: MintToChecked");
 
             process_mint_to_checked(accounts, instruction_data)
         }
         // 15 - BurnChecked
-        15 => {
+        TokenInstruction::BurnChecked => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: BurnChecked");
 
             process_burn_checked(accounts, instruction_data)
         }
         // 16 - InitializeAccount2
-        16 => {
+        TokenInstruction::InitializeAccount2 => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeAccount2");
 
             process_initialize_account2(accounts, instruction_data)
         }
         // 17 - SyncNative
-        17 => {
+        TokenInstruction::SyncNative => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: SyncNative");
 
             process_sync_native(accounts)
         }
         // 19 - InitializeMultisig2
-        19 => {
+        TokenInstruction::InitializeMultisig2 => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeMultisig2");
 
             process_initialize_multisig2(accounts, instruction_data)
         }
         // 21 - GetAccountDataSize
-        21 => {
+        TokenInstruction::GetAccountDataSize => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: GetAccountDataSize");
 
             process_get_account_data_size(accounts)
         }
         // 22 - InitializeImmutableOwner
-        22 => {
+        TokenInstruction::InitializeImmutableOwner => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeImmutableOwner");
 
             process_initialize_immutable_owner(accounts)
         }
         // 23 - AmountToUiAmount
-        23 => {
+        TokenInstruction::AmountToUiAmount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: AmountToUiAmount");
 
             process_amount_to_ui_amount(accounts, instruction_data)
         }
         // 24 - UiAmountToAmount
-        24 => {
+        TokenInstruction::UiAmountToAmount => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: UiAmountToAmount");
 

+ 11 - 11
p-token/src/processor/approve_checked.rs

@@ -4,16 +4,16 @@ use super::shared;
 
 #[inline(always)]
 pub fn process_approve_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
-    let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
-    let amount = u64::from_le_bytes(
-        amount
-            .try_into()
-            .map_err(|_error| ProgramError::InvalidInstructionData)?,
-    );
+    // expected u64 (8) + u8 (1)
+    let (amount, decimals) = if instruction_data.len() == 9 {
+        let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
+        (
+            u64::from_le_bytes(amount.try_into().unwrap()),
+            decimals.first().copied(),
+        )
+    } else {
+        return Err(ProgramError::InvalidInstructionData);
+    };
 
-    shared::approve::process_approve(
-        accounts,
-        amount,
-        Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?),
-    )
+    shared::approve::process_approve(accounts, amount, decimals)
 }

+ 3 - 7
p-token/src/processor/burn_checked.rs

@@ -8,16 +8,12 @@ pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -
     let (amount, decimals) = if instruction_data.len() == 9 {
         let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
         (
-            u64::from_le_bytes(
-                amount
-                    .try_into()
-                    .map_err(|_error| ProgramError::InvalidInstructionData)?,
-            ),
-            decimals.first(),
+            u64::from_le_bytes(amount.try_into().unwrap()),
+            decimals.first().copied(),
         )
     } else {
         return Err(ProgramError::InvalidInstructionData);
     };
 
-    shared::burn::process_burn(accounts, amount, decimals.copied())
+    shared::burn::process_burn(accounts, amount, decimals)
 }

+ 5 - 10
p-token/src/processor/close_account.rs

@@ -1,19 +1,14 @@
-use pinocchio::{
-    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
-};
+use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult};
 use spl_token_interface::{
     error::TokenError,
-    state::{account::Account, load},
+    state::{
+        account::{Account, INCINERATOR_ID},
+        load,
+    },
 };
 
 use super::validate_owner;
 
-/// Incinerator (`1nc1nerator11111111111111111111111111111111`) address.
-const INCINERATOR_ID: Pubkey = [
-    0, 51, 144, 114, 141, 52, 17, 96, 121, 189, 201, 17, 191, 255, 0, 219, 212, 77, 46, 205, 204,
-    247, 156, 166, 225, 0, 56, 225, 0, 0, 0, 0,
-];
-
 #[inline(always)]
 pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult {
     let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts

+ 1 - 1
p-token/src/processor/get_account_data_size.rs

@@ -3,7 +3,7 @@ use pinocchio::{
 };
 use spl_token_interface::{
     error::TokenError,
-    state::{account::Account, load, mint::Mint, RawType},
+    state::{account::Account, load, mint::Mint, Transmutable},
 };
 
 use super::check_account_owner;

+ 5 - 12
p-token/src/processor/initialize_account2.rs

@@ -1,8 +1,5 @@
 use pinocchio::{
-    account_info::AccountInfo,
-    program_error::ProgramError,
-    pubkey::{Pubkey, PUBKEY_BYTES},
-    ProgramResult,
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
 use super::shared;
@@ -12,13 +9,9 @@ pub fn process_initialize_account2(
     accounts: &[AccountInfo],
     instruction_data: &[u8],
 ) -> ProgramResult {
-    // SAFETY: validate `instruction_data` length.
-    let owner = unsafe {
-        if instruction_data.len() != PUBKEY_BYTES {
-            return Err(ProgramError::InvalidInstructionData);
-        } else {
-            &*(instruction_data.as_ptr() as *const Pubkey)
-        }
-    };
+    let owner: &Pubkey = instruction_data
+        .try_into()
+        .map_err(|_error| ProgramError::InvalidInstructionData)?;
+
     shared::initialize_account::process_initialize_account(accounts, Some(owner), true)
 }

+ 5 - 12
p-token/src/processor/initialize_account3.rs

@@ -1,8 +1,5 @@
 use pinocchio::{
-    account_info::AccountInfo,
-    program_error::ProgramError,
-    pubkey::{Pubkey, PUBKEY_BYTES},
-    ProgramResult,
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
 use super::shared;
@@ -12,13 +9,9 @@ pub fn process_initialize_account3(
     accounts: &[AccountInfo],
     instruction_data: &[u8],
 ) -> ProgramResult {
-    // SAFETY: validate `instruction_data` length.
-    let owner = unsafe {
-        if instruction_data.len() != PUBKEY_BYTES {
-            return Err(ProgramError::InvalidInstructionData);
-        } else {
-            &*(instruction_data.as_ptr() as *const Pubkey)
-        }
-    };
+    let owner: &Pubkey = instruction_data
+        .try_into()
+        .map_err(|_error| ProgramError::InvalidInstructionData)?;
+
     shared::initialize_account::process_initialize_account(accounts, Some(owner), false)
 }

+ 2 - 2
p-token/src/processor/initialize_immutable_owner.rs

@@ -1,4 +1,4 @@
-use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult};
+use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult};
 use spl_token_interface::{
     error::TokenError,
     state::{account::Account, load_unchecked, Initializable},
@@ -14,6 +14,6 @@ pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramRe
     if account.is_initialized() {
         return Err(TokenError::AlreadyInUse.into());
     }
-    msg!("Please upgrade to SPL Token 2022 for immutable owner support");
+    // Please upgrade to SPL Token 2022 for immutable owner support.
     Ok(())
 }

+ 5 - 117
p-token/src/processor/initialize_mint.rs

@@ -1,120 +1,8 @@
-use core::{marker::PhantomData, mem::size_of};
-use pinocchio::{
-    account_info::AccountInfo,
-    program_error::ProgramError,
-    pubkey::Pubkey,
-    sysvars::{rent::Rent, Sysvar},
-    ProgramResult,
-};
-use spl_token_interface::{
-    error::TokenError,
-    state::{load_mut_unchecked, mint::Mint, Initializable},
-};
+use pinocchio::{account_info::AccountInfo, ProgramResult};
 
-#[inline(always)]
-pub fn process_initialize_mint(
-    accounts: &[AccountInfo],
-    instruction_data: &[u8],
-    rent_sysvar_account: bool,
-) -> ProgramResult {
-    // Validates the instruction data.
-
-    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)
-    };
-
-    // SAFETY: single mutable borrow to `mint_info` account data.
-    let mint = unsafe { load_mut_unchecked::<Mint>(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 {
-        // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are
-        // checked by `from_account_info_unchecked`.
-        let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? };
-        rent.is_exempt(mint_info.lamports(), size_of::<Mint>())
-    } else {
-        Rent::get()?.is_exempt(mint_info.lamports(), size_of::<Mint>())
-    };
-
-    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();
+use super::shared;
 
-    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<InitializeMint, ProgramError> {
-        // 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 || (bytes[33] == 1 && bytes.len() < 66) {
-            return Err(ProgramError::InvalidInstructionData);
-        }
-
-        Ok(InitializeMint {
-            raw: bytes.as_ptr(),
-            _data: PhantomData,
-        })
-    }
-
-    #[inline]
-    pub fn decimals(&self) -> u8 {
-        // SAFETY: the `bytes` length was validated in `try_from_bytes`.
-        unsafe { *self.raw }
-    }
-
-    #[inline]
-    pub fn mint_authority(&self) -> &Pubkey {
-        // SAFETY: the `bytes` length was validated in `try_from_bytes`.
-        unsafe { &*(self.raw.add(1) as *const Pubkey) }
-    }
-
-    #[inline]
-    pub fn freeze_authority(&self) -> Option<&Pubkey> {
-        // SAFETY: the `bytes` length was validated in `try_from_bytes`.
-        unsafe {
-            if *self.raw.add(33) == 0 {
-                Option::None
-            } else {
-                Option::Some(&*(self.raw.add(34) as *const Pubkey))
-            }
-        }
-    }
+#[inline(always)]
+pub fn process_initialize_mint(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
+    shared::initialize_mint::process_initialize_mint(accounts, instruction_data, true)
 }

+ 1 - 1
p-token/src/processor/initialize_mint2.rs

@@ -1,6 +1,6 @@
 use pinocchio::{account_info::AccountInfo, ProgramResult};
 
-use super::initialize_mint::process_initialize_mint;
+use super::shared::initialize_mint::process_initialize_mint;
 
 #[inline(always)]
 pub fn process_initialize_mint2(

+ 3 - 7
p-token/src/processor/mint_to_checked.rs

@@ -8,16 +8,12 @@ pub fn process_mint_to_checked(accounts: &[AccountInfo], instruction_data: &[u8]
     let (amount, decimals) = if instruction_data.len() == 9 {
         let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
         (
-            u64::from_le_bytes(
-                amount
-                    .try_into()
-                    .map_err(|_error| ProgramError::InvalidInstructionData)?,
-            ),
-            decimals.first(),
+            u64::from_le_bytes(amount.try_into().unwrap()),
+            decimals.first().copied(),
         )
     } else {
         return Err(ProgramError::InvalidInstructionData);
     };
 
-    shared::mint_to::process_mint_to(accounts, amount, decimals.copied())
+    shared::mint_to::process_mint_to(accounts, amount, decimals)
 }

+ 20 - 44
p-token/src/processor/mod.rs

@@ -1,11 +1,6 @@
-use core::{
-    cmp::max,
-    mem::MaybeUninit,
-    slice::{from_raw_parts, from_raw_parts_mut},
-    str::from_utf8_unchecked,
-};
+use core::{slice::from_raw_parts, str::from_utf8_unchecked};
 use pinocchio::{
-    account_info::AccountInfo, memory::sol_memcpy, program_error::ProgramError, pubkey::Pubkey,
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, syscalls::sol_memcpy_,
     ProgramResult,
 };
 use spl_token_interface::{
@@ -14,7 +9,7 @@ use spl_token_interface::{
     state::{
         load,
         multisig::{Multisig, MAX_SIGNERS},
-        RawType,
+        Transmutable,
     },
 };
 
@@ -72,9 +67,6 @@ pub use transfer::process_transfer;
 pub use transfer_checked::process_transfer_checked;
 pub use ui_amount_to_amount::process_ui_amount_to_amount;
 
-/// An uninitialized byte.
-const UNINIT_BYTE: MaybeUninit<u8> = MaybeUninit::uninit();
-
 /// Maximum number of digits in a formatted `u64`.
 ///
 /// The maximum number of digits is equal to the maximum number
@@ -95,7 +87,8 @@ fn check_account_owner(account_info: &AccountInfo) -> ProgramResult {
 /// Validates owner(s) are present.
 ///
 /// Note that `owner_account_info` will be immutable borrowed when it represents
-/// a multisig account.
+/// a multisig account, therefore it should not have any mutable borrows when
+/// calling this function.
 #[inline(always)]
 fn validate_owner(
     expected_owner: &Pubkey,
@@ -110,11 +103,13 @@ fn validate_owner(
         && owner_account_info.owner() == &TOKEN_PROGRAM_ID
     {
         // SAFETY: the caller guarantees that there are no mutable borrows of `owner_account_info`
-        // account data and the `load` validates that the account is initialized.
+        // account data and the `load` validates that the account is initialized; additionally,
+        // `Multisig` accounts are only ever loaded in this function, which means that previous
+        // loads will have already failed by the time we get here.
         let multisig = unsafe { load::<Multisig>(owner_account_info.borrow_data_unchecked())? };
 
         let mut num_signers = 0;
-        let mut matched = [false; MAX_SIGNERS];
+        let mut matched = [false; MAX_SIGNERS as usize];
 
         for signer in signers.iter() {
             for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() {
@@ -152,54 +147,35 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result<u64, Progr
 
     // Validates the input.
 
-    let mut length = amount_str.len();
-    let expected_after_decimal_length = max(after_decimal.len(), decimals);
+    let length = amount_str.len();
 
     if (amount_str.is_empty() && after_decimal.is_empty())
         || parts.next().is_some()
         || after_decimal.len() > decimals
-        || (length + expected_after_decimal_length) > MAX_FORMATTED_DIGITS
+        || (length + decimals) > MAX_FORMATTED_DIGITS
     {
         return Err(ProgramError::InvalidArgument);
     }
 
-    let mut digits = [UNINIT_BYTE; MAX_FORMATTED_DIGITS];
-    // SAFETY: `digits` is an array of `MaybeUninit<u8>`, which has the same
-    // memory layout as `u8`.
-    let slice: &mut [u8] =
-        unsafe { from_raw_parts_mut(digits.as_mut_ptr() as *mut _, MAX_FORMATTED_DIGITS) };
+    let mut digits = [b'0'; MAX_FORMATTED_DIGITS];
 
     // SAFETY: the total length of `amount_str` and `after_decimal` is less than
-    // `MAX_DIGITS_U64`.
+    // `MAX_FORMATTED_DIGITS`.
     unsafe {
-        sol_memcpy(slice, amount_str.as_bytes(), length);
+        sol_memcpy_(digits.as_mut_ptr(), amount_str.as_ptr(), length as u64);
 
-        sol_memcpy(
-            &mut slice[length..],
-            after_decimal.as_bytes(),
-            after_decimal.len(),
+        sol_memcpy_(
+            digits.as_mut_ptr().add(length),
+            after_decimal.as_ptr(),
+            after_decimal.len() as u64,
         );
     }
 
-    length += after_decimal.len();
-    let remaining = decimals.saturating_sub(after_decimal.len());
-
-    // SAFETY: `digits` is an array of `MaybeUninit<u8>`, which has the same memory
-    // layout as `u8`.
-    let ptr = unsafe { digits.as_mut_ptr().add(length) };
-
-    for offset in 0..remaining {
-        // SAFETY: `ptr` is within the bounds of `digits`.
-        unsafe {
-            (ptr.add(offset) as *mut u8).write(b'0');
-        }
-    }
-
-    length += remaining;
+    let length = amount_str.len() + decimals;
 
     // SAFETY: `digits` only contains valid UTF-8 bytes.
     unsafe {
-        from_utf8_unchecked(from_raw_parts(digits.as_ptr() as _, length))
+        from_utf8_unchecked(from_raw_parts(digits.as_ptr(), length))
             .parse::<u64>()
             .map_err(|_| ProgramError::InvalidArgument)
     }

+ 17 - 48
p-token/src/processor/set_authority.rs

@@ -1,12 +1,10 @@
-use core::marker::PhantomData;
-
 use pinocchio::{
     account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 use spl_token_interface::{
     error::TokenError,
     instruction::AuthorityType,
-    state::{account::Account, load_mut, mint::Mint, RawType},
+    state::{account::Account, load_mut, mint::Mint, Transmutable},
 };
 
 use super::validate_owner;
@@ -15,10 +13,22 @@ use super::validate_owner;
 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();
+    // SAFETY: The expected size of the instruction data is either 2 or 34 bytes:
+    //   - authority_type (1 byte)
+    //   - option + new_authority (1 byte + 32 bytes)
+    let (authority_type, new_authority) = unsafe {
+        match instruction_data.len() {
+            2 if *instruction_data.get_unchecked(1) == 0 => (
+                AuthorityType::try_from(*instruction_data.get_unchecked(0))?,
+                None,
+            ),
+            34 if *instruction_data.get_unchecked(1) == 1 => (
+                AuthorityType::try_from(*instruction_data.get_unchecked(0))?,
+                Some(&*(instruction_data.as_ptr().add(2) as *const Pubkey)),
+            ),
+            _ => return Err(ProgramError::InvalidInstructionData),
+        }
+    };
 
     // Validates the accounts.
 
@@ -110,44 +120,3 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8])
 
     Ok(())
 }
-
-struct SetAuthority<'a> {
-    raw: *const u8,
-
-    _data: PhantomData<&'a [u8]>,
-}
-
-impl SetAuthority<'_> {
-    #[inline(always)]
-    pub fn try_from_bytes(bytes: &[u8]) -> Result<SetAuthority, ProgramError> {
-        // The minimum expected size of the instruction data.
-        // - authority_type (1 byte)
-        // - option + new_authority (1 byte + 32 bytes)
-        if bytes.len() < 2 || (bytes[1] == 1 && bytes.len() < 34) {
-            return Err(ProgramError::InvalidInstructionData);
-        }
-
-        Ok(SetAuthority {
-            raw: bytes.as_ptr(),
-            _data: PhantomData,
-        })
-    }
-
-    #[inline(always)]
-    pub fn authority_type(&self) -> Result<AuthorityType, ProgramError> {
-        // SAFETY: `bytes` length is validated in `try_from_bytes`.
-        unsafe { AuthorityType::from(*self.raw) }
-    }
-
-    #[inline(always)]
-    pub fn new_authority(&self) -> Option<&Pubkey> {
-        // SAFETY: `bytes` length is validated in `try_from_bytes`.
-        unsafe {
-            if *self.raw.add(1) == 0 {
-                Option::None
-            } else {
-                Option::Some(&*(self.raw.add(2) as *const Pubkey))
-            }
-        }
-    }
-}

+ 6 - 6
p-token/src/processor/shared/burn.rs

@@ -36,7 +36,9 @@ pub fn process_burn(
         .ok_or(TokenError::InsufficientFunds)?;
 
     // SAFETY: single mutable borrow to `mint_info` account data and
-    // `load_mut` validates that the mint is initialized.
+    // `load_mut` validates that the mint is initialized; additionally, an
+    // account cannot be both a token account and a mint, so if duplicates are
+    // passed in, one of them will fail the `load_mut` check.
     let mint = unsafe { load_mut::<Mint>(mint_info.borrow_mut_data_unchecked())? };
 
     if mint_info.key() != &source_account.mint {
@@ -77,11 +79,9 @@ pub fn process_burn(
         check_account_owner(mint_info)?;
     } else {
         source_account.set_amount(updated_source_amount);
-
-        let mint_supply = mint
-            .supply()
-            .checked_sub(amount)
-            .ok_or(TokenError::Overflow)?;
+        // Note: The amount of a token account is always within the range of the
+        // mint supply (`u64`).
+        let mint_supply = mint.supply().checked_sub(amount).unwrap();
         mint.set_supply(mint_supply);
     }
 

+ 5 - 11
p-token/src/processor/shared/initialize_account.rs

@@ -20,7 +20,7 @@ use crate::processor::check_account_owner;
 pub fn process_initialize_account(
     accounts: &[AccountInfo],
     owner: Option<&Pubkey>,
-    rent_sysvar_account: bool,
+    rent_sysvar_account_provided: bool,
 ) -> ProgramResult {
     // Accounts expected depend on whether we have the `rent_sysvar` account or not.
 
@@ -40,7 +40,7 @@ pub fn process_initialize_account(
 
     let new_account_info_data_len = new_account_info.data_len();
 
-    let minimum_balance = if rent_sysvar_account {
+    let minimum_balance = if rent_sysvar_account_provided {
         let rent_sysvar_info = remaining
             .first()
             .ok_or(ProgramError::NotEnoughAccountKeys)?;
@@ -85,15 +85,9 @@ pub fn process_initialize_account(
     if is_native_mint {
         account.set_native(true);
         account.set_native_amount(minimum_balance);
-        // SAFETY: single mutable borrow to `new_account_info` lamports.
-        unsafe {
-            account.set_amount(
-                new_account_info
-                    .borrow_lamports_unchecked()
-                    .checked_sub(minimum_balance)
-                    .ok_or(TokenError::Overflow)?,
-            );
-        }
+        // `new_account_info` lamports are already checked to be greater than or equal
+        // to the minimum balance.
+        account.set_amount(new_account_info.lamports() - minimum_balance);
     }
 
     Ok(())

+ 89 - 0
p-token/src/processor/shared/initialize_mint.rs

@@ -0,0 +1,89 @@
+use core::mem::size_of;
+use pinocchio::{
+    account_info::AccountInfo,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    sysvars::{rent::Rent, Sysvar},
+    ProgramResult,
+};
+use spl_token_interface::{
+    error::TokenError,
+    state::{load_mut_unchecked, mint::Mint, Initializable},
+};
+
+#[inline(always)]
+pub fn process_initialize_mint(
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+    rent_sysvar_account_provided: bool,
+) -> ProgramResult {
+    // Validates the instruction data.
+
+    // SAFETY: The minimum size of the instruction data is either 34 or 66 bytes:
+    //   - decimals (1 byte)
+    //   - mint_authority (32 bytes)
+    //   - option + freeze_authority (1 byte + 32 bytes)
+    let (decimals, mint_authority, freeze_authority) = unsafe {
+        match instruction_data.len() {
+            34 if *instruction_data.get_unchecked(33) == 0 => (
+                *instruction_data.get_unchecked(0),
+                &*(instruction_data.as_ptr().add(1) as *const Pubkey),
+                None,
+            ),
+            66 if *instruction_data.get_unchecked(33) == 1 => (
+                *instruction_data.get_unchecked(0),
+                &*(instruction_data.as_ptr().add(1) as *const Pubkey),
+                Some(&*(instruction_data.as_ptr().add(34) as *const Pubkey)),
+            ),
+            _ => return Err(ProgramError::InvalidInstructionData),
+        }
+    };
+
+    // Validates the accounts.
+
+    let (mint_info, rent_sysvar_info) = if rent_sysvar_account_provided {
+        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)
+    };
+
+    // SAFETY: single mutable borrow to `mint_info` account data.
+    let mint = unsafe { load_mut_unchecked::<Mint>(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 {
+        // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are
+        // checked by `from_account_info_unchecked`.
+        let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? };
+        rent.is_exempt(mint_info.lamports(), size_of::<Mint>())
+    } else {
+        Rent::get()?.is_exempt(mint_info.lamports(), size_of::<Mint>())
+    };
+
+    if !is_exempt {
+        return Err(TokenError::NotRentExempt.into());
+    }
+
+    // Initialize the mint.
+
+    mint.set_initialized();
+    mint.set_mint_authority(mint_authority);
+    mint.decimals = decimals;
+
+    if let Some(freeze_authority) = freeze_authority {
+        mint.set_freeze_authority(freeze_authority);
+    }
+
+    Ok(())
+}

+ 4 - 4
p-token/src/processor/shared/initialize_multisig.rs

@@ -13,11 +13,11 @@ use spl_token_interface::{
 pub fn process_initialize_multisig(
     accounts: &[AccountInfo],
     m: u8,
-    rent_sysvar_account: bool,
+    rent_sysvar_account_provided: 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) = if rent_sysvar_account_provided {
         let [multisig_info, rent_sysvar_info, remaining @ ..] = accounts else {
             return Err(ProgramError::NotEnoughAccountKeys);
         };
@@ -57,10 +57,10 @@ pub fn process_initialize_multisig(
     multisig.m = m;
     multisig.n = remaining.len() as u8;
 
-    if !Multisig::is_valid_signer_index(multisig.n as usize) {
+    if !Multisig::is_valid_signer_index(multisig.n) {
         return Err(TokenError::InvalidNumberOfProvidedSigners.into());
     }
-    if !Multisig::is_valid_signer_index(multisig.m as usize) {
+    if !Multisig::is_valid_signer_index(multisig.m) {
         return Err(TokenError::InvalidNumberOfRequiredSigners.into());
     }
 

+ 5 - 6
p-token/src/processor/shared/mint_to.rs

@@ -51,20 +51,19 @@ pub fn process_mint_to(
     }
 
     if amount == 0 {
+        // Validates the accounts' owner since we are not writing
+        // to these account.
         check_account_owner(mint_info)?;
         check_account_owner(destination_account_info)?;
     } else {
-        let destination_amount = destination_account
-            .amount()
-            .checked_add(amount)
-            .ok_or(TokenError::Overflow)?;
-        destination_account.set_amount(destination_amount);
-
         let mint_supply = mint
             .supply()
             .checked_add(amount)
             .ok_or(TokenError::Overflow)?;
         mint.set_supply(mint_supply);
+
+        // This should not fail since there is no overflow on the mint supply.
+        destination_account.set_amount(destination_account.amount() + amount);
     }
 
     Ok(())

+ 1 - 0
p-token/src/processor/shared/mod.rs

@@ -6,6 +6,7 @@
 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;

+ 3 - 1
p-token/src/processor/shared/toggle_account_state.rs

@@ -28,7 +28,9 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P
     }
 
     // SAFETY: single immutable borrow of `mint_info` account data and
-    // `load` validates that the mint is initialized.
+    // `load` validates that the mint is initialized; additionally, an
+    // account cannot be both a token account and a mint, so if duplicates are
+    // passed in, one of them will fail the `load` check.
     let mint = unsafe { load::<Mint>(mint_info.borrow_data_unchecked())? };
 
     match mint.freeze_authority() {

+ 19 - 8
p-token/src/processor/shared/transfer.rs

@@ -64,6 +64,16 @@ pub fn process_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.
+    //
+    // Note: the logic is partially duplicated for self transfers and transfers
+    // to different accounts to improve CU consumption:
+    //
+    //   - self-transfer: we only need to check that the source account is not frozen
+    //     and has enough tokens.
+    //
+    //   - transfers to different accounts: we need to check that the source and
+    //     destination accounts are not frozen, have the same mint, and the source
+    //     account has enough tokens.
     let remaining_amount = if self_transfer {
         if source_account.is_frozen() {
             return Err(TokenError::AccountFrozen.into());
@@ -75,7 +85,8 @@ pub fn process_transfer(
             .ok_or(TokenError::InsufficientFunds)?
     } else {
         // SAFETY: scoped immutable borrow to `destination_account_info` account data and
-        // `load` validates that the account is initialized.
+        // `load` validates that the account is initialized; additionally, the account
+        // is guaranteed to be different than `source_account_info`.
         let destination_account =
             unsafe { load::<Account>(destination_account_info.borrow_data_unchecked())? };
 
@@ -143,15 +154,14 @@ pub fn process_transfer(
         source_account.set_amount(remaining_amount);
 
         // SAFETY: single mutable borrow to `destination_account_info` account data; the account
-        // is guaranteed to be initialized and different than `source_account_info`.
+        // is guaranteed to be initialized and different than `source_account_info`; it was
+        // also already validated to be a token account.
         let destination_account = unsafe {
             load_mut_unchecked::<Account>(destination_account_info.borrow_mut_data_unchecked())?
         };
-        let destination_amount = destination_account
-            .amount()
-            .checked_add(amount)
-            .ok_or(TokenError::Overflow)?;
-        destination_account.set_amount(destination_amount);
+        // Note: The amount of a token account is always within the range of the
+        // mint supply (`u64`).
+        destination_account.set_amount(destination_account.amount() + amount);
 
         if source_account.is_native() {
             // SAFETY: single mutable borrow to `source_account_info` lamports.
@@ -160,7 +170,8 @@ pub fn process_transfer(
                 .checked_sub(amount)
                 .ok_or(TokenError::Overflow)?;
 
-            // SAFETY: single mutable borrow to `destination_account_info` lamports.
+            // SAFETY: single mutable borrow to `destination_account_info` lamports; the account
+            // is already validated to be different from `source_account_info`.
             let destination_lamports =
                 unsafe { destination_account_info.borrow_mut_lamports_unchecked() };
             *destination_lamports = destination_lamports

+ 3 - 7
p-token/src/processor/transfer_checked.rs

@@ -11,16 +11,12 @@ pub fn process_transfer_checked(
     let (amount, decimals) = if instruction_data.len() == 9 {
         let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
         (
-            u64::from_le_bytes(
-                amount
-                    .try_into()
-                    .map_err(|_error| ProgramError::InvalidInstructionData)?,
-            ),
-            decimals.first(),
+            u64::from_le_bytes(amount.try_into().unwrap()),
+            decimals.first().copied(),
         )
     } else {
         return Err(ProgramError::InvalidInstructionData);
     };
 
-    shared::transfer::process_transfer(accounts, amount, decimals.copied())
+    shared::transfer::process_transfer(accounts, amount, decimals)
 }