transfer.rs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. use {
  2. crate::processor::{check_account_owner, validate_owner},
  3. pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult},
  4. spl_token_interface::{
  5. error::TokenError,
  6. state::{account::Account, load, load_mut, load_mut_unchecked, mint::Mint},
  7. },
  8. };
  9. #[inline(always)]
  10. #[allow(clippy::arithmetic_side_effects)]
  11. pub fn process_transfer(
  12. accounts: &[AccountInfo],
  13. amount: u64,
  14. expected_decimals: Option<u8>,
  15. ) -> ProgramResult {
  16. // Accounts expected depend on whether we have the mint `decimals` or not; when
  17. // we have the 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. remaining,
  24. ) = if let Some(decimals) = expected_decimals {
  25. let [source_account_info, mint_info, destination_account_info, authority_info, remaining @ ..] =
  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. remaining,
  36. )
  37. } else {
  38. let [source_account_info, destination_account_info, authority_info, remaining @ ..] =
  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. remaining,
  49. )
  50. };
  51. // Validates source and destination accounts.
  52. // SAFETY: single mutable borrow to `source_account_info` account data and
  53. // `load_mut` validates that the account is initialized.
  54. let source_account =
  55. unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
  56. // Comparing whether the AccountInfo's "point" to the same account or
  57. // not - this is a faster comparison since it just checks the internal
  58. // raw pointer.
  59. let self_transfer = source_account_info == destination_account_info;
  60. // Implicitly validates that the account has enough tokens by calculating the
  61. // remaining amount - the amount is only updated on the account if the transfer
  62. // is successful.
  63. //
  64. // Note: the logic is partially duplicated for self transfers and transfers
  65. // to different accounts to improve CU consumption:
  66. //
  67. // - self-transfer: we only need to check that the source account is not
  68. // frozen and has enough tokens.
  69. //
  70. // - transfers to different accounts: we need to check that the source and
  71. // destination accounts are not frozen, have the same mint, and the source
  72. // account has enough tokens.
  73. let remaining_amount = if self_transfer {
  74. if source_account.is_frozen()? {
  75. return Err(TokenError::AccountFrozen.into());
  76. }
  77. source_account
  78. .amount()
  79. .checked_sub(amount)
  80. .ok_or(TokenError::InsufficientFunds)?
  81. } else {
  82. // SAFETY: scoped immutable borrow to `destination_account_info` account data
  83. // and `load` validates that the account is initialized; additionally,
  84. // the account is guaranteed to be different than `source_account_info`.
  85. let destination_account =
  86. unsafe { load::<Account>(destination_account_info.borrow_data_unchecked())? };
  87. if source_account.is_frozen()? || destination_account.is_frozen()? {
  88. return Err(TokenError::AccountFrozen.into());
  89. }
  90. let remaining_amount = source_account
  91. .amount()
  92. .checked_sub(amount)
  93. .ok_or(TokenError::InsufficientFunds)?;
  94. if source_account.mint != destination_account.mint {
  95. return Err(TokenError::MintMismatch.into());
  96. }
  97. remaining_amount
  98. };
  99. // Validates the mint information.
  100. if let Some((mint_info, decimals)) = expected_mint_info {
  101. if mint_info.key() != &source_account.mint {
  102. return Err(TokenError::MintMismatch.into());
  103. }
  104. // SAFETY: single immutable borrow of `mint_info` account data and
  105. // `load` validates that the mint is initialized.
  106. let mint = unsafe { load::<Mint>(mint_info.borrow_data_unchecked())? };
  107. if decimals != mint.decimals {
  108. return Err(TokenError::MintDecimalsMismatch.into());
  109. }
  110. }
  111. // Validates the authority (delegate or owner).
  112. if source_account.delegate()? == Some(authority_info.key()) {
  113. validate_owner(authority_info.key(), authority_info, remaining)?;
  114. let delegated_amount = source_account
  115. .delegated_amount()
  116. .checked_sub(amount)
  117. .ok_or(TokenError::InsufficientFunds)?;
  118. if !self_transfer {
  119. source_account.set_delegated_amount(delegated_amount);
  120. if delegated_amount == 0 {
  121. source_account.clear_delegate();
  122. }
  123. }
  124. } else {
  125. validate_owner(&source_account.owner, authority_info, remaining)?;
  126. }
  127. if self_transfer || amount == 0 {
  128. // Validates the token accounts owner since we are not writing
  129. // to these account.
  130. check_account_owner(source_account_info)?;
  131. check_account_owner(destination_account_info)?;
  132. } else {
  133. // Moves the tokens.
  134. source_account.set_amount(remaining_amount);
  135. // SAFETY: single mutable borrow to `destination_account_info` account data; the
  136. // account is guaranteed to be initialized and different than
  137. // `source_account_info`; it was also already validated to be a token
  138. // account.
  139. let destination_account = unsafe {
  140. load_mut_unchecked::<Account>(destination_account_info.borrow_mut_data_unchecked())?
  141. };
  142. // Note: The amount of a token account is always within the range of the
  143. // mint supply (`u64`).
  144. destination_account.set_amount(destination_account.amount() + amount);
  145. if source_account.is_native() {
  146. // SAFETY: single mutable borrow to `source_account_info` lamports.
  147. let source_lamports = unsafe { source_account_info.borrow_mut_lamports_unchecked() };
  148. *source_lamports = source_lamports
  149. .checked_sub(amount)
  150. .ok_or(TokenError::Overflow)?;
  151. // SAFETY: single mutable borrow to `destination_account_info` lamports; the
  152. // account is already validated to be different from
  153. // `source_account_info`.
  154. let destination_lamports =
  155. unsafe { destination_account_info.borrow_mut_lamports_unchecked() };
  156. *destination_lamports = destination_lamports
  157. .checked_add(amount)
  158. .ok_or(TokenError::Overflow)?;
  159. }
  160. }
  161. Ok(())
  162. }