Skip to main content

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