|
@@ -0,0 +1,158 @@
|
|
|
+use pinocchio::{
|
|
|
+ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
|
|
|
+ pubkey::Pubkey,
|
|
|
+};
|
|
|
+
|
|
|
+use crate::{
|
|
|
+ error::TokenError,
|
|
|
+ native_mint::is_native_mint,
|
|
|
+ state::{account::Account, mint::Mint, PodCOption},
|
|
|
+};
|
|
|
+
|
|
|
+use super::{check_account_owner, validate_owner};
|
|
|
+
|
|
|
+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_data = unsafe { source_account_info.unchecked_borrow_mut_data() };
|
|
|
+ let source_account = bytemuck::from_bytes_mut::<Account>(source_account_data);
|
|
|
+
|
|
|
+ let destination_account_data = unsafe { destination_account_info.unchecked_borrow_mut_data() };
|
|
|
+ let destination_account = bytemuck::from_bytes_mut::<Account>(destination_account_data);
|
|
|
+
|
|
|
+ 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_le_bytes(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_data = mint_info.try_borrow_data()?;
|
|
|
+ let mint = bytemuck::from_bytes::<Mint>(&mint_data);
|
|
|
+
|
|
|
+ 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_le_bytes(source_account.delegated_amount)
|
|
|
+ .checked_sub(amount)
|
|
|
+ .ok_or(TokenError::InsufficientFunds)?;
|
|
|
+
|
|
|
+ if !self_transfer {
|
|
|
+ source_account.delegated_amount = delegated_amount.to_le_bytes();
|
|
|
+
|
|
|
+ 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.to_le_bytes();
|
|
|
+
|
|
|
+ let destination_amount = u64::from_le_bytes(destination_account.amount)
|
|
|
+ .checked_add(amount)
|
|
|
+ .ok_or(TokenError::Overflow)?;
|
|
|
+ destination_account.amount = destination_amount.to_le_bytes();
|
|
|
+
|
|
|
+ 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(())
|
|
|
+}
|