diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index edcd6b4..bdbee3f 100644 --- a/program/src/entrypoint.rs +++ b/program/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, _)) => { diff --git a/program/src/processor/approve.rs b/program/src/processor/approve.rs index ca09471..6a6d1cd 100644 --- a/program/src/processor/approve.rs +++ b/program/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, ) -> 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::(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::(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) } diff --git a/program/src/processor/approve_checked.rs b/program/src/processor/approve_checked.rs index 8ce145b..6495712 100644 --- a/program/src/processor/approve_checked.rs +++ b/program/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> { diff --git a/program/src/processor/burn.rs b/program/src/processor/burn.rs index f0a1c54..34aa606 100644 --- a/program/src/processor/burn.rs +++ b/program/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, -) -> 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::(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::(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) } diff --git a/program/src/processor/burn_checked.rs b/program/src/processor/burn_checked.rs index 5ada832..aac4d6d 100644 --- a/program/src/processor/burn_checked.rs +++ b/program/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> { diff --git a/program/src/processor/freeze_account.rs b/program/src/processor/freeze_account.rs index 188a019..910f9ee 100644 --- a/program/src/processor/freeze_account.rs +++ b/program/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) } diff --git a/program/src/processor/initialize_account.rs b/program/src/processor/initialize_account.rs index ac8efa9..fd2dab8 100644 --- a/program/src/processor/initialize_account.rs +++ b/program/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::()) - } else { - Rent::get()?.is_exempt(new_account_info.lamports(), size_of::()) - }; - - 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_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_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.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) } diff --git a/program/src/processor/initialize_account2.rs b/program/src/processor/initialize_account2.rs index 476fb89..d199b32 100644 --- a/program/src/processor/initialize_account2.rs +++ b/program/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) } diff --git a/program/src/processor/initialize_account3.rs b/program/src/processor/initialize_account3.rs index d375ef1..82ed1b5 100644 --- a/program/src/processor/initialize_account3.rs +++ b/program/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) } diff --git a/program/src/processor/initialize_mint.rs b/program/src/processor/initialize_mint.rs index 74fd57a..5601015 100644 --- a/program/src/processor/initialize_mint.rs +++ b/program/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::(unsafe { mint_info.borrow_mut_data_unchecked() }) - .map_err(|_error| ProgramError::InvalidAccountData)?; - - if mint.is_initialized.into() { - return Err(TokenError::AlreadyInUse.into()); - } - - // Check rent-exempt status of the mint account. - - let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; - rent.is_exempt(mint_info.lamports(), size_of::()) - } else { - Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) - }; - - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } - - // Initialize the mint. - - mint.mint_authority = PodCOption::from(Some(*args.mint_authority())); - mint.decimals = args.decimals(); - mint.is_initialized = true.into(); - - if let Some(freeze_authority) = args.freeze_authority() { - mint.freeze_authority = PodCOption::from(Some(*freeze_authority)); - } - - Ok(()) -} - -/* -/// Instruction data for the `InitializeMint` instruction. -pub struct InitializeMint<'a> { - 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 { - // 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::() { - return Err(ProgramError::InvalidInstructionData); - } - - let (data, remaining) = data.split_at(size_of::()); - let data = bytemuck::from_bytes::(data); - - let freeze_authority = match remaining.split_first() { - Some((&0, _)) => None, - Some((&1, pubkey)) if pubkey.len() == PUBKEY_BYTES => { - Some(bytemuck::from_bytes::(pubkey)) - } - _ => return Err(ProgramError::InvalidInstructionData), - }; - - 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 { - // 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) } diff --git a/program/src/processor/initialize_mint2.rs b/program/src/processor/initialize_mint2.rs index 7693ef5..663104c 100644 --- a/program/src/processor/initialize_mint2.rs +++ b/program/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) } diff --git a/program/src/processor/initialize_multisig2.rs b/program/src/processor/initialize_multisig2.rs index eba10b1..089d05b 100644 --- a/program/src/processor/initialize_multisig2.rs +++ b/program/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) } diff --git a/program/src/processor/mint_to.rs b/program/src/processor/mint_to.rs index acecc7a..eaea29b 100644 --- a/program/src/processor/mint_to.rs +++ b/program/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, ) -> 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_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_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) } diff --git a/program/src/processor/mint_to_checked.rs b/program/src/processor/mint_to_checked.rs index 98e37da..deb8d8e 100644 --- a/program/src/processor/mint_to_checked.rs +++ b/program/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> { diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 6e51486..9527cc7 100644 --- a/program/src/processor/mod.rs +++ b/program/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 = diff --git a/program/src/processor/shared/approve.rs b/program/src/processor/shared/approve.rs new file mode 100644 index 0000000..303d943 --- /dev/null +++ b/program/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, +) -> 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::(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::(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(()) +} diff --git a/program/src/processor/shared/burn.rs b/program/src/processor/shared/burn.rs new file mode 100644 index 0000000..c46d0e3 --- /dev/null +++ b/program/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, +) -> 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::(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::(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(()) +} diff --git a/program/src/processor/shared/initialize_account.rs b/program/src/processor/shared/initialize_account.rs new file mode 100644 index 0000000..8176ed3 --- /dev/null +++ b/program/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::()) + } else { + Rent::get()?.is_exempt(new_account_info.lamports(), size_of::()) + }; + + 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_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_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.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(()) +} diff --git a/program/src/processor/shared/initialize_mint.rs b/program/src/processor/shared/initialize_mint.rs new file mode 100644 index 0000000..845b64b --- /dev/null +++ b/program/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::(unsafe { mint_info.borrow_mut_data_unchecked() }) + .map_err(|_error| ProgramError::InvalidAccountData)?; + + if mint.is_initialized.into() { + return Err(TokenError::AlreadyInUse.into()); + } + + // Check rent-exempt status of the mint account. + + let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { + let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; + rent.is_exempt(mint_info.lamports(), size_of::()) + } else { + Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) + }; + + if !is_exempt { + return Err(TokenError::NotRentExempt.into()); + } + + // Initialize the mint. + + mint.mint_authority = PodCOption::from(Some(*args.mint_authority())); + mint.decimals = args.decimals(); + mint.is_initialized = true.into(); + + if let Some(freeze_authority) = args.freeze_authority() { + mint.freeze_authority = PodCOption::from(Some(*freeze_authority)); + } + + Ok(()) +} + +/// Instruction data for the `InitializeMint` instruction. +pub struct InitializeMint<'a> { + raw: *const u8, + + _data: PhantomData<&'a [u8]>, +} + +impl InitializeMint<'_> { + pub fn try_from_bytes(bytes: &[u8]) -> Result { + // The minimum expected size of the instruction data. + // - decimals (1 byte) + // - mint_authority (32 bytes) + // - option + freeze_authority (1 byte + 32 bytes) + if bytes.len() < 34 { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(InitializeMint { + raw: bytes.as_ptr(), + _data: PhantomData, + }) + } + + pub fn decimals(&self) -> u8 { + unsafe { *self.raw } + } + + pub fn mint_authority(&self) -> &Pubkey { + unsafe { &*(self.raw.add(1) as *const Pubkey) } + } + + pub fn freeze_authority(&self) -> Option<&Pubkey> { + unsafe { + if *self.raw.add(33) == 0 { + Option::None + } else { + Option::Some(&*(self.raw.add(34) as *const Pubkey)) + } + } + } +} diff --git a/program/src/processor/shared/initialize_multisig.rs b/program/src/processor/shared/initialize_multisig.rs new file mode 100644 index 0000000..8db95f1 --- /dev/null +++ b/program/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::(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(()) +} diff --git a/program/src/processor/shared/mint_to.rs b/program/src/processor/shared/mint_to.rs new file mode 100644 index 0000000..62ca9d7 --- /dev/null +++ b/program/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, +) -> 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_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_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(()) +} diff --git a/program/src/processor/shared/mod.rs b/program/src/processor/shared/mod.rs new file mode 100644 index 0000000..634ccab --- /dev/null +++ b/program/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; diff --git a/program/src/processor/toggle_account_state.rs b/program/src/processor/shared/toggle_account_state.rs similarity index 96% rename from program/src/processor/toggle_account_state.rs rename to program/src/processor/shared/toggle_account_state.rs index 69fbac0..e55ea63 100644 --- a/program/src/processor/toggle_account_state.rs +++ b/program/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], diff --git a/program/src/processor/shared/transfer.rs b/program/src/processor/shared/transfer.rs new file mode 100644 index 0000000..766011b --- /dev/null +++ b/program/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, +) -> 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::(unsafe { + source_account_info.borrow_mut_data_unchecked() + }) + .map_err(|_error| ProgramError::InvalidAccountData)?; + + let destination_account = bytemuck::try_from_bytes_mut::(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::(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(()) +} diff --git a/program/src/processor/thaw_account.rs b/program/src/processor/thaw_account.rs index a7f4da0..bd964aa 100644 --- a/program/src/processor/thaw_account.rs +++ b/program/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) } diff --git a/program/src/processor/transfer.rs b/program/src/processor/transfer.rs index 92db0cc..79f1a87 100644 --- a/program/src/processor/transfer.rs +++ b/program/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, ) -> 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::(unsafe { - source_account_info.borrow_mut_data_unchecked() - }) - .map_err(|_error| ProgramError::InvalidAccountData)?; - - let destination_account = bytemuck::try_from_bytes_mut::(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::(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) } diff --git a/program/src/processor/transfer_checked.rs b/program/src/processor/transfer_checked.rs index 60f4cda..8b216c5 100644 --- a/program/src/processor/transfer_checked.rs +++ b/program/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> {