Selaa lähdekoodia

Refactor shared processors

febo 10 kuukautta sitten
vanhempi
sitoutus
e579147891

+ 8 - 7
p-token/src/entrypoint.rs

@@ -18,7 +18,7 @@ use crate::processor::{
     initialize_account2::process_initialize_account2,
     initialize_account3::process_initialize_account3,
     initialize_immutable_owner::process_initialize_immutable_owner,
-    initialize_mint::{process_initialize_mint, InitializeMint},
+    initialize_mint::process_initialize_mint,
     initialize_mint2::process_initialize_mint2,
     initialize_multisig::process_initialize_multisig,
     initialize_multisig2::process_initialize_multisig2,
@@ -26,6 +26,7 @@ use crate::processor::{
     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,
@@ -49,14 +50,14 @@ pub fn process_instruction(
 
             let instruction = InitializeMint::try_from_bytes(data)?;
 
-            process_initialize_mint(accounts, &instruction, true)
+            process_initialize_mint(accounts, &instruction)
         }
         // 1 - InitializeAccount
         Some((&1, _)) => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: InitializeAccount");
 
-            process_initialize_account(program_id, accounts, None, true)
+            process_initialize_account(program_id, accounts)
         }
         // 2 - InitializeMultisig
         Some((&2, data)) => {
@@ -77,7 +78,7 @@ pub fn process_instruction(
                     .map_err(|_error| ProgramError::InvalidInstructionData)?,
             );
 
-            process_transfer(program_id, accounts, amount, None)
+            process_transfer(program_id, accounts, amount)
         }
         // 4 - Approve
         Some((&4, data)) => {
@@ -89,7 +90,7 @@ pub fn process_instruction(
                     .map_err(|_error| ProgramError::InvalidInstructionData)?,
             );
 
-            process_approve(program_id, accounts, amount, None)
+            process_approve(program_id, accounts, amount)
         }
         // 5 - Revoke
         Some((&5, _)) => {
@@ -121,7 +122,7 @@ pub fn process_instruction(
                     .map_err(|_error| ProgramError::InvalidInstructionData)?,
             );
 
-            process_mint_to(program_id, accounts, amount, None)
+            process_mint_to(program_id, accounts, amount)
         }
         // 8 - Burn
         Some((&8, data)) => {
@@ -133,7 +134,7 @@ pub fn process_instruction(
                     .map_err(|_error| ProgramError::InvalidInstructionData)?,
             );
 
-            process_burn(program_id, accounts, amount, None)
+            process_burn(program_id, accounts, amount)
         }
         // 9 - CloseAccount
         Some((&9, _)) => {

+ 3 - 65
p-token/src/processor/approve.rs

@@ -1,73 +1,11 @@
-use pinocchio::{
-    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
-};
-use token_interface::{
-    error::TokenError,
-    state::{account::Account, mint::Mint, PodCOption},
-};
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::validate_owner;
+use super::shared;
 
 pub fn process_approve(
     program_id: &Pubkey,
     accounts: &[AccountInfo],
     amount: u64,
-    expected_decimals: Option<u8>,
 ) -> ProgramResult {
-    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 @ ..] =
-                accounts
-            else {
-                return Err(ProgramError::NotEnoughAccountKeys);
-            };
-
-            (
-                source_account_info,
-                Some((expected_mint_info, expected_decimals)),
-                delegate_info,
-                owner_info,
-                remaning,
-            )
-        } else {
-            let [source_account_info, delegate_info, owner_info, remaning @ ..] = accounts else {
-                return Err(ProgramError::NotEnoughAccountKeys);
-            };
-            (
-                source_account_info,
-                None,
-                delegate_info,
-                owner_info,
-                remaning,
-            )
-        };
-
-    let source_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
-        source_account_info.borrow_mut_data_unchecked()
-    })
-    .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if source_account.is_frozen() {
-        return Err(TokenError::AccountFrozen.into());
-    }
-
-    if let Some((mint_info, expected_decimals)) = expected_mint_info {
-        if mint_info.key() != &source_account.mint {
-            return Err(TokenError::MintMismatch.into());
-        }
-
-        let mint = bytemuck::try_from_bytes::<Mint>(unsafe { mint_info.borrow_data_unchecked() })
-            .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-        if expected_decimals != mint.decimals {
-            return Err(TokenError::MintDecimalsMismatch.into());
-        }
-    }
-
-    validate_owner(program_id, &source_account.owner, owner_info, remaining)?;
-
-    source_account.delegate = PodCOption::some(*delegate_info.key());
-    source_account.delegated_amount = amount.into();
-
-    Ok(())
+    shared::approve::process_approve(program_id, accounts, amount, None)
 }

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

@@ -4,7 +4,7 @@ use pinocchio::{
     account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
-use super::approve::process_approve;
+use super::shared;
 
 #[inline(always)]
 pub fn process_approve_checked(
@@ -13,7 +13,7 @@ pub fn process_approve_checked(
     amount: u64,
     decimals: u8,
 ) -> ProgramResult {
-    process_approve(program_id, accounts, amount, Some(decimals))
+    shared::approve::process_approve(program_id, accounts, amount, Some(decimals))
 }
 
 pub struct ApproveChecked<'a> {

+ 4 - 86
p-token/src/processor/burn.rs

@@ -1,89 +1,7 @@
-use pinocchio::{
-    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
-};
-use token_interface::{
-    error::TokenError,
-    state::{account::Account, mint::Mint},
-};
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::{check_account_owner, is_owned_by_system_program_or_incinerator, validate_owner};
+use super::shared;
 
-/// Processes a [Burn](enum.TokenInstruction.html) instruction.
-pub fn process_burn(
-    program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    amount: u64,
-    expected_decimals: Option<u8>,
-) -> ProgramResult {
-    let [source_account_info, mint_info, authority_info, remaining @ ..] = accounts else {
-        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::<Account>(unsafe {
-        source_account_info.borrow_mut_data_unchecked()
-    })
-    .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if source_account.is_frozen() {
-        return Err(TokenError::AccountFrozen.into());
-    }
-    if source_account.is_native.is_some() {
-        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)
-        .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::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
-            .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if mint_info.key() != &source_account.mint {
-        return Err(TokenError::MintMismatch.into());
-    }
-
-    if let Some(expected_decimals) = expected_decimals {
-        if expected_decimals != mint.decimals {
-            return Err(TokenError::MintDecimalsMismatch.into());
-        }
-    }
-
-    if !is_owned_by_system_program_or_incinerator(&source_account.owner) {
-        match source_account.delegate.as_ref() {
-            Some(delegate) if authority_info.key() == delegate => {
-                validate_owner(program_id, delegate, authority_info, remaining)?;
-
-                let delegated_amount = u64::from(source_account.delegated_amount)
-                    .checked_sub(amount)
-                    .ok_or(TokenError::InsufficientFunds)?;
-                source_account.delegated_amount = delegated_amount.into();
-
-                if delegated_amount == 0 {
-                    source_account.delegate.clear();
-                }
-            }
-            _ => {
-                validate_owner(program_id, &source_account.owner, authority_info, remaining)?;
-            }
-        }
-    }
-
-    if amount == 0 {
-        check_account_owner(program_id, source_account_info)?;
-        check_account_owner(program_id, mint_info)?;
-    }
-
-    source_account.amount = updated_source_amount.into();
-
-    let mint_supply = u64::from(mint.supply)
-        .checked_sub(amount)
-        .ok_or(TokenError::Overflow)?;
-    mint.supply = mint_supply.into();
-
-    Ok(())
+pub fn process_burn(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult {
+    shared::burn::process_burn(program_id, accounts, amount, None)
 }

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

@@ -4,7 +4,7 @@ use pinocchio::{
     account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
-use super::burn::process_burn;
+use super::shared;
 
 #[inline(always)]
 pub fn process_burn_checked(
@@ -13,7 +13,7 @@ pub fn process_burn_checked(
     amount: u64,
     decimals: u8,
 ) -> ProgramResult {
-    process_burn(program_id, accounts, amount, Some(decimals))
+    shared::burn::process_burn(program_id, accounts, amount, Some(decimals))
 }
 
 pub struct BurnChecked<'a> {

+ 1 - 2
p-token/src/processor/freeze_account.rs

@@ -1,8 +1,7 @@
 use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::toggle_account_state::process_toggle_account_state;
+use super::shared::toggle_account_state::process_toggle_account_state;
 
-#[inline(always)]
 pub fn process_freeze_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
     process_toggle_account_state(program_id, accounts, true)
 }

+ 4 - 97
p-token/src/processor/initialize_account.rs

@@ -1,100 +1,7 @@
-use pinocchio::{
-    account_info::AccountInfo,
-    program_error::ProgramError,
-    pubkey::Pubkey,
-    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,
-    },
-};
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::check_account_owner;
+use super::shared;
 
-pub fn process_initialize_account(
-    program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    owner: Option<&Pubkey>,
-    rent_sysvar_account: bool,
-) -> ProgramResult {
-    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);
-        };
-        (new_account_info, mint_info, owner, remaning)
-    } else {
-        let [new_account_info, mint_info, owner_info, remaning @ ..] = accounts else {
-            return Err(ProgramError::NotEnoughAccountKeys);
-        };
-        (new_account_info, mint_info, owner_info.key(), remaning)
-    };
-
-    // Check rent-exempt status of the token account.
-
-    let is_exempt = if rent_sysvar_account {
-        let rent_sysvar_info = remaning.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
-        let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) };
-        rent.is_exempt(new_account_info.lamports(), size_of::<Account>())
-    } else {
-        Rent::get()?.is_exempt(new_account_info.lamports(), size_of::<Account>())
-    };
-
-    if !is_exempt {
-        return Err(TokenError::NotRentExempt.into());
-    }
-
-    let account_data = unsafe { new_account_info.borrow_mut_data_unchecked() };
-    let account = bytemuck::try_from_bytes_mut::<Account>(account_data)
-        .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if account.is_initialized() {
-        return Err(TokenError::AlreadyInUse.into());
-    }
-
-    let is_native_mint = is_native_mint(mint_info.key());
-
-    if !is_native_mint {
-        check_account_owner(program_id, mint_info)?;
-
-        let mint_data = unsafe { mint_info.borrow_data_unchecked() };
-        let mint = bytemuck::try_from_bytes::<Mint>(mint_data)
-            .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-        if !bool::from(mint.is_initialized) {
-            return Err(TokenError::InvalidMint.into());
-        }
-    }
-
-    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>());
-
-        account.is_native = PodCOption::from(Some(rent_exempt_reserve.into()));
-        unsafe {
-            account.amount = new_account_info
-                .borrow_lamports_unchecked()
-                .checked_sub(rent_exempt_reserve)
-                .ok_or(TokenError::Overflow)?
-                .into()
-        }
-    } else {
-        account.is_native.clear();
-        account.amount = 0u64.into();
-    };
-
-    Ok(())
+pub fn process_initialize_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+    shared::initialize_account::process_initialize_account(program_id, accounts, None, true)
 }

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

@@ -1,6 +1,6 @@
 use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::initialize_account::process_initialize_account;
+use super::shared;
 
 #[inline(always)]
 pub fn process_initialize_account2(
@@ -8,5 +8,5 @@ pub fn process_initialize_account2(
     accounts: &[AccountInfo],
     owner: &Pubkey,
 ) -> ProgramResult {
-    process_initialize_account(program_id, accounts, Some(owner), true)
+    shared::initialize_account::process_initialize_account(program_id, accounts, Some(owner), true)
 }

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

@@ -1,6 +1,6 @@
 use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::initialize_account::process_initialize_account;
+use super::shared;
 
 #[inline(always)]
 pub fn process_initialize_account3(
@@ -8,5 +8,5 @@ pub fn process_initialize_account3(
     accounts: &[AccountInfo],
     owner: &Pubkey,
 ) -> ProgramResult {
-    process_initialize_account(program_id, accounts, Some(owner), false)
+    shared::initialize_account::process_initialize_account(program_id, accounts, Some(owner), false)
 }

+ 4 - 148
p-token/src/processor/initialize_mint.rs

@@ -1,151 +1,7 @@
-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},
-};
+use pinocchio::{account_info::AccountInfo, ProgramResult};
 
-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)
-    };
+use super::shared::{self, initialize_mint::InitializeMint};
 
-    let mint =
-        bytemuck::try_from_bytes_mut::<Mint>(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::<Mint>())
-    } else {
-        Rent::get()?.is_exempt(mint_info.lamports(), size_of::<Mint>())
-    };
-
-    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> {
-    pub data: &'a MintData,
-
-    /// The freeze authority/multisignature of the mint.
-    pub freeze_authority: Option<&'a Pubkey>,
-}
-
-impl<'a> InitializeMint<'a> {
-    pub fn try_from_bytes(data: &'a [u8]) -> Result<Self, ProgramError> {
-        // We expect the data to be at least the size of the MintInput struct
-        // plus one byte for the freeze_authority option.
-        if data.len() <= size_of::<MintData>() {
-            return Err(ProgramError::InvalidInstructionData);
-        }
-
-        let (data, remaining) = data.split_at(size_of::<MintData>());
-        let data = bytemuck::from_bytes::<MintData>(data);
-
-        let freeze_authority = match remaining.split_first() {
-            Some((&0, _)) => None,
-            Some((&1, pubkey)) if pubkey.len() == PUBKEY_BYTES => {
-                Some(bytemuck::from_bytes::<Pubkey>(pubkey))
-            }
-            _ => return Err(ProgramError::InvalidInstructionData),
-        };
-
-        Ok(Self {
-            data,
-            freeze_authority,
-        })
-    }
-}
-
-/// Base information for the mint.
-#[repr(C)]
-#[derive(Clone, Copy, Default, Pod, Zeroable)]
-pub struct MintData {
-    /// Number of base 10 digits to the right of the decimal place.
-    pub decimals: u8,
-
-    /// The authority/multisignature to mint tokens.
-    pub mint_authority: Pubkey,
-}
-*/
-/// Instruction data for the `InitializeMint2` instruction.
-pub struct InitializeMint<'a> {
-    raw: *const u8,
-
-    _data: PhantomData<&'a [u8]>,
-}
-
-impl InitializeMint<'_> {
-    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 {
-            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))
-            }
-        }
-    }
+pub fn process_initialize_mint(accounts: &[AccountInfo], args: &InitializeMint) -> ProgramResult {
+    shared::initialize_mint::process_initialize_mint(accounts, args, true)
 }

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

@@ -1,8 +1,8 @@
 use pinocchio::{account_info::AccountInfo, ProgramResult};
 
-use super::initialize_mint::{process_initialize_mint, InitializeMint};
+use super::shared::{self, initialize_mint::InitializeMint};
 
 #[inline(always)]
 pub fn process_initialize_mint2(accounts: &[AccountInfo], args: &InitializeMint) -> ProgramResult {
-    process_initialize_mint(accounts, args, false)
+    shared::initialize_mint::process_initialize_mint(accounts, args, false)
 }

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

@@ -1,8 +1,8 @@
 use pinocchio::{account_info::AccountInfo, ProgramResult};
 
-use super::initialize_multisig::process_initialize_multisig;
+use super::shared;
 
 #[inline(always)]
 pub fn process_initialize_multisig2(accounts: &[AccountInfo], m: u8) -> ProgramResult {
-    process_initialize_multisig(accounts, m, false)
+    shared::initialize_multisig::process_initialize_multisig(accounts, m, false)
 }

+ 3 - 63
p-token/src/processor/mint_to.rs

@@ -1,71 +1,11 @@
-use pinocchio::{
-    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
-};
-use token_interface::{
-    error::TokenError,
-    native_mint::is_native_mint,
-    state::{account::Account, mint::Mint},
-};
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::{check_account_owner, validate_owner};
+use super::shared;
 
 pub fn process_mint_to(
     program_id: &Pubkey,
     accounts: &[AccountInfo],
     amount: u64,
-    expected_decimals: Option<u8>,
 ) -> ProgramResult {
-    let [mint_info, destination_account_info, owner_info, remaining @ ..] = accounts else {
-        return Err(ProgramError::NotEnoughAccountKeys);
-    };
-
-    // destination account
-
-    let account_data = unsafe { destination_account_info.borrow_mut_data_unchecked() };
-    let destination_account = bytemuck::try_from_bytes_mut::<Account>(account_data)
-        .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if destination_account.is_frozen() {
-        return Err(TokenError::AccountFrozen.into());
-    }
-
-    if is_native_mint(mint_info.key()) {
-        return Err(TokenError::NativeNotSupported.into());
-    }
-
-    if mint_info.key() != &destination_account.mint {
-        return Err(TokenError::MintMismatch.into());
-    }
-
-    let mint_data = unsafe { mint_info.borrow_mut_data_unchecked() };
-    let mint = bytemuck::try_from_bytes_mut::<Mint>(mint_data)
-        .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    if let Some(expected_decimals) = expected_decimals {
-        if expected_decimals != mint.decimals {
-            return Err(TokenError::MintDecimalsMismatch.into());
-        }
-    }
-
-    match mint.mint_authority.get() {
-        Some(mint_authority) => validate_owner(program_id, &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)?;
-    }
-
-    let destination_amount = u64::from(destination_account.amount)
-        .checked_add(amount)
-        .ok_or(ProgramError::InvalidAccountData)?;
-    destination_account.amount = destination_amount.into();
-
-    let mint_supply = u64::from(mint.supply)
-        .checked_add(amount)
-        .ok_or(ProgramError::InvalidAccountData)?;
-    mint.supply = mint_supply.into();
-
-    Ok(())
+    shared::mint_to::process_mint_to(program_id, accounts, amount, None)
 }

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

@@ -4,7 +4,7 @@ use pinocchio::{
     account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
-use super::mint_to::process_mint_to;
+use super::shared;
 
 #[inline(always)]
 pub fn process_mint_to_checked(
@@ -13,7 +13,7 @@ pub fn process_mint_to_checked(
     amount: u64,
     decimals: u8,
 ) -> ProgramResult {
-    process_mint_to(program_id, accounts, amount, Some(decimals))
+    shared::mint_to::process_mint_to(program_id, accounts, amount, Some(decimals))
 }
 
 pub struct MintToChecked<'a> {

+ 2 - 3
p-token/src/processor/mod.rs

@@ -31,9 +31,8 @@ pub mod thaw_account;
 pub mod transfer;
 pub mod transfer_checked;
 pub mod ui_amount_to_amount;
-// Private processor to toggle the account state. This logic is reused by the
-// freeze and thaw account instructions.
-mod toggle_account_state;
+// Shared processors.
+pub mod shared;
 
 /// Incinerator address.
 const INCINERATOR_ID: Pubkey =

+ 74 - 0
p-token/src/processor/shared/approve.rs

@@ -0,0 +1,74 @@
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{
+    error::TokenError,
+    state::{account::Account, mint::Mint, PodCOption},
+};
+
+use crate::processor::validate_owner;
+
+#[inline(always)]
+pub fn process_approve(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    expected_decimals: Option<u8>,
+) -> ProgramResult {
+    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 @ ..] =
+                accounts
+            else {
+                return Err(ProgramError::NotEnoughAccountKeys);
+            };
+
+            (
+                source_account_info,
+                Some((expected_mint_info, expected_decimals)),
+                delegate_info,
+                owner_info,
+                remaning,
+            )
+        } else {
+            let [source_account_info, delegate_info, owner_info, remaning @ ..] = accounts else {
+                return Err(ProgramError::NotEnoughAccountKeys);
+            };
+            (
+                source_account_info,
+                None,
+                delegate_info,
+                owner_info,
+                remaning,
+            )
+        };
+
+    let source_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        source_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if source_account.is_frozen() {
+        return Err(TokenError::AccountFrozen.into());
+    }
+
+    if let Some((mint_info, expected_decimals)) = expected_mint_info {
+        if mint_info.key() != &source_account.mint {
+            return Err(TokenError::MintMismatch.into());
+        }
+
+        let mint = bytemuck::try_from_bytes::<Mint>(unsafe { mint_info.borrow_data_unchecked() })
+            .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+        if expected_decimals != mint.decimals {
+            return Err(TokenError::MintDecimalsMismatch.into());
+        }
+    }
+
+    validate_owner(program_id, &source_account.owner, owner_info, remaining)?;
+
+    source_account.delegate = PodCOption::some(*delegate_info.key());
+    source_account.delegated_amount = amount.into();
+
+    Ok(())
+}

+ 91 - 0
p-token/src/processor/shared/burn.rs

@@ -0,0 +1,91 @@
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{
+    error::TokenError,
+    state::{account::Account, mint::Mint},
+};
+
+use crate::processor::{
+    check_account_owner, is_owned_by_system_program_or_incinerator, validate_owner,
+};
+
+#[inline(always)]
+pub fn process_burn(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    expected_decimals: Option<u8>,
+) -> ProgramResult {
+    let [source_account_info, mint_info, authority_info, remaining @ ..] = accounts else {
+        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::<Account>(unsafe {
+        source_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if source_account.is_frozen() {
+        return Err(TokenError::AccountFrozen.into());
+    }
+    if source_account.is_native.is_some() {
+        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)
+        .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::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
+            .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if mint_info.key() != &source_account.mint {
+        return Err(TokenError::MintMismatch.into());
+    }
+
+    if let Some(expected_decimals) = expected_decimals {
+        if expected_decimals != mint.decimals {
+            return Err(TokenError::MintDecimalsMismatch.into());
+        }
+    }
+
+    if !is_owned_by_system_program_or_incinerator(&source_account.owner) {
+        match source_account.delegate.as_ref() {
+            Some(delegate) if authority_info.key() == delegate => {
+                validate_owner(program_id, delegate, authority_info, remaining)?;
+
+                let delegated_amount = u64::from(source_account.delegated_amount)
+                    .checked_sub(amount)
+                    .ok_or(TokenError::InsufficientFunds)?;
+                source_account.delegated_amount = delegated_amount.into();
+
+                if delegated_amount == 0 {
+                    source_account.delegate.clear();
+                }
+            }
+            _ => {
+                validate_owner(program_id, &source_account.owner, authority_info, remaining)?;
+            }
+        }
+    }
+
+    if amount == 0 {
+        check_account_owner(program_id, source_account_info)?;
+        check_account_owner(program_id, mint_info)?;
+    }
+
+    source_account.amount = updated_source_amount.into();
+
+    let mint_supply = u64::from(mint.supply)
+        .checked_sub(amount)
+        .ok_or(TokenError::Overflow)?;
+    mint.supply = mint_supply.into();
+
+    Ok(())
+}

+ 101 - 0
p-token/src/processor/shared/initialize_account.rs

@@ -0,0 +1,101 @@
+use pinocchio::{
+    account_info::AccountInfo,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    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,
+    },
+};
+
+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 {
+    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);
+        };
+        (new_account_info, mint_info, owner, remaning)
+    } else {
+        let [new_account_info, mint_info, owner_info, remaning @ ..] = accounts else {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        };
+        (new_account_info, mint_info, owner_info.key(), remaning)
+    };
+
+    // Check rent-exempt status of the token account.
+
+    let is_exempt = if rent_sysvar_account {
+        let rent_sysvar_info = remaning.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
+        let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) };
+        rent.is_exempt(new_account_info.lamports(), size_of::<Account>())
+    } else {
+        Rent::get()?.is_exempt(new_account_info.lamports(), size_of::<Account>())
+    };
+
+    if !is_exempt {
+        return Err(TokenError::NotRentExempt.into());
+    }
+
+    let account_data = unsafe { new_account_info.borrow_mut_data_unchecked() };
+    let account = bytemuck::try_from_bytes_mut::<Account>(account_data)
+        .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if account.is_initialized() {
+        return Err(TokenError::AlreadyInUse.into());
+    }
+
+    let is_native_mint = is_native_mint(mint_info.key());
+
+    if !is_native_mint {
+        check_account_owner(program_id, mint_info)?;
+
+        let mint_data = unsafe { mint_info.borrow_data_unchecked() };
+        let mint = bytemuck::try_from_bytes::<Mint>(mint_data)
+            .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+        if !bool::from(mint.is_initialized) {
+            return Err(TokenError::InvalidMint.into());
+        }
+    }
+
+    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>());
+
+        account.is_native = PodCOption::from(Some(rent_exempt_reserve.into()));
+        unsafe {
+            account.amount = new_account_info
+                .borrow_lamports_unchecked()
+                .checked_sub(rent_exempt_reserve)
+                .ok_or(TokenError::Overflow)?
+                .into()
+        }
+    } else {
+        account.is_native.clear();
+        account.amount = 0u64.into();
+    };
+
+    Ok(())
+}

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

@@ -0,0 +1,106 @@
+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::<Mint>(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::<Mint>())
+    } else {
+        Rent::get()?.is_exempt(mint_info.lamports(), size_of::<Mint>())
+    };
+
+    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<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 {
+            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))
+            }
+        }
+    }
+}

+ 64 - 0
p-token/src/processor/shared/initialize_multisig.rs

@@ -0,0 +1,64 @@
+use pinocchio::{
+    account_info::AccountInfo,
+    program_error::ProgramError,
+    sysvars::{rent::Rent, Sysvar},
+    ProgramResult,
+};
+use token_interface::{error::TokenError, state::multisig::Multisig};
+
+#[inline(always)]
+pub fn process_initialize_multisig(
+    accounts: &[AccountInfo],
+    m: u8,
+    rent_sysvar_account: bool,
+) -> 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::<Multisig>(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();
+
+    Ok(())
+}

+ 72 - 0
p-token/src/processor/shared/mint_to.rs

@@ -0,0 +1,72 @@
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{
+    error::TokenError,
+    native_mint::is_native_mint,
+    state::{account::Account, mint::Mint},
+};
+
+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<u8>,
+) -> ProgramResult {
+    let [mint_info, destination_account_info, owner_info, remaining @ ..] = accounts else {
+        return Err(ProgramError::NotEnoughAccountKeys);
+    };
+
+    // destination account
+
+    let account_data = unsafe { destination_account_info.borrow_mut_data_unchecked() };
+    let destination_account = bytemuck::try_from_bytes_mut::<Account>(account_data)
+        .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if destination_account.is_frozen() {
+        return Err(TokenError::AccountFrozen.into());
+    }
+
+    if is_native_mint(mint_info.key()) {
+        return Err(TokenError::NativeNotSupported.into());
+    }
+
+    if mint_info.key() != &destination_account.mint {
+        return Err(TokenError::MintMismatch.into());
+    }
+
+    let mint_data = unsafe { mint_info.borrow_mut_data_unchecked() };
+    let mint = bytemuck::try_from_bytes_mut::<Mint>(mint_data)
+        .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if let Some(expected_decimals) = expected_decimals {
+        if expected_decimals != mint.decimals {
+            return Err(TokenError::MintDecimalsMismatch.into());
+        }
+    }
+
+    match mint.mint_authority.get() {
+        Some(mint_authority) => validate_owner(program_id, &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)?;
+    }
+
+    let destination_amount = u64::from(destination_account.amount)
+        .checked_add(amount)
+        .ok_or(ProgramError::InvalidAccountData)?;
+    destination_account.amount = destination_amount.into();
+
+    let mint_supply = u64::from(mint.supply)
+        .checked_add(amount)
+        .ok_or(ProgramError::InvalidAccountData)?;
+    mint.supply = mint_supply.into();
+
+    Ok(())
+}

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

@@ -0,0 +1,8 @@
+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;
+pub mod transfer;

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

@@ -9,8 +9,9 @@ use token_interface::{
     },
 };
 
-use super::validate_owner;
+use crate::processor::validate_owner;
 
+#[inline(always)]
 pub fn process_toggle_account_state(
     program_id: &Pubkey,
     accounts: &[AccountInfo],

+ 162 - 0
p-token/src/processor/shared/transfer.rs

@@ -0,0 +1,162 @@
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{
+    error::TokenError,
+    native_mint::is_native_mint,
+    state::{account::Account, mint::Mint, PodCOption},
+};
+
+use crate::processor::{check_account_owner, validate_owner};
+
+#[inline(always)]
+pub fn process_transfer(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    expected_decimals: Option<u8>,
+) -> ProgramResult {
+    // Accounts expected depends 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,
+        destination_account_info,
+        authority_info,
+        remaning,
+    ) = if let Some(decimals) = expected_decimals {
+        let [source_account_info, mint_info, destination_account_info, authority_info, remaning @ ..] =
+            accounts
+        else {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        };
+        (
+            source_account_info,
+            Some((mint_info, decimals)),
+            destination_account_info,
+            authority_info,
+            remaning,
+        )
+    } else {
+        let [source_account_info, destination_account_info, authority_info, remaning @ ..] =
+            accounts
+        else {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        };
+        (
+            source_account_info,
+            None,
+            destination_account_info,
+            authority_info,
+            remaning,
+        )
+    };
+
+    // Validates source and destination accounts.
+
+    let source_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        source_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    let destination_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        destination_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    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
+    // is successful.
+    let remaining_amount = u64::from(source_account.amount)
+        .checked_sub(amount)
+        .ok_or(TokenError::InsufficientFunds)?;
+
+    if source_account.mint != destination_account.mint {
+        return Err(TokenError::MintMismatch.into());
+    }
+
+    // Validates the mint information.
+
+    if let Some((mint_info, decimals)) = expected_mint_info {
+        if mint_info.key() != &source_account.mint {
+            return Err(TokenError::MintMismatch.into());
+        }
+
+        let mint =
+            bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
+                .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+        if decimals != mint.decimals {
+            return Err(TokenError::MintDecimalsMismatch.into());
+        }
+    }
+
+    let self_transfer = source_account_info.key() == destination_account_info.key();
+
+    // 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)?;
+
+        let delegated_amount = u64::from(source_account.delegated_amount)
+            .checked_sub(amount)
+            .ok_or(TokenError::InsufficientFunds)?;
+
+        if !self_transfer {
+            source_account.delegated_amount = delegated_amount.into();
+
+            if delegated_amount == 0 {
+                source_account.delegate = PodCOption::from(None);
+            }
+        }
+    } else {
+        validate_owner(program_id, &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(());
+    }
+
+    // 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();
+
+    let destination_amount = u64::from(destination_account.amount)
+        .checked_add(amount)
+        .ok_or(TokenError::Overflow)?;
+    destination_account.amount = destination_amount.into();
+
+    if is_native_mint(&source_account.mint) {
+        let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
+        *source_lamports = source_lamports
+            .checked_sub(amount)
+            .ok_or(TokenError::Overflow)?;
+
+        let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
+        *destination_lamports = destination_lamports
+            .checked_add(amount)
+            .ok_or(TokenError::Overflow)?;
+    }
+
+    Ok(())
+}

+ 1 - 2
p-token/src/processor/thaw_account.rs

@@ -1,8 +1,7 @@
 use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::toggle_account_state::process_toggle_account_state;
+use super::shared::toggle_account_state::process_toggle_account_state;
 
-#[inline(always)]
 pub fn process_thaw_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
     process_toggle_account_state(program_id, accounts, false)
 }

+ 3 - 153
p-token/src/processor/transfer.rs

@@ -1,161 +1,11 @@
-use pinocchio::{
-    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
-};
-use token_interface::{
-    error::TokenError,
-    native_mint::is_native_mint,
-    state::{account::Account, mint::Mint, PodCOption},
-};
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
 
-use super::{check_account_owner, validate_owner};
+use super::shared;
 
 pub fn process_transfer(
     program_id: &Pubkey,
     accounts: &[AccountInfo],
     amount: u64,
-    expected_decimals: Option<u8>,
 ) -> ProgramResult {
-    // Accounts expected depends 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,
-        destination_account_info,
-        authority_info,
-        remaning,
-    ) = if let Some(decimals) = expected_decimals {
-        let [source_account_info, mint_info, destination_account_info, authority_info, remaning @ ..] =
-            accounts
-        else {
-            return Err(ProgramError::NotEnoughAccountKeys);
-        };
-        (
-            source_account_info,
-            Some((mint_info, decimals)),
-            destination_account_info,
-            authority_info,
-            remaning,
-        )
-    } else {
-        let [source_account_info, destination_account_info, authority_info, remaning @ ..] =
-            accounts
-        else {
-            return Err(ProgramError::NotEnoughAccountKeys);
-        };
-        (
-            source_account_info,
-            None,
-            destination_account_info,
-            authority_info,
-            remaning,
-        )
-    };
-
-    // Validates source and destination accounts.
-
-    let source_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
-        source_account_info.borrow_mut_data_unchecked()
-    })
-    .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    let destination_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
-        destination_account_info.borrow_mut_data_unchecked()
-    })
-    .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-    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
-    // is successful.
-    let remaining_amount = u64::from(source_account.amount)
-        .checked_sub(amount)
-        .ok_or(TokenError::InsufficientFunds)?;
-
-    if source_account.mint != destination_account.mint {
-        return Err(TokenError::MintMismatch.into());
-    }
-
-    // Validates the mint information.
-
-    if let Some((mint_info, decimals)) = expected_mint_info {
-        if mint_info.key() != &source_account.mint {
-            return Err(TokenError::MintMismatch.into());
-        }
-
-        let mint =
-            bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
-                .map_err(|_error| ProgramError::InvalidAccountData)?;
-
-        if decimals != mint.decimals {
-            return Err(TokenError::MintDecimalsMismatch.into());
-        }
-    }
-
-    let self_transfer = source_account_info.key() == destination_account_info.key();
-
-    // 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)?;
-
-        let delegated_amount = u64::from(source_account.delegated_amount)
-            .checked_sub(amount)
-            .ok_or(TokenError::InsufficientFunds)?;
-
-        if !self_transfer {
-            source_account.delegated_amount = delegated_amount.into();
-
-            if delegated_amount == 0 {
-                source_account.delegate = PodCOption::from(None);
-            }
-        }
-    } else {
-        validate_owner(program_id, &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(());
-    }
-
-    // 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();
-
-    let destination_amount = u64::from(destination_account.amount)
-        .checked_add(amount)
-        .ok_or(TokenError::Overflow)?;
-    destination_account.amount = destination_amount.into();
-
-    if is_native_mint(&source_account.mint) {
-        let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
-        *source_lamports = source_lamports
-            .checked_sub(amount)
-            .ok_or(TokenError::Overflow)?;
-
-        let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
-        *destination_lamports = destination_lamports
-            .checked_add(amount)
-            .ok_or(TokenError::Overflow)?;
-    }
-
-    Ok(())
+    shared::transfer::process_transfer(program_id, accounts, amount, None)
 }

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

@@ -4,7 +4,7 @@ use pinocchio::{
     account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
 };
 
-use super::transfer::process_transfer;
+use super::shared;
 
 #[inline(always)]
 pub fn process_transfer_checked(
@@ -13,7 +13,7 @@ pub fn process_transfer_checked(
     amount: u64,
     decimals: u8,
 ) -> ProgramResult {
-    process_transfer(program_id, accounts, amount, Some(decimals))
+    shared::transfer::process_transfer(program_id, accounts, amount, Some(decimals))
 }
 
 pub struct TransferChecked<'a> {