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