transfer.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. use pinocchio::{
  2. account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
  3. };
  4. use token_interface::{
  5. error::TokenError,
  6. native_mint::is_native_mint,
  7. state::{account::Account, mint::Mint, PodCOption},
  8. };
  9. use super::{check_account_owner, validate_owner};
  10. pub fn process_transfer(
  11. program_id: &Pubkey,
  12. accounts: &[AccountInfo],
  13. amount: u64,
  14. expected_decimals: Option<u8>,
  15. ) -> ProgramResult {
  16. // Accounts expected depends on whether we have the mint `decimals` or not; when we have the
  17. // mint `decimals`, we expect the mint account to be present.
  18. let (
  19. source_account_info,
  20. expected_mint_info,
  21. destination_account_info,
  22. authority_info,
  23. remaning,
  24. ) = if let Some(decimals) = expected_decimals {
  25. let [source_account_info, mint_info, destination_account_info, authority_info, remaning @ ..] =
  26. accounts
  27. else {
  28. return Err(ProgramError::NotEnoughAccountKeys);
  29. };
  30. (
  31. source_account_info,
  32. Some((mint_info, decimals)),
  33. destination_account_info,
  34. authority_info,
  35. remaning,
  36. )
  37. } else {
  38. let [source_account_info, destination_account_info, authority_info, remaning @ ..] =
  39. accounts
  40. else {
  41. return Err(ProgramError::NotEnoughAccountKeys);
  42. };
  43. (
  44. source_account_info,
  45. None,
  46. destination_account_info,
  47. authority_info,
  48. remaning,
  49. )
  50. };
  51. // Validates source and destination accounts.
  52. let source_account_data = unsafe { source_account_info.borrow_mut_data_unchecked() };
  53. let source_account = bytemuck::try_from_bytes_mut::<Account>(source_account_data)
  54. .map_err(|_error| ProgramError::InvalidAccountData)?;
  55. let destination_account_data = unsafe { destination_account_info.borrow_mut_data_unchecked() };
  56. let destination_account = bytemuck::try_from_bytes_mut::<Account>(destination_account_data)
  57. .map_err(|_error| ProgramError::InvalidAccountData)?;
  58. if source_account.is_frozen() || destination_account.is_frozen() {
  59. return Err(TokenError::AccountFrozen.into());
  60. }
  61. // FEBO: Implicitly validates that the account has enough tokens by calculating the
  62. // remaining amount. The amount is only updated on the account if the transfer
  63. // is successful.
  64. let remaining_amount = u64::from(source_account.amount)
  65. .checked_sub(amount)
  66. .ok_or(TokenError::InsufficientFunds)?;
  67. if source_account.mint != destination_account.mint {
  68. return Err(TokenError::MintMismatch.into());
  69. }
  70. // Validates the mint information.
  71. if let Some((mint_info, decimals)) = expected_mint_info {
  72. if mint_info.key() != &source_account.mint {
  73. return Err(TokenError::MintMismatch.into());
  74. }
  75. let mint_data = mint_info.try_borrow_data()?;
  76. let mint = bytemuck::from_bytes::<Mint>(&mint_data);
  77. if decimals != mint.decimals {
  78. return Err(TokenError::MintDecimalsMismatch.into());
  79. }
  80. }
  81. let self_transfer = source_account_info.key() == destination_account_info.key();
  82. // Validates the authority (delegate or owner).
  83. if source_account.delegate.as_ref() == Some(authority_info.key()) {
  84. validate_owner(program_id, authority_info.key(), authority_info, remaning)?;
  85. let delegated_amount = u64::from(source_account.delegated_amount)
  86. .checked_sub(amount)
  87. .ok_or(TokenError::InsufficientFunds)?;
  88. if !self_transfer {
  89. source_account.delegated_amount = delegated_amount.into();
  90. if delegated_amount == 0 {
  91. source_account.delegate = PodCOption::from(None);
  92. }
  93. }
  94. } else {
  95. validate_owner(program_id, &source_account.owner, authority_info, remaning)?;
  96. }
  97. if self_transfer || amount == 0 {
  98. check_account_owner(program_id, source_account_info)?;
  99. check_account_owner(program_id, destination_account_info)?;
  100. // No need to move tokens around.
  101. return Ok(());
  102. }
  103. // FEBO: This was moved to the if statement above since we can skip the amount
  104. // manipulation if it is a self-transfer or the amount is zero.
  105. //
  106. // This check MUST occur just before the amounts are manipulated
  107. // to ensure self-transfers are fully validated
  108. /*
  109. if self_transfer {
  110. return Ok(());
  111. }
  112. */
  113. // Moves the tokens.
  114. source_account.amount = remaining_amount.into();
  115. let destination_amount = u64::from(destination_account.amount)
  116. .checked_add(amount)
  117. .ok_or(TokenError::Overflow)?;
  118. destination_account.amount = destination_amount.into();
  119. if is_native_mint(&source_account.mint) {
  120. let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
  121. *source_lamports = source_lamports
  122. .checked_sub(amount)
  123. .ok_or(TokenError::Overflow)?;
  124. let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
  125. *destination_lamports = destination_lamports
  126. .checked_add(amount)
  127. .ok_or(TokenError::Overflow)?;
  128. }
  129. Ok(())
  130. }