spl_transfer_hook_interface/
offchain.rs1pub use spl_tlv_account_resolution::state::{AccountDataResult, AccountFetchError};
4use {
5 crate::{
6 error::TransferHookError,
7 get_extra_account_metas_address,
8 instruction::{execute, ExecuteInstruction},
9 },
10 solana_instruction::{AccountMeta, Instruction},
11 solana_program_error::ProgramError,
12 solana_pubkey::Pubkey,
13 spl_tlv_account_resolution::state::ExtraAccountMetaList,
14 std::future::Future,
15};
16
17#[allow(clippy::too_many_arguments)]
48pub async fn add_extra_account_metas_for_execute<F, Fut>(
49 instruction: &mut Instruction,
50 program_id: &Pubkey,
51 source_pubkey: &Pubkey,
52 mint_pubkey: &Pubkey,
53 destination_pubkey: &Pubkey,
54 authority_pubkey: &Pubkey,
55 amount: u64,
56 fetch_account_data_fn: F,
57) -> Result<(), AccountFetchError>
58where
59 F: Fn(Pubkey) -> Fut,
60 Fut: Future<Output = AccountDataResult>,
61{
62 let validate_state_pubkey = get_extra_account_metas_address(mint_pubkey, program_id);
63 let validate_state_data = fetch_account_data_fn(validate_state_pubkey)
64 .await?
65 .ok_or(ProgramError::InvalidAccountData)?;
66
67 if [
69 source_pubkey,
70 mint_pubkey,
71 destination_pubkey,
72 authority_pubkey,
73 ]
74 .iter()
75 .any(|&key| !instruction.accounts.iter().any(|meta| meta.pubkey == *key))
76 {
77 Err(TransferHookError::IncorrectAccount)?;
78 }
79
80 let mut execute_instruction = execute(
81 program_id,
82 source_pubkey,
83 mint_pubkey,
84 destination_pubkey,
85 authority_pubkey,
86 amount,
87 );
88 execute_instruction
89 .accounts
90 .push(AccountMeta::new_readonly(validate_state_pubkey, false));
91
92 ExtraAccountMetaList::add_to_instruction::<ExecuteInstruction, _, _>(
93 &mut execute_instruction,
94 fetch_account_data_fn,
95 &validate_state_data,
96 )
97 .await?;
98
99 instruction
101 .accounts
102 .extend_from_slice(&execute_instruction.accounts[5..]);
103
104 instruction
106 .accounts
107 .push(AccountMeta::new_readonly(*program_id, false));
108 instruction
109 .accounts
110 .push(AccountMeta::new_readonly(validate_state_pubkey, false));
111
112 Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117 use {
118 super::*,
119 spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed},
120 tokio,
121 };
122
123 const PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
124 const EXTRA_META_1: Pubkey = Pubkey::new_from_array([2u8; 32]);
125 const EXTRA_META_2: Pubkey = Pubkey::new_from_array([3u8; 32]);
126
127 async fn mock_fetch_account_data_fn(_address: Pubkey) -> AccountDataResult {
129 let extra_metas = vec![
130 ExtraAccountMeta::new_with_pubkey(&EXTRA_META_1, true, false).unwrap(),
131 ExtraAccountMeta::new_with_pubkey(&EXTRA_META_2, true, false).unwrap(),
132 ExtraAccountMeta::new_with_seeds(
133 &[
134 Seed::AccountKey { index: 0 }, Seed::AccountKey { index: 2 }, Seed::AccountKey { index: 4 }, ],
138 false,
139 true,
140 )
141 .unwrap(),
142 ExtraAccountMeta::new_with_seeds(
143 &[
144 Seed::InstructionData {
145 index: 8,
146 length: 8,
147 }, Seed::AccountKey { index: 2 }, Seed::AccountKey { index: 5 }, Seed::AccountKey { index: 7 }, ],
152 false,
153 true,
154 )
155 .unwrap(),
156 ];
157 let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
158 let mut data = vec![0u8; account_size];
159 ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &extra_metas)?;
160 Ok(Some(data))
161 }
162
163 #[tokio::test]
164 async fn test_add_extra_account_metas_for_execute() {
165 let source = Pubkey::new_unique();
166 let mint = Pubkey::new_unique();
167 let destination = Pubkey::new_unique();
168 let authority = Pubkey::new_unique();
169 let amount = 100u64;
170
171 let validate_state_pubkey = get_extra_account_metas_address(&mint, &PROGRAM_ID);
172 let extra_meta_3_pubkey = Pubkey::find_program_address(
173 &[
174 source.as_ref(),
175 destination.as_ref(),
176 validate_state_pubkey.as_ref(),
177 ],
178 &PROGRAM_ID,
179 )
180 .0;
181 let extra_meta_4_pubkey = Pubkey::find_program_address(
182 &[
183 amount.to_le_bytes().as_ref(),
184 destination.as_ref(),
185 EXTRA_META_1.as_ref(),
186 extra_meta_3_pubkey.as_ref(),
187 ],
188 &PROGRAM_ID,
189 )
190 .0;
191
192 let mut instruction = Instruction::new_with_bytes(
194 PROGRAM_ID,
195 &[],
196 vec![
197 AccountMeta::new_readonly(mint, false),
199 AccountMeta::new(destination, false),
200 AccountMeta::new_readonly(authority, true),
201 ],
202 );
203 assert_eq!(
204 add_extra_account_metas_for_execute(
205 &mut instruction,
206 &PROGRAM_ID,
207 &source,
208 &mint,
209 &destination,
210 &authority,
211 amount,
212 mock_fetch_account_data_fn,
213 )
214 .await
215 .unwrap_err()
216 .downcast::<TransferHookError>()
217 .unwrap(),
218 Box::new(TransferHookError::IncorrectAccount)
219 );
220
221 let mut instruction = Instruction::new_with_bytes(
223 PROGRAM_ID,
224 &[],
225 vec![
226 AccountMeta::new(source, false),
227 AccountMeta::new_readonly(mint, false),
228 AccountMeta::new(destination, false),
229 AccountMeta::new_readonly(authority, true),
230 ],
231 );
232 add_extra_account_metas_for_execute(
233 &mut instruction,
234 &PROGRAM_ID,
235 &source,
236 &mint,
237 &destination,
238 &authority,
239 amount,
240 mock_fetch_account_data_fn,
241 )
242 .await
243 .unwrap();
244
245 let check_metas = [
246 AccountMeta::new(source, false),
247 AccountMeta::new_readonly(mint, false),
248 AccountMeta::new(destination, false),
249 AccountMeta::new_readonly(authority, true),
250 AccountMeta::new_readonly(EXTRA_META_1, true),
251 AccountMeta::new_readonly(EXTRA_META_2, true),
252 AccountMeta::new(extra_meta_3_pubkey, false),
253 AccountMeta::new(extra_meta_4_pubkey, false),
254 AccountMeta::new_readonly(PROGRAM_ID, false),
255 AccountMeta::new_readonly(validate_state_pubkey, false),
256 ];
257
258 assert_eq!(instruction.accounts, check_metas);
259 }
260}