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        Self::validate_anchor_signer(account_info.key, &input_to_sign)?;
863
864        let buf = get_transaction_to_sign();
865        let arr: [u8; 4] = buf
866            .get(..4)
867            .and_then(|s| s.try_into().ok())
868            .ok_or(ProgramError::InvalidInstructionData)?;
869        let tx_len = u32::from_le_bytes(arr) as usize;
870        let tx_data = buf
871            .get(4..4 + tx_len)
872            .ok_or(ProgramError::InvalidInstructionData)?;
873        let tx: arch_bitcoin::Transaction = arch_bitcoin::consensus::deserialize(tx_data)
874            .map_err(|_| ProgramError::InvalidInstructionData)?;
875
876        let txid = tx.compute_txid();
877        let mut txid_bytes: [u8; 32] = txid.as_raw_hash().to_byte_array();
878        txid_bytes.reverse();
879
880        set_input_to_sign(accounts, txid_bytes, &[input_to_sign])?;
881
882        Ok(())
883    }
884
885    /// Processes an [`Instruction`](enum.Instruction.html).
886    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
887        let instruction = TokenInstruction::unpack(input)?;
888
889        match instruction {
890            TokenInstruction::InitializeMint {
891                decimals,
892                mint_authority,
893                freeze_authority,
894            } => {
895                msg!("Instruction: InitializeMint");
896                Self::process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
897            }
898            TokenInstruction::InitializeMint2 {
899                decimals,
900                mint_authority,
901                freeze_authority,
902            } => {
903                msg!("Instruction: InitializeMint2");
904                Self::process_initialize_mint2(accounts, decimals, mint_authority, freeze_authority)
905            }
906            TokenInstruction::InitializeAccount => {
907                msg!("Instruction: InitializeAccount");
908                Self::process_initialize_account(program_id, accounts)
909            }
910            TokenInstruction::InitializeAccount2 { owner } => {
911                msg!("Instruction: InitializeAccount2");
912                Self::process_initialize_account2(program_id, accounts, owner)
913            }
914            TokenInstruction::InitializeAccount3 { owner } => {
915                msg!("Instruction: InitializeAccount3");
916                Self::process_initialize_account3(program_id, accounts, owner)
917            }
918            TokenInstruction::InitializeMultisig { m } => {
919                msg!("Instruction: InitializeMultisig");
920                Self::process_initialize_multisig(accounts, m)
921            }
922            TokenInstruction::Transfer { amount } => {
923                msg!("Instruction: Transfer");
924                Self::process_transfer(program_id, accounts, amount, None)
925            }
926            TokenInstruction::Approve { amount } => {
927                msg!("Instruction: Approve");
928                Self::process_approve(program_id, accounts, amount, None)
929            }
930            TokenInstruction::Revoke => {
931                msg!("Instruction: Revoke");
932                Self::process_revoke(program_id, accounts)
933            }
934            TokenInstruction::SetAuthority {
935                authority_type,
936                new_authority,
937            } => {
938                msg!("Instruction: SetAuthority");
939                Self::process_set_authority(program_id, accounts, authority_type, new_authority)
940            }
941            TokenInstruction::MintTo { amount } => {
942                msg!("Instruction: MintTo");
943                Self::process_mint_to(program_id, accounts, amount, None)
944            }
945            TokenInstruction::Burn { amount } => {
946                msg!("Instruction: Burn");
947                Self::process_burn(program_id, accounts, amount, None)
948            }
949            TokenInstruction::CloseAccount => {
950                msg!("Instruction: CloseAccount");
951                Self::process_close_account(program_id, accounts)
952            }
953            TokenInstruction::FreezeAccount => {
954                msg!("Instruction: FreezeAccount");
955                Self::process_toggle_freeze_account(program_id, accounts, true)
956            }
957            TokenInstruction::ThawAccount => {
958                msg!("Instruction: ThawAccount");
959                Self::process_toggle_freeze_account(program_id, accounts, false)
960            }
961            TokenInstruction::TransferChecked { amount, decimals } => {
962                msg!("Instruction: TransferChecked");
963                Self::process_transfer(program_id, accounts, amount, Some(decimals))
964            }
965            TokenInstruction::ApproveChecked { amount, decimals } => {
966                msg!("Instruction: ApproveChecked");
967                Self::process_approve(program_id, accounts, amount, Some(decimals))
968            }
969            TokenInstruction::MintToChecked { amount, decimals } => {
970                msg!("Instruction: MintToChecked");
971                Self::process_mint_to(program_id, accounts, amount, Some(decimals))
972            }
973            TokenInstruction::BurnChecked { amount, decimals } => {
974                msg!("Instruction: BurnChecked");
975                Self::process_burn(program_id, accounts, amount, Some(decimals))
976            }
977            TokenInstruction::SyncNative => {
978                msg!("Instruction: SyncNative");
979                Self::process_sync_native(program_id, accounts)
980            }
981            TokenInstruction::GetAccountDataSize => {
982                msg!("Instruction: GetAccountDataSize");
983                Self::process_get_account_data_size(program_id, accounts)
984            }
985            TokenInstruction::InitializeImmutableOwner => {
986                msg!("Instruction: InitializeImmutableOwner");
987                Self::process_initialize_immutable_owner(accounts)
988            }
989            TokenInstruction::AmountToUiAmount { amount } => {
990                msg!("Instruction: AmountToUiAmount");
991                Self::process_amount_to_ui_amount(program_id, accounts, amount)
992            }
993            TokenInstruction::UiAmountToAmount { ui_amount } => {
994                msg!("Instruction: UiAmountToAmount");
995                Self::process_ui_amount_to_amount(program_id, accounts, ui_amount)
996            }
997            TokenInstruction::Anchor { input_to_sign } => {
998                msg!("Instruction: Anchor");
999                Self::process_anchor(program_id, accounts, input_to_sign)
1000            }
1001        }
1002    }
1003
1004    /// Checks that the account is owned by the expected program
1005    pub fn check_account_owner(program_id: &Pubkey, account_info: &AccountInfo) -> ProgramResult {
1006        if !Self::cmp_pubkeys(program_id, account_info.owner) {
1007            Err(ProgramError::IncorrectProgramId)
1008        } else {
1009            Ok(())
1010        }
1011    }
1012
1013    /// Checks two pubkeys for equality in a computationally cheap way using
1014    /// `sol_memcmp`
1015    pub fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool {
1016        sol_memcmp(a.as_ref(), b.as_ref(), PUBKEY_BYTES) == 0
1017    }
1018
1019    /// Validates owner(s) are present
1020    pub fn validate_owner(
1021        program_id: &Pubkey,
1022        expected_owner: &Pubkey,
1023        owner_account_info: &AccountInfo,
1024        signers: &[AccountInfo],
1025    ) -> ProgramResult {
1026        if !Self::cmp_pubkeys(expected_owner, owner_account_info.key) {
1027            return Err(TokenError::OwnerMismatch.into());
1028        }
1029        if Self::cmp_pubkeys(program_id, owner_account_info.owner)
1030            && owner_account_info.data_len() == Multisig::get_packed_len()
1031        {
1032            let multisig = Multisig::unpack(&owner_account_info.data.borrow())?;
1033            let mut num_signers = 0;
1034            let mut matched = [false; MAX_SIGNERS];
1035            for signer in signers.iter() {
1036                for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() {
1037                    if Self::cmp_pubkeys(key, signer.key) && !matched[position] {
1038                        if !signer.is_signer {
1039                            return Err(ProgramError::MissingRequiredSignature);
1040                        }
1041                        matched[position] = true;
1042                        num_signers += 1;
1043                    }
1044                }
1045            }
1046            if num_signers < multisig.m {
1047                return Err(ProgramError::MissingRequiredSignature);
1048            }
1049            return Ok(());
1050        } else if !owner_account_info.is_signer {
1051            return Err(ProgramError::MissingRequiredSignature);
1052        }
1053        Ok(())
1054    }
1055
1056    /// Validates that the signer of an [`Anchor`](enum.TokenInstruction.html)
1057    /// input is the account whose authority was validated.
1058    ///
1059    /// The signer selects the tweaked key that spends the anchored UTXO. If
1060    /// it were not bound to the validated account, a caller could request a
1061    /// FROST signature spending a UTXO anchored to any token-program-owned
1062    /// account it does not control (the syscall accepts program-owned
1063    /// accounts as signers without a signature).
1064    fn validate_anchor_signer(account: &Pubkey, input_to_sign: &InputToSign) -> ProgramResult {
1065        if input_to_sign.signer != *account {
1066            return Err(TokenError::OwnerMismatch.into());
1067        }
1068        Ok(())
1069    }
1070}
1071
1072/// Helper function to mostly delete an account in a test environment.  We could
1073/// potentially muck around the bytes assuming that a vec is passed in, but that
1074/// would be more trouble than it's worth.
1075#[cfg(not(target_os = "solana"))]
1076fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1077    account_info.assign(&Pubkey::from_slice(b"11111111111111111111111111111111"));
1078    let mut account_data = account_info.data.borrow_mut();
1079    let data_len = account_data.len();
1080    arch_program::program_memory::sol_memset(*account_data, 0, data_len);
1081    Ok(())
1082}
1083
1084/// Helper function to totally delete an account on-chain
1085#[cfg(target_os = "solana")]
1086fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1087    account_info.assign(&Pubkey::system_program());
1088    account_info.realloc(0, false)
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093    use super::*;
1094    use arch_program::utxo::UtxoMeta;
1095
1096    #[test]
1097    fn validate_anchor_signer_accepts_bound_signer() {
1098        let key = Pubkey::new_unique();
1099        assert!(Processor::validate_anchor_signer(
1100            &key,
1101            &InputToSign {
1102                index: 3,
1103                signer: key
1104            }
1105        )
1106        .is_ok());
1107    }
1108
1109    #[test]
1110    fn validate_anchor_signer_rejects_unbound_signer() {
1111        let key = Pubkey::new_unique();
1112        let err = Processor::validate_anchor_signer(
1113            &key,
1114            &InputToSign {
1115                index: 0,
1116                signer: Pubkey::new_unique(),
1117            },
1118        )
1119        .unwrap_err();
1120        assert_eq!(err, TokenError::OwnerMismatch.into());
1121    }
1122
1123    /// Regression test for the audit finding: process_anchor must not forward
1124    /// a signer other than the account whose authority was validated, or a
1125    /// caller can have the network FROST-sign a spend of a UTXO anchored to
1126    /// a victim's token-program-owned account.
1127    #[test]
1128    fn process_anchor_rejects_signer_not_matching_validated_account() {
1129        let program_id = crate::id();
1130        let owner_key = Pubkey::new_unique();
1131        let account_key = Pubkey::new_unique();
1132        let victim_key = Pubkey::new_unique();
1133        let utxo = UtxoMeta::from([0; 32], 0);
1134
1135        // Token account owned by the token program, with authority `owner_key`.
1136        let mut account_data = vec![0u8; Account::LEN];
1137        Account {
1138            mint: Pubkey::new_unique(),
1139            owner: owner_key,
1140            amount: 1,
1141            delegate: COption::None,
1142            state: AccountState::Initialized,
1143            is_native: COption::None,
1144            delegated_amount: 0,
1145            close_authority: COption::None,
1146        }
1147        .pack_into_slice(&mut account_data);
1148        let mut account_lamports = 1u64;
1149        let account_info = AccountInfo::new(
1150            &account_key,
1151            &mut account_lamports,
1152            &mut account_data,
1153            &program_id,
1154            &utxo,
1155            false,
1156            true,
1157            false,
1158        );
1159
1160        // The caller's authority (signs for its own account).
1161        let mut owner_data = Vec::new();
1162        let mut owner_lamports = 1u64;
1163        let owner_info = AccountInfo::new(
1164            &owner_key,
1165            &mut owner_lamports,
1166            &mut owner_data,
1167            &SYSTEM_PROGRAM_ID,
1168            &utxo,
1169            true,
1170            false,
1171            false,
1172        );
1173
1174        let accounts = vec![account_info, owner_info];
1175        let err = Processor::process_anchor(
1176            &program_id,
1177            &accounts,
1178            InputToSign {
1179                index: 0,
1180                signer: victim_key,
1181            },
1182        )
1183        .unwrap_err();
1184        assert_eq!(err, TokenError::OwnerMismatch.into());
1185    }
1186}