apl_token/
processor.rs

1//! Program state processor
2
3use {
4    crate::{
5        amount_to_ui_amount_string_trimmed,
6        error::TokenError,
7        instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS},
8        state::{is_rent_exempt_account, Account, AccountState, Mint, Multisig},
9        try_ui_amount_into_amount,
10    },
11    arch_program::{
12        account::{next_account_info, AccountInfo},
13        entrypoint::ProgramResult,
14        input_to_sign::InputToSign,
15        msg,
16        program::{set_input_to_sign, set_return_data},
17        program_error::ProgramError,
18        program_memory::sol_memcmp,
19        program_option::COption,
20        program_pack::{IsInitialized, Pack},
21        pubkey::{Pubkey, PUBKEY_BYTES},
22        system_program::SYSTEM_PROGRAM_ID,
23    },
24};
25
26/// Program state handler.
27pub struct Processor {}
28impl Processor {
29    fn _process_initialize_mint(
30        accounts: &[AccountInfo],
31        decimals: u8,
32        mint_authority: Pubkey,
33        freeze_authority: COption<Pubkey>,
34    ) -> ProgramResult {
35        let account_info_iter = &mut accounts.iter();
36        let mint_info = next_account_info(account_info_iter)?;
37
38        let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?;
39        if mint.is_initialized {
40            return Err(TokenError::AlreadyInUse.into());
41        }
42
43        if !is_rent_exempt_account(mint_info.lamports(), mint_info.data_len()) {
44            return Err(TokenError::NotRentExempt.into());
45        }
46
47        mint.mint_authority = COption::Some(mint_authority);
48        mint.decimals = decimals;
49        mint.is_initialized = true;
50        mint.freeze_authority = freeze_authority;
51
52        Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
53
54        Ok(())
55    }
56
57    /// Processes an [`InitializeMint`](enum.TokenInstruction.html) instruction.
58    pub fn process_initialize_mint(
59        accounts: &[AccountInfo],
60        decimals: u8,
61        mint_authority: Pubkey,
62        freeze_authority: COption<Pubkey>,
63    ) -> ProgramResult {
64        Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
65    }
66
67    /// Processes an [`InitializeMint2`](enum.TokenInstruction.html)
68    /// instruction.
69    pub fn process_initialize_mint2(
70        accounts: &[AccountInfo],
71        decimals: u8,
72        mint_authority: Pubkey,
73        freeze_authority: COption<Pubkey>,
74    ) -> ProgramResult {
75        Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
76    }
77
78    fn _process_initialize_account(
79        program_id: &Pubkey,
80        accounts: &[AccountInfo],
81        owner: Option<&Pubkey>,
82    ) -> ProgramResult {
83        let account_info_iter = &mut accounts.iter();
84        let new_account_info = next_account_info(account_info_iter)?;
85        let mint_info = next_account_info(account_info_iter)?;
86        let owner = if let Some(owner) = owner {
87            owner
88        } else {
89            next_account_info(account_info_iter)?.key
90        };
91
92        let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?;
93        if account.is_initialized() {
94            return Err(TokenError::AlreadyInUse.into());
95        }
96
97        if !is_rent_exempt_account(new_account_info.lamports(), new_account_info.data_len()) {
98            return Err(TokenError::NotRentExempt.into());
99        }
100
101        let is_native_mint = Self::cmp_pubkeys(mint_info.key, &crate::native_mint::id());
102        if !is_native_mint {
103            Self::check_account_owner(program_id, mint_info)?;
104            let _ = Mint::unpack(&mint_info.data.borrow_mut())
105                .map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
106        }
107
108        account.mint = *mint_info.key;
109        account.owner = *owner;
110        account.close_authority = COption::None;
111        account.delegate = COption::None;
112        account.delegated_amount = 0;
113        account.state = AccountState::Initialized;
114        if is_native_mint {
115            let rent_exempt_reserve = arch_program::rent::minimum_rent(new_account_info.data_len());
116            account.is_native = COption::Some(rent_exempt_reserve);
117            account.amount = new_account_info
118                .lamports()
119                .checked_sub(rent_exempt_reserve)
120                .ok_or(TokenError::Overflow)?;
121        } else {
122            account.is_native = COption::None;
123            account.amount = 0;
124        };
125
126        Account::pack(account, &mut new_account_info.data.borrow_mut())?;
127
128        Ok(())
129    }
130
131    /// Processes an [`InitializeAccount`](enum.TokenInstruction.html)
132    /// instruction.
133    pub fn process_initialize_account(
134        program_id: &Pubkey,
135        accounts: &[AccountInfo],
136    ) -> ProgramResult {
137        Self::_process_initialize_account(program_id, accounts, None)
138    }
139
140    /// Processes an [`InitializeAccount2`](enum.TokenInstruction.html)
141    /// instruction.
142    pub fn process_initialize_account2(
143        program_id: &Pubkey,
144        accounts: &[AccountInfo],
145        owner: Pubkey,
146    ) -> ProgramResult {
147        Self::_process_initialize_account(program_id, accounts, Some(&owner))
148    }
149
150    /// Processes an [`InitializeAccount3`](enum.TokenInstruction.html)
151    /// instruction.
152    pub fn process_initialize_account3(
153        program_id: &Pubkey,
154        accounts: &[AccountInfo],
155        owner: Pubkey,
156    ) -> ProgramResult {
157        Self::_process_initialize_account(program_id, accounts, Some(&owner))
158    }
159
160    fn _process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
161        let account_info_iter = &mut accounts.iter();
162        let multisig_info = next_account_info(account_info_iter)?;
163
164        let mut multisig = Multisig::unpack_unchecked(&multisig_info.data.borrow())?;
165        if multisig.is_initialized {
166            return Err(TokenError::AlreadyInUse.into());
167        }
168        if !is_rent_exempt_account(multisig_info.lamports(), multisig_info.data_len()) {
169            return Err(TokenError::NotRentExempt.into());
170        }
171
172        let signer_infos = account_info_iter.as_slice();
173        multisig.m = m;
174        multisig.n = signer_infos.len() as u8;
175        if !is_valid_signer_index(multisig.n as usize) {
176            return Err(TokenError::InvalidNumberOfProvidedSigners.into());
177        }
178        if !is_valid_signer_index(multisig.m as usize) {
179            return Err(TokenError::InvalidNumberOfRequiredSigners.into());
180        }
181        for (i, signer_info) in signer_infos.iter().enumerate() {
182            multisig.signers[i] = *signer_info.key;
183        }
184        multisig.is_initialized = true;
185
186        Multisig::pack(multisig, &mut multisig_info.data.borrow_mut())?;
187
188        Ok(())
189    }
190
191    /// Processes a [`InitializeMultisig`](enum.TokenInstruction.html)
192    /// instruction.
193    pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
194        Self::_process_initialize_multisig(accounts, m)
195    }
196
197    /// Processes a [`Transfer`](enum.TokenInstruction.html) instruction.
198    pub fn process_transfer(
199        program_id: &Pubkey,
200        accounts: &[AccountInfo],
201        amount: u64,
202        expected_decimals: Option<u8>,
203    ) -> ProgramResult {
204        let account_info_iter = &mut accounts.iter();
205
206        let source_account_info = next_account_info(account_info_iter)?;
207
208        let expected_mint_info = if let Some(expected_decimals) = expected_decimals {
209            Some((next_account_info(account_info_iter)?, expected_decimals))
210        } else {
211            None
212        };
213
214        let destination_account_info = next_account_info(account_info_iter)?;
215        let authority_info = next_account_info(account_info_iter)?;
216
217        let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
218        let mut destination_account = Account::unpack(&destination_account_info.data.borrow())?;
219
220        if source_account.is_frozen() || destination_account.is_frozen() {
221            return Err(TokenError::AccountFrozen.into());
222        }
223        if source_account.amount < amount {
224            return Err(TokenError::InsufficientFunds.into());
225        }
226        if !Self::cmp_pubkeys(&source_account.mint, &destination_account.mint) {
227            return Err(TokenError::MintMismatch.into());
228        }
229
230        if let Some((mint_info, expected_decimals)) = expected_mint_info {
231            if !Self::cmp_pubkeys(mint_info.key, &source_account.mint) {
232                return Err(TokenError::MintMismatch.into());
233            }
234
235            let mint = Mint::unpack(&mint_info.data.borrow_mut())?;
236            if expected_decimals != mint.decimals {
237                return Err(TokenError::MintDecimalsMismatch.into());
238            }
239        }
240
241        let self_transfer =
242            Self::cmp_pubkeys(source_account_info.key, destination_account_info.key);
243
244        match source_account.delegate {
245            COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => {
246                Self::validate_owner(
247                    program_id,
248                    delegate,
249                    authority_info,
250                    account_info_iter.as_slice(),
251                )?;
252                if source_account.delegated_amount < amount {
253                    return Err(TokenError::InsufficientFunds.into());
254                }
255                if !self_transfer {
256                    source_account.delegated_amount = source_account
257                        .delegated_amount
258                        .checked_sub(amount)
259                        .ok_or(TokenError::Overflow)?;
260                    if source_account.delegated_amount == 0 {
261                        source_account.delegate = COption::None;
262                    }
263                }
264            }
265            _ => Self::validate_owner(
266                program_id,
267                &source_account.owner,
268                authority_info,
269                account_info_iter.as_slice(),
270            )?,
271        };
272
273        if self_transfer || amount == 0 {
274            Self::check_account_owner(program_id, source_account_info)?;
275            Self::check_account_owner(program_id, destination_account_info)?;
276        }
277
278        // This check MUST occur just before the amounts are manipulated
279        // to ensure self-transfers are fully validated
280        if self_transfer {
281            return Ok(());
282        }
283
284        source_account.amount = source_account
285            .amount
286            .checked_sub(amount)
287            .ok_or(TokenError::Overflow)?;
288        destination_account.amount = destination_account
289            .amount
290            .checked_add(amount)
291            .ok_or(TokenError::Overflow)?;
292
293        if source_account.is_native() {
294            let source_starting_lamports = source_account_info.lamports();
295            **source_account_info.lamports.borrow_mut() = source_starting_lamports
296                .checked_sub(amount)
297                .ok_or(TokenError::Overflow)?;
298
299            let destination_starting_lamports = destination_account_info.lamports();
300            **destination_account_info.lamports.borrow_mut() = destination_starting_lamports
301                .checked_add(amount)
302                .ok_or(TokenError::Overflow)?;
303        }
304
305        Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
306        Account::pack(
307            destination_account,
308            &mut destination_account_info.data.borrow_mut(),
309        )?;
310
311        Ok(())
312    }
313
314    /// Processes an [`Approve`](enum.TokenInstruction.html) instruction.
315    pub fn process_approve(
316        program_id: &Pubkey,
317        accounts: &[AccountInfo],
318        amount: u64,
319        expected_decimals: Option<u8>,
320    ) -> ProgramResult {
321        let account_info_iter = &mut accounts.iter();
322
323        let source_account_info = next_account_info(account_info_iter)?;
324
325        let expected_mint_info = if let Some(expected_decimals) = expected_decimals {
326            Some((next_account_info(account_info_iter)?, expected_decimals))
327        } else {
328            None
329        };
330        let delegate_info = next_account_info(account_info_iter)?;
331        let owner_info = next_account_info(account_info_iter)?;
332
333        let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
334
335        if source_account.is_frozen() {
336            return Err(TokenError::AccountFrozen.into());
337        }
338
339        if let Some((mint_info, expected_decimals)) = expected_mint_info {
340            if !Self::cmp_pubkeys(mint_info.key, &source_account.mint) {
341                return Err(TokenError::MintMismatch.into());
342            }
343
344            let mint = Mint::unpack(&mint_info.data.borrow_mut())?;
345            if expected_decimals != mint.decimals {
346                return Err(TokenError::MintDecimalsMismatch.into());
347            }
348        }
349
350        Self::validate_owner(
351            program_id,
352            &source_account.owner,
353            owner_info,
354            account_info_iter.as_slice(),
355        )?;
356
357        source_account.delegate = COption::Some(*delegate_info.key);
358        source_account.delegated_amount = amount;
359
360        Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
361
362        Ok(())
363    }
364
365    /// Processes an [`Revoke`](enum.TokenInstruction.html) instruction.
366    pub fn process_revoke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
367        let account_info_iter = &mut accounts.iter();
368        let source_account_info = next_account_info(account_info_iter)?;
369
370        let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
371
372        let owner_info = next_account_info(account_info_iter)?;
373
374        if source_account.is_frozen() {
375            return Err(TokenError::AccountFrozen.into());
376        }
377
378        Self::validate_owner(
379            program_id,
380            &source_account.owner,
381            owner_info,
382            account_info_iter.as_slice(),
383        )?;
384
385        source_account.delegate = COption::None;
386        source_account.delegated_amount = 0;
387
388        Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
389
390        Ok(())
391    }
392
393    /// Processes a [`SetAuthority`](enum.TokenInstruction.html) instruction.
394    pub fn process_set_authority(
395        program_id: &Pubkey,
396        accounts: &[AccountInfo],
397        authority_type: AuthorityType,
398        new_authority: COption<Pubkey>,
399    ) -> ProgramResult {
400        let account_info_iter = &mut accounts.iter();
401        let account_info = next_account_info(account_info_iter)?;
402        let authority_info = next_account_info(account_info_iter)?;
403
404        if account_info.data_len() == Account::get_packed_len() {
405            let mut account = Account::unpack(&account_info.data.borrow())?;
406
407            if account.is_frozen() {
408                return Err(TokenError::AccountFrozen.into());
409            }
410
411            match authority_type {
412                AuthorityType::AccountOwner => {
413                    Self::validate_owner(
414                        program_id,
415                        &account.owner,
416                        authority_info,
417                        account_info_iter.as_slice(),
418                    )?;
419
420                    if let COption::Some(authority) = new_authority {
421                        account.owner = authority;
422                    } else {
423                        return Err(TokenError::InvalidInstruction.into());
424                    }
425
426                    account.delegate = COption::None;
427                    account.delegated_amount = 0;
428                }
429                AuthorityType::CloseAccount => {
430                    let authority = account.close_authority.unwrap_or(account.owner);
431                    Self::validate_owner(
432                        program_id,
433                        &authority,
434                        authority_info,
435                        account_info_iter.as_slice(),
436                    )?;
437                    account.close_authority = new_authority;
438                }
439                _ => {
440                    return Err(TokenError::AuthorityTypeNotSupported.into());
441                }
442            }
443            Account::pack(account, &mut account_info.data.borrow_mut())?;
444        } else if account_info.data_len() == Mint::get_packed_len() {
445            let mut mint = Mint::unpack(&account_info.data.borrow())?;
446            match authority_type {
447                AuthorityType::MintTokens => {
448                    // Once a mint's supply is fixed, it cannot be undone by setting a new
449                    // mint_authority
450                    let mint_authority = mint
451                        .mint_authority
452                        .ok_or(Into::<ProgramError>::into(TokenError::FixedSupply))?;
453                    Self::validate_owner(
454                        program_id,
455                        &mint_authority,
456                        authority_info,
457                        account_info_iter.as_slice(),
458                    )?;
459                    mint.mint_authority = new_authority;
460                }
461                AuthorityType::FreezeAccount => {
462                    // Once a mint's freeze authority is disabled, it cannot be re-enabled by
463                    // setting a new freeze_authority
464                    let freeze_authority = mint
465                        .freeze_authority
466                        .ok_or(Into::<ProgramError>::into(TokenError::MintCannotFreeze))?;
467                    Self::validate_owner(
468                        program_id,
469                        &freeze_authority,
470                        authority_info,
471                        account_info_iter.as_slice(),
472                    )?;
473                    mint.freeze_authority = new_authority;
474                }
475                _ => {
476                    return Err(TokenError::AuthorityTypeNotSupported.into());
477                }
478            }
479            Mint::pack(mint, &mut account_info.data.borrow_mut())?;
480        } else {
481            return Err(ProgramError::InvalidArgument);
482        }
483
484        Ok(())
485    }
486
487    /// Processes a [`MintTo`](enum.TokenInstruction.html) instruction.
488    pub fn process_mint_to(
489        program_id: &Pubkey,
490        accounts: &[AccountInfo],
491        amount: u64,
492        expected_decimals: Option<u8>,
493    ) -> ProgramResult {
494        let account_info_iter = &mut accounts.iter();
495        let mint_info = next_account_info(account_info_iter)?;
496        let destination_account_info = next_account_info(account_info_iter)?;
497        let owner_info = next_account_info(account_info_iter)?;
498
499        let mut destination_account = Account::unpack(&destination_account_info.data.borrow())?;
500        if destination_account.is_frozen() {
501            return Err(TokenError::AccountFrozen.into());
502        }
503
504        if destination_account.is_native() {
505            return Err(TokenError::NativeNotSupported.into());
506        }
507
508        if !Self::cmp_pubkeys(mint_info.key, &destination_account.mint) {
509            return Err(TokenError::MintMismatch.into());
510        }
511
512        let mut mint = Mint::unpack(&mint_info.data.borrow())?;
513        if let Some(expected_decimals) = expected_decimals {
514            if expected_decimals != mint.decimals {
515                return Err(TokenError::MintDecimalsMismatch.into());
516            }
517        }
518
519        match mint.mint_authority {
520            COption::Some(mint_authority) => Self::validate_owner(
521                program_id,
522                &mint_authority,
523                owner_info,
524                account_info_iter.as_slice(),
525            )?,
526            COption::None => return Err(TokenError::FixedSupply.into()),
527        }
528
529        if amount == 0 {
530            Self::check_account_owner(program_id, mint_info)?;
531            Self::check_account_owner(program_id, destination_account_info)?;
532        }
533
534        destination_account.amount = destination_account
535            .amount
536            .checked_add(amount)
537            .ok_or(TokenError::Overflow)?;
538
539        mint.supply = mint
540            .supply
541            .checked_add(amount)
542            .ok_or(TokenError::Overflow)?;
543
544        Account::pack(
545            destination_account,
546            &mut destination_account_info.data.borrow_mut(),
547        )?;
548        Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
549
550        Ok(())
551    }
552
553    /// Processes a [`Burn`](enum.TokenInstruction.html) instruction.
554    pub fn process_burn(
555        program_id: &Pubkey,
556        accounts: &[AccountInfo],
557        amount: u64,
558        expected_decimals: Option<u8>,
559    ) -> ProgramResult {
560        let account_info_iter = &mut accounts.iter();
561
562        let source_account_info = next_account_info(account_info_iter)?;
563        let mint_info = next_account_info(account_info_iter)?;
564        let authority_info = next_account_info(account_info_iter)?;
565
566        let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
567        let mut mint = Mint::unpack(&mint_info.data.borrow())?;
568
569        if source_account.is_frozen() {
570            return Err(TokenError::AccountFrozen.into());
571        }
572        if source_account.is_native() {
573            return Err(TokenError::NativeNotSupported.into());
574        }
575        if source_account.amount < amount {
576            return Err(TokenError::InsufficientFunds.into());
577        }
578        if !Self::cmp_pubkeys(mint_info.key, &source_account.mint) {
579            return Err(TokenError::MintMismatch.into());
580        }
581
582        if let Some(expected_decimals) = expected_decimals {
583            if expected_decimals != mint.decimals {
584                return Err(TokenError::MintDecimalsMismatch.into());
585            }
586        }
587
588        if source_account.owner != SYSTEM_PROGRAM_ID {
589            match source_account.delegate {
590                COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => {
591                    Self::validate_owner(
592                        program_id,
593                        delegate,
594                        authority_info,
595                        account_info_iter.as_slice(),
596                    )?;
597
598                    if source_account.delegated_amount < amount {
599                        return Err(TokenError::InsufficientFunds.into());
600                    }
601                    source_account.delegated_amount = source_account
602                        .delegated_amount
603                        .checked_sub(amount)
604                        .ok_or(TokenError::Overflow)?;
605                    if source_account.delegated_amount == 0 {
606                        source_account.delegate = COption::None;
607                    }
608                }
609                _ => Self::validate_owner(
610                    program_id,
611                    &source_account.owner,
612                    authority_info,
613                    account_info_iter.as_slice(),
614                )?,
615            }
616        }
617
618        if amount == 0 {
619            Self::check_account_owner(program_id, source_account_info)?;
620            Self::check_account_owner(program_id, mint_info)?;
621        }
622
623        source_account.amount = source_account
624            .amount
625            .checked_sub(amount)
626            .ok_or(TokenError::Overflow)?;
627        mint.supply = mint
628            .supply
629            .checked_sub(amount)
630            .ok_or(TokenError::Overflow)?;
631
632        Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
633        Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
634
635        Ok(())
636    }
637
638    /// Processes a [`CloseAccount`](enum.TokenInstruction.html) instruction.
639    pub fn process_close_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
640        let account_info_iter = &mut accounts.iter();
641        let source_account_info = next_account_info(account_info_iter)?;
642        let destination_account_info = next_account_info(account_info_iter)?;
643        let authority_info = next_account_info(account_info_iter)?;
644
645        if Self::cmp_pubkeys(source_account_info.key, destination_account_info.key) {
646            return Err(ProgramError::InvalidAccountData);
647        }
648
649        let source_account = Account::unpack(&source_account_info.data.borrow())?;
650        if !source_account.is_native() && source_account.amount != 0 {
651            return Err(TokenError::NonNativeHasBalance.into());
652        }
653
654        let authority = source_account
655            .close_authority
656            .unwrap_or(source_account.owner);
657
658        if source_account.owner != SYSTEM_PROGRAM_ID {
659            Self::validate_owner(
660                program_id,
661                &authority,
662                authority_info,
663                account_info_iter.as_slice(),
664            )?;
665        }
666
667        let destination_starting_lamports = destination_account_info.lamports();
668        **destination_account_info.lamports.borrow_mut() = destination_starting_lamports
669            .checked_add(source_account_info.lamports())
670            .ok_or(TokenError::Overflow)?;
671
672        **source_account_info.lamports.borrow_mut() = 0;
673
674        delete_account(source_account_info)?;
675
676        Ok(())
677    }
678
679    /// Processes a [`FreezeAccount`](enum.TokenInstruction.html) or a
680    /// [`ThawAccount`](enum.TokenInstruction.html) instruction.
681    pub fn process_toggle_freeze_account(
682        program_id: &Pubkey,
683        accounts: &[AccountInfo],
684        freeze: bool,
685    ) -> ProgramResult {
686        let account_info_iter = &mut accounts.iter();
687        let source_account_info = next_account_info(account_info_iter)?;
688        let mint_info = next_account_info(account_info_iter)?;
689        let authority_info = next_account_info(account_info_iter)?;
690
691        let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
692        if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() {
693            return Err(TokenError::InvalidState.into());
694        }
695        if source_account.is_native() {
696            return Err(TokenError::NativeNotSupported.into());
697        }
698        if !Self::cmp_pubkeys(mint_info.key, &source_account.mint) {
699            return Err(TokenError::MintMismatch.into());
700        }
701
702        let mint = Mint::unpack(&mint_info.data.borrow_mut())?;
703        match mint.freeze_authority {
704            COption::Some(authority) => Self::validate_owner(
705                program_id,
706                &authority,
707                authority_info,
708                account_info_iter.as_slice(),
709            ),
710            COption::None => Err(TokenError::MintCannotFreeze.into()),
711        }?;
712
713        source_account.state = if freeze {
714            AccountState::Frozen
715        } else {
716            AccountState::Initialized
717        };
718
719        Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
720
721        Ok(())
722    }
723
724    /// Processes a [`SyncNative`](enum.TokenInstruction.html) instruction
725    pub fn process_sync_native(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
726        let account_info_iter = &mut accounts.iter();
727        let native_account_info = next_account_info(account_info_iter)?;
728        Self::check_account_owner(program_id, native_account_info)?;
729
730        let mut native_account = Account::unpack(&native_account_info.data.borrow())?;
731
732        if let COption::Some(rent_exempt_reserve) = native_account.is_native {
733            let new_amount = native_account_info
734                .lamports()
735                .checked_sub(rent_exempt_reserve)
736                .ok_or(TokenError::Overflow)?;
737            if new_amount < native_account.amount {
738                return Err(TokenError::InvalidState.into());
739            }
740            native_account.amount = new_amount;
741        } else {
742            return Err(TokenError::NonNativeNotSupported.into());
743        }
744
745        Account::pack(native_account, &mut native_account_info.data.borrow_mut())?;
746        Ok(())
747    }
748
749    /// Processes a [`GetAccountDataSize`](enum.TokenInstruction.html)
750    /// instruction
751    pub fn process_get_account_data_size(
752        program_id: &Pubkey,
753        accounts: &[AccountInfo],
754    ) -> ProgramResult {
755        let account_info_iter = &mut accounts.iter();
756        // make sure the mint is valid
757        let mint_info = next_account_info(account_info_iter)?;
758        Self::check_account_owner(program_id, mint_info)?;
759        let _ = Mint::unpack(&mint_info.data.borrow())
760            .map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
761        set_return_data(&Account::LEN.to_le_bytes());
762        Ok(())
763    }
764
765    /// Processes an [`InitializeImmutableOwner`](enum.TokenInstruction.html)
766    /// instruction
767    pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult {
768        let account_info_iter = &mut accounts.iter();
769        let token_account_info = next_account_info(account_info_iter)?;
770        let account = Account::unpack_unchecked(&token_account_info.data.borrow())?;
771        if account.is_initialized() {
772            return Err(TokenError::AlreadyInUse.into());
773        }
774        msg!("Not supported");
775        Ok(())
776    }
777
778    /// Processes an [`AmountToUiAmount`](enum.TokenInstruction.html)
779    /// instruction
780    pub fn process_amount_to_ui_amount(
781        program_id: &Pubkey,
782        accounts: &[AccountInfo],
783        amount: u64,
784    ) -> ProgramResult {
785        let account_info_iter = &mut accounts.iter();
786        let mint_info = next_account_info(account_info_iter)?;
787        Self::check_account_owner(program_id, mint_info)?;
788
789        let mint = Mint::unpack(&mint_info.data.borrow_mut())
790            .map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
791        let ui_amount = amount_to_ui_amount_string_trimmed(amount, mint.decimals);
792
793        set_return_data(&ui_amount.into_bytes());
794        Ok(())
795    }
796
797    /// Processes an [`AmountToUiAmount`](enum.TokenInstruction.html)
798    /// instruction
799    pub fn process_ui_amount_to_amount(
800        program_id: &Pubkey,
801        accounts: &[AccountInfo],
802        ui_amount: &str,
803    ) -> ProgramResult {
804        let account_info_iter = &mut accounts.iter();
805        let mint_info = next_account_info(account_info_iter)?;
806        Self::check_account_owner(program_id, mint_info)?;
807
808        let mint = Mint::unpack(&mint_info.data.borrow_mut())
809            .map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
810        let amount = try_ui_amount_into_amount(ui_amount.to_string(), mint.decimals)?;
811
812        set_return_data(&amount.to_le_bytes());
813        Ok(())
814    }
815
816    /// Processes an [`Anchor`](enum.TokenInstruction.html) instruction
817    pub fn process_anchor(
818        program_id: &Pubkey,
819        accounts: &[AccountInfo],
820        txid: [u8; 32],
821        input_to_sign: InputToSign,
822    ) -> ProgramResult {
823        let account_info_iter = &mut accounts.iter();
824        let account_info = next_account_info(account_info_iter)?;
825        let owner_info = next_account_info(account_info_iter)?;
826
827        let account = Account::unpack(&account_info.data.borrow())?;
828
829        if account.is_frozen() {
830            return Err(TokenError::AccountFrozen.into());
831        }
832
833        Self::validate_owner(
834            program_id,
835            &account.owner,
836            owner_info,
837            account_info_iter.as_slice(),
838        )?;
839
840        set_input_to_sign(account_info_iter.as_slice(), txid, &[input_to_sign])?;
841
842        Ok(())
843    }
844
845    /// Processes an [`Instruction`](enum.Instruction.html).
846    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
847        let instruction = TokenInstruction::unpack(input)?;
848
849        match instruction {
850            TokenInstruction::InitializeMint {
851                decimals,
852                mint_authority,
853                freeze_authority,
854            } => {
855                msg!("Instruction: InitializeMint");
856                Self::process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
857            }
858            TokenInstruction::InitializeMint2 {
859                decimals,
860                mint_authority,
861                freeze_authority,
862            } => {
863                msg!("Instruction: InitializeMint2");
864                Self::process_initialize_mint2(accounts, decimals, mint_authority, freeze_authority)
865            }
866            TokenInstruction::InitializeAccount => {
867                msg!("Instruction: InitializeAccount");
868                Self::process_initialize_account(program_id, accounts)
869            }
870            TokenInstruction::InitializeAccount2 { owner } => {
871                msg!("Instruction: InitializeAccount2");
872                Self::process_initialize_account2(program_id, accounts, owner)
873            }
874            TokenInstruction::InitializeAccount3 { owner } => {
875                msg!("Instruction: InitializeAccount3");
876                Self::process_initialize_account3(program_id, accounts, owner)
877            }
878            TokenInstruction::InitializeMultisig { m } => {
879                msg!("Instruction: InitializeMultisig");
880                Self::process_initialize_multisig(accounts, m)
881            }
882            TokenInstruction::Transfer { amount } => {
883                msg!("Instruction: Transfer");
884                Self::process_transfer(program_id, accounts, amount, None)
885            }
886            TokenInstruction::Approve { amount } => {
887                msg!("Instruction: Approve");
888                Self::process_approve(program_id, accounts, amount, None)
889            }
890            TokenInstruction::Revoke => {
891                msg!("Instruction: Revoke");
892                Self::process_revoke(program_id, accounts)
893            }
894            TokenInstruction::SetAuthority {
895                authority_type,
896                new_authority,
897            } => {
898                msg!("Instruction: SetAuthority");
899                Self::process_set_authority(program_id, accounts, authority_type, new_authority)
900            }
901            TokenInstruction::MintTo { amount } => {
902                msg!("Instruction: MintTo");
903                Self::process_mint_to(program_id, accounts, amount, None)
904            }
905            TokenInstruction::Burn { amount } => {
906                msg!("Instruction: Burn");
907                Self::process_burn(program_id, accounts, amount, None)
908            }
909            TokenInstruction::CloseAccount => {
910                msg!("Instruction: CloseAccount");
911                Self::process_close_account(program_id, accounts)
912            }
913            TokenInstruction::FreezeAccount => {
914                msg!("Instruction: FreezeAccount");
915                Self::process_toggle_freeze_account(program_id, accounts, true)
916            }
917            TokenInstruction::ThawAccount => {
918                msg!("Instruction: ThawAccount");
919                Self::process_toggle_freeze_account(program_id, accounts, false)
920            }
921            TokenInstruction::TransferChecked { amount, decimals } => {
922                msg!("Instruction: TransferChecked");
923                Self::process_transfer(program_id, accounts, amount, Some(decimals))
924            }
925            TokenInstruction::ApproveChecked { amount, decimals } => {
926                msg!("Instruction: ApproveChecked");
927                Self::process_approve(program_id, accounts, amount, Some(decimals))
928            }
929            TokenInstruction::MintToChecked { amount, decimals } => {
930                msg!("Instruction: MintToChecked");
931                Self::process_mint_to(program_id, accounts, amount, Some(decimals))
932            }
933            TokenInstruction::BurnChecked { amount, decimals } => {
934                msg!("Instruction: BurnChecked");
935                Self::process_burn(program_id, accounts, amount, Some(decimals))
936            }
937            TokenInstruction::SyncNative => {
938                msg!("Instruction: SyncNative");
939                Self::process_sync_native(program_id, accounts)
940            }
941            TokenInstruction::GetAccountDataSize => {
942                msg!("Instruction: GetAccountDataSize");
943                Self::process_get_account_data_size(program_id, accounts)
944            }
945            TokenInstruction::InitializeImmutableOwner => {
946                msg!("Instruction: InitializeImmutableOwner");
947                Self::process_initialize_immutable_owner(accounts)
948            }
949            TokenInstruction::AmountToUiAmount { amount } => {
950                msg!("Instruction: AmountToUiAmount");
951                Self::process_amount_to_ui_amount(program_id, accounts, amount)
952            }
953            TokenInstruction::UiAmountToAmount { ui_amount } => {
954                msg!("Instruction: UiAmountToAmount");
955                Self::process_ui_amount_to_amount(program_id, accounts, ui_amount)
956            }
957            TokenInstruction::Anchor {
958                txid,
959                input_to_sign,
960            } => {
961                msg!("Instruction: Anchor");
962                Self::process_anchor(program_id, accounts, txid, input_to_sign)
963            }
964        }
965    }
966
967    /// Checks that the account is owned by the expected program
968    pub fn check_account_owner(program_id: &Pubkey, account_info: &AccountInfo) -> ProgramResult {
969        if !Self::cmp_pubkeys(program_id, account_info.owner) {
970            Err(ProgramError::IncorrectProgramId)
971        } else {
972            Ok(())
973        }
974    }
975
976    /// Checks two pubkeys for equality in a computationally cheap way using
977    /// `sol_memcmp`
978    pub fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool {
979        sol_memcmp(a.as_ref(), b.as_ref(), PUBKEY_BYTES) == 0
980    }
981
982    /// Validates owner(s) are present
983    pub fn validate_owner(
984        program_id: &Pubkey,
985        expected_owner: &Pubkey,
986        owner_account_info: &AccountInfo,
987        signers: &[AccountInfo],
988    ) -> ProgramResult {
989        if !Self::cmp_pubkeys(expected_owner, owner_account_info.key) {
990            return Err(TokenError::OwnerMismatch.into());
991        }
992        if Self::cmp_pubkeys(program_id, owner_account_info.owner)
993            && owner_account_info.data_len() == Multisig::get_packed_len()
994        {
995            let multisig = Multisig::unpack(&owner_account_info.data.borrow())?;
996            let mut num_signers = 0;
997            let mut matched = [false; MAX_SIGNERS];
998            for signer in signers.iter() {
999                for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() {
1000                    if Self::cmp_pubkeys(key, signer.key) && !matched[position] {
1001                        if !signer.is_signer {
1002                            return Err(ProgramError::MissingRequiredSignature);
1003                        }
1004                        matched[position] = true;
1005                        num_signers += 1;
1006                    }
1007                }
1008            }
1009            if num_signers < multisig.m {
1010                return Err(ProgramError::MissingRequiredSignature);
1011            }
1012            return Ok(());
1013        } else if !owner_account_info.is_signer {
1014            return Err(ProgramError::MissingRequiredSignature);
1015        }
1016        Ok(())
1017    }
1018}
1019
1020/// Helper function to mostly delete an account in a test environment.  We could
1021/// potentially muck around the bytes assuming that a vec is passed in, but that
1022/// would be more trouble than it's worth.
1023#[cfg(not(target_os = "solana"))]
1024fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1025    account_info.assign(&Pubkey::from_slice(b"11111111111111111111111111111111"));
1026    let mut account_data = account_info.data.borrow_mut();
1027    let data_len = account_data.len();
1028    arch_program::program_memory::sol_memset(*account_data, 0, data_len);
1029    Ok(())
1030}
1031
1032/// Helper function to totally delete an account on-chain
1033#[cfg(target_os = "solana")]
1034fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1035    account_info.assign(&Pubkey::system_program());
1036    account_info.realloc(0, false)
1037}