spl_transfer_hook_example/
processor.rs1use {
4 solana_program::{
5 account_info::{next_account_info, AccountInfo},
6 entrypoint::ProgramResult,
7 msg,
8 program::invoke_signed,
9 program_error::ProgramError,
10 pubkey::Pubkey,
11 system_instruction,
12 },
13 spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList},
14 spl_token_2022::{
15 extension::{
16 transfer_hook::TransferHookAccount, BaseStateWithExtensions, StateWithExtensions,
17 },
18 state::{Account, Mint},
19 },
20 spl_transfer_hook_interface::{
21 collect_extra_account_metas_signer_seeds,
22 error::TransferHookError,
23 get_extra_account_metas_address, get_extra_account_metas_address_and_bump_seed,
24 instruction::{ExecuteInstruction, TransferHookInstruction},
25 },
26};
27
28fn check_token_account_is_transferring(account_info: &AccountInfo) -> Result<(), ProgramError> {
29 let account_data = account_info.try_borrow_data()?;
30 let token_account = StateWithExtensions::<Account>::unpack(&account_data)?;
31 let extension = token_account.get_extension::<TransferHookAccount>()?;
32 if bool::from(extension.transferring) {
33 Ok(())
34 } else {
35 Err(TransferHookError::ProgramCalledOutsideOfTransfer.into())
36 }
37}
38
39pub fn process_execute(
41 program_id: &Pubkey,
42 accounts: &[AccountInfo],
43 amount: u64,
44) -> ProgramResult {
45 let account_info_iter = &mut accounts.iter();
46
47 let source_account_info = next_account_info(account_info_iter)?;
48 let mint_info = next_account_info(account_info_iter)?;
49 let destination_account_info = next_account_info(account_info_iter)?;
50 let _authority_info = next_account_info(account_info_iter)?;
51 let extra_account_metas_info = next_account_info(account_info_iter)?;
52
53 check_token_account_is_transferring(source_account_info)?;
55 check_token_account_is_transferring(destination_account_info)?;
56
57 let expected_validation_address = get_extra_account_metas_address(mint_info.key, program_id);
60 if expected_validation_address != *extra_account_metas_info.key {
61 return Err(ProgramError::InvalidSeeds);
62 }
63
64 let data = extra_account_metas_info.try_borrow_data()?;
65
66 ExtraAccountMetaList::check_account_infos::<ExecuteInstruction>(
67 accounts,
68 &TransferHookInstruction::Execute { amount }.pack(),
69 program_id,
70 &data,
71 )?;
72
73 Ok(())
74}
75
76pub fn process_initialize_extra_account_meta_list(
80 program_id: &Pubkey,
81 accounts: &[AccountInfo],
82 extra_account_metas: &[ExtraAccountMeta],
83) -> ProgramResult {
84 let account_info_iter = &mut accounts.iter();
85
86 let extra_account_metas_info = next_account_info(account_info_iter)?;
87 let mint_info = next_account_info(account_info_iter)?;
88 let authority_info = next_account_info(account_info_iter)?;
89 let _system_program_info = next_account_info(account_info_iter)?;
90
91 #[cfg(feature = "forbid-additional-mints")]
94 if *mint_info.key != crate::mint::id() {
95 return Err(ProgramError::InvalidArgument);
96 }
97
98 let mint_data = mint_info.try_borrow_data()?;
100 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
101 let mint_authority = mint
102 .base
103 .mint_authority
104 .ok_or(TransferHookError::MintHasNoMintAuthority)?;
105
106 if !authority_info.is_signer {
108 return Err(ProgramError::MissingRequiredSignature);
109 }
110 if *authority_info.key != mint_authority {
111 return Err(TransferHookError::IncorrectMintAuthority.into());
112 }
113
114 let (expected_validation_address, bump_seed) =
116 get_extra_account_metas_address_and_bump_seed(mint_info.key, program_id);
117 if expected_validation_address != *extra_account_metas_info.key {
118 return Err(ProgramError::InvalidSeeds);
119 }
120
121 let bump_seed = [bump_seed];
123 let signer_seeds = collect_extra_account_metas_signer_seeds(mint_info.key, &bump_seed);
124 let length = extra_account_metas.len();
125 let account_size = ExtraAccountMetaList::size_of(length)?;
126 invoke_signed(
127 &system_instruction::allocate(extra_account_metas_info.key, account_size as u64),
128 &[extra_account_metas_info.clone()],
129 &[&signer_seeds],
130 )?;
131 invoke_signed(
132 &system_instruction::assign(extra_account_metas_info.key, program_id),
133 &[extra_account_metas_info.clone()],
134 &[&signer_seeds],
135 )?;
136
137 let mut data = extra_account_metas_info.try_borrow_mut_data()?;
139 ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, extra_account_metas)?;
140
141 Ok(())
142}
143
144pub fn process_update_extra_account_meta_list(
148 program_id: &Pubkey,
149 accounts: &[AccountInfo],
150 extra_account_metas: &[ExtraAccountMeta],
151) -> ProgramResult {
152 let account_info_iter = &mut accounts.iter();
153
154 let extra_account_metas_info = next_account_info(account_info_iter)?;
155 let mint_info = next_account_info(account_info_iter)?;
156 let authority_info = next_account_info(account_info_iter)?;
157
158 let mint_data = mint_info.try_borrow_data()?;
160 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
161 let mint_authority = mint
162 .base
163 .mint_authority
164 .ok_or(TransferHookError::MintHasNoMintAuthority)?;
165
166 if !authority_info.is_signer {
168 return Err(ProgramError::MissingRequiredSignature);
169 }
170 if *authority_info.key != mint_authority {
171 return Err(TransferHookError::IncorrectMintAuthority.into());
172 }
173
174 let expected_validation_address = get_extra_account_metas_address(mint_info.key, program_id);
176 if expected_validation_address != *extra_account_metas_info.key {
177 return Err(ProgramError::InvalidSeeds);
178 }
179
180 let min_account_size = ExtraAccountMetaList::size_of(0)?;
182 let original_account_size = extra_account_metas_info.data_len();
183 if program_id != extra_account_metas_info.owner || original_account_size < min_account_size {
184 return Err(ProgramError::UninitializedAccount);
185 }
186
187 let length = extra_account_metas.len();
190 let account_size = ExtraAccountMetaList::size_of(length)?;
191 if account_size >= original_account_size {
192 extra_account_metas_info.realloc(account_size, false)?;
193 let mut data = extra_account_metas_info.try_borrow_mut_data()?;
194 ExtraAccountMetaList::update::<ExecuteInstruction>(&mut data, extra_account_metas)?;
195 } else {
196 {
197 let mut data = extra_account_metas_info.try_borrow_mut_data()?;
198 ExtraAccountMetaList::update::<ExecuteInstruction>(&mut data, extra_account_metas)?;
199 }
200 extra_account_metas_info.realloc(account_size, false)?;
201 }
202
203 Ok(())
204}
205
206pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
208 let instruction = TransferHookInstruction::unpack(input)?;
209
210 match instruction {
211 TransferHookInstruction::Execute { amount } => {
212 msg!("Instruction: Execute");
213 process_execute(program_id, accounts, amount)
214 }
215 TransferHookInstruction::InitializeExtraAccountMetaList {
216 extra_account_metas,
217 } => {
218 msg!("Instruction: InitializeExtraAccountMetaList");
219 process_initialize_extra_account_meta_list(program_id, accounts, &extra_account_metas)
220 }
221 TransferHookInstruction::UpdateExtraAccountMetaList {
222 extra_account_metas,
223 } => {
224 msg!("Instruction: UpdateExtraAccountMetaList");
225 process_update_extra_account_meta_list(program_id, accounts, &extra_account_metas)
226 }
227 }
228}