spl_token_2022/extension/transfer_hook/
processor.rs

1use {
2    crate::{
3        check_program_account,
4        error::TokenError,
5        extension::{
6            transfer_hook::{
7                instruction::{
8                    InitializeInstructionData, TransferHookInstruction, UpdateInstructionData,
9                },
10                TransferHook,
11            },
12            BaseStateWithExtensionsMut, PodStateWithExtensionsMut,
13        },
14        instruction::{decode_instruction_data, decode_instruction_type},
15        pod::PodMint,
16        processor::Processor,
17    },
18    solana_program::{
19        account_info::{next_account_info, AccountInfo},
20        entrypoint::ProgramResult,
21        msg,
22        program_error::ProgramError,
23        pubkey::Pubkey,
24    },
25    spl_pod::optional_keys::OptionalNonZeroPubkey,
26};
27
28fn process_initialize(
29    program_id: &Pubkey,
30    accounts: &[AccountInfo],
31    authority: &OptionalNonZeroPubkey,
32    transfer_hook_program_id: &OptionalNonZeroPubkey,
33) -> ProgramResult {
34    let account_info_iter = &mut accounts.iter();
35    let mint_account_info = next_account_info(account_info_iter)?;
36    let mut mint_data = mint_account_info.data.borrow_mut();
37    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack_uninitialized(&mut mint_data)?;
38
39    let extension = mint.init_extension::<TransferHook>(true)?;
40    extension.authority = *authority;
41
42    if let Some(transfer_hook_program_id) = Option::<Pubkey>::from(*transfer_hook_program_id) {
43        if transfer_hook_program_id == *program_id {
44            return Err(ProgramError::IncorrectProgramId);
45        }
46    } else if Option::<Pubkey>::from(*authority).is_none() {
47        msg!("The transfer hook extension requires at least an authority or a program id for initialization, neither was provided");
48        Err(TokenError::InvalidInstruction)?;
49    }
50    extension.program_id = *transfer_hook_program_id;
51    Ok(())
52}
53
54fn process_update(
55    program_id: &Pubkey,
56    accounts: &[AccountInfo],
57    new_program_id: &OptionalNonZeroPubkey,
58) -> ProgramResult {
59    let account_info_iter = &mut accounts.iter();
60    let mint_account_info = next_account_info(account_info_iter)?;
61    let owner_info = next_account_info(account_info_iter)?;
62    let owner_info_data_len = owner_info.data_len();
63
64    let mut mint_data = mint_account_info.data.borrow_mut();
65    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(&mut mint_data)?;
66    let extension = mint.get_extension_mut::<TransferHook>()?;
67    let authority =
68        Option::<Pubkey>::from(extension.authority).ok_or(TokenError::NoAuthorityExists)?;
69
70    Processor::validate_owner(
71        program_id,
72        &authority,
73        owner_info,
74        owner_info_data_len,
75        account_info_iter.as_slice(),
76    )?;
77
78    if let Some(new_program_id) = Option::<Pubkey>::from(*new_program_id) {
79        if new_program_id == *program_id {
80            return Err(ProgramError::IncorrectProgramId);
81        }
82    }
83
84    extension.program_id = *new_program_id;
85    Ok(())
86}
87
88pub(crate) fn process_instruction(
89    program_id: &Pubkey,
90    accounts: &[AccountInfo],
91    input: &[u8],
92) -> ProgramResult {
93    check_program_account(program_id)?;
94    match decode_instruction_type(input)? {
95        TransferHookInstruction::Initialize => {
96            msg!("TransferHookInstruction::Initialize");
97            let InitializeInstructionData {
98                authority,
99                program_id: transfer_hook_program_id,
100            } = decode_instruction_data(input)?;
101            process_initialize(program_id, accounts, authority, transfer_hook_program_id)
102        }
103        TransferHookInstruction::Update => {
104            msg!("TransferHookInstruction::Update");
105            let UpdateInstructionData {
106                program_id: transfer_hook_program_id,
107            } = decode_instruction_data(input)?;
108            process_update(program_id, accounts, transfer_hook_program_id)
109        }
110    }
111}