spl_transfer_hook_interface/
instruction.rs1use {
4 solana_instruction::{AccountMeta, Instruction},
5 solana_program_error::ProgramError,
6 solana_pubkey::Pubkey,
7 spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
8 spl_pod::{bytemuck::pod_slice_to_bytes, list::ListView},
9 spl_tlv_account_resolution::account::ExtraAccountMeta,
10 std::convert::TryInto,
11};
12
13const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
14
15#[repr(C)]
17#[derive(Clone, Debug, PartialEq)]
18pub enum TransferHookInstruction {
19 Execute {
31 amount: u64,
33 },
34
35 InitializeExtraAccountMetaList {
45 extra_account_metas: Vec<ExtraAccountMeta>,
47 },
48 UpdateExtraAccountMetaList {
57 extra_account_metas: Vec<ExtraAccountMeta>,
60 },
61}
62#[derive(SplDiscriminate)]
66#[discriminator_hash_input("spl-transfer-hook-interface:execute")]
67pub struct ExecuteInstruction;
68
69#[derive(SplDiscriminate)]
72#[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")]
73pub struct InitializeExtraAccountMetaListInstruction;
74
75#[derive(SplDiscriminate)]
78#[discriminator_hash_input("spl-transfer-hook-interface:update-extra-account-metas")]
79pub struct UpdateExtraAccountMetaListInstruction;
80
81impl TransferHookInstruction {
82 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
85 if input.len() < ArrayDiscriminator::LENGTH {
86 return Err(ProgramError::InvalidInstructionData);
87 }
88 let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
89 Ok(match discriminator {
90 ExecuteInstruction::SPL_DISCRIMINATOR_SLICE => {
91 let amount = rest
92 .get(..8)
93 .and_then(|slice| slice.try_into().ok())
94 .map(u64::from_le_bytes)
95 .ok_or(ProgramError::InvalidInstructionData)?;
96 Self::Execute { amount }
97 }
98 InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
99 let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
100 let extra_account_metas = list_view.to_vec();
101 Self::InitializeExtraAccountMetaList {
102 extra_account_metas,
103 }
104 }
105 UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
106 let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
107 let extra_account_metas = list_view.to_vec();
108 Self::UpdateExtraAccountMetaList {
109 extra_account_metas,
110 }
111 }
112 _ => return Err(ProgramError::InvalidInstructionData),
113 })
114 }
115
116 pub fn pack(&self) -> Vec<u8> {
119 let mut buf = vec![];
120 match self {
121 Self::Execute { amount } => {
122 buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE);
123 buf.extend_from_slice(&amount.to_le_bytes());
124 }
125 Self::InitializeExtraAccountMetaList {
126 extra_account_metas,
127 } => {
128 buf.extend_from_slice(
129 InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
130 );
131 buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
132 buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
133 }
134 Self::UpdateExtraAccountMetaList {
135 extra_account_metas,
136 } => {
137 buf.extend_from_slice(
138 UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
139 );
140 buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
141 buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
142 }
143 };
144 buf
145 }
146}
147
148#[allow(clippy::too_many_arguments)]
151pub fn execute_with_extra_account_metas(
152 program_id: &Pubkey,
153 source_pubkey: &Pubkey,
154 mint_pubkey: &Pubkey,
155 destination_pubkey: &Pubkey,
156 authority_pubkey: &Pubkey,
157 validate_state_pubkey: &Pubkey,
158 additional_accounts: &[AccountMeta],
159 amount: u64,
160) -> Instruction {
161 let mut instruction = execute(
162 program_id,
163 source_pubkey,
164 mint_pubkey,
165 destination_pubkey,
166 authority_pubkey,
167 amount,
168 );
169 instruction
170 .accounts
171 .push(AccountMeta::new_readonly(*validate_state_pubkey, false));
172 instruction.accounts.extend_from_slice(additional_accounts);
173 instruction
174}
175
176#[allow(clippy::too_many_arguments)]
178pub fn execute(
179 program_id: &Pubkey,
180 source_pubkey: &Pubkey,
181 mint_pubkey: &Pubkey,
182 destination_pubkey: &Pubkey,
183 authority_pubkey: &Pubkey,
184 amount: u64,
185) -> Instruction {
186 let data = TransferHookInstruction::Execute { amount }.pack();
187 let accounts = vec![
188 AccountMeta::new_readonly(*source_pubkey, false),
189 AccountMeta::new_readonly(*mint_pubkey, false),
190 AccountMeta::new_readonly(*destination_pubkey, false),
191 AccountMeta::new_readonly(*authority_pubkey, false),
192 ];
193 Instruction {
194 program_id: *program_id,
195 accounts,
196 data,
197 }
198}
199
200pub fn initialize_extra_account_meta_list(
202 program_id: &Pubkey,
203 extra_account_metas_pubkey: &Pubkey,
204 mint_pubkey: &Pubkey,
205 authority_pubkey: &Pubkey,
206 extra_account_metas: &[ExtraAccountMeta],
207) -> Instruction {
208 let data = TransferHookInstruction::InitializeExtraAccountMetaList {
209 extra_account_metas: extra_account_metas.to_vec(),
210 }
211 .pack();
212
213 let accounts = vec![
214 AccountMeta::new(*extra_account_metas_pubkey, false),
215 AccountMeta::new_readonly(*mint_pubkey, false),
216 AccountMeta::new_readonly(*authority_pubkey, true),
217 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
218 ];
219
220 Instruction {
221 program_id: *program_id,
222 accounts,
223 data,
224 }
225}
226
227pub fn update_extra_account_meta_list(
229 program_id: &Pubkey,
230 extra_account_metas_pubkey: &Pubkey,
231 mint_pubkey: &Pubkey,
232 authority_pubkey: &Pubkey,
233 extra_account_metas: &[ExtraAccountMeta],
234) -> Instruction {
235 let data = TransferHookInstruction::UpdateExtraAccountMetaList {
236 extra_account_metas: extra_account_metas.to_vec(),
237 }
238 .pack();
239
240 let accounts = vec![
241 AccountMeta::new(*extra_account_metas_pubkey, false),
242 AccountMeta::new_readonly(*mint_pubkey, false),
243 AccountMeta::new_readonly(*authority_pubkey, true),
244 ];
245
246 Instruction {
247 program_id: *program_id,
248 accounts,
249 data,
250 }
251}
252
253#[cfg(test)]
254mod test {
255 use {super::*, crate::NAMESPACE, solana_program::hash, spl_pod::bytemuck::pod_from_bytes};
256
257 #[test]
258 fn system_program_id() {
259 assert_eq!(solana_system_interface::program::id(), SYSTEM_PROGRAM_ID);
260 }
261
262 #[test]
263 fn validate_packing() {
264 let amount = 111_111_111;
265 let check = TransferHookInstruction::Execute { amount };
266 let packed = check.pack();
267 let preimage = hash::hashv(&[format!("{NAMESPACE}:execute").as_bytes()]);
270 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
271 let mut expect = vec![];
272 expect.extend_from_slice(discriminator.as_ref());
273 expect.extend_from_slice(&amount.to_le_bytes());
274 assert_eq!(packed, expect);
275 let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
276 assert_eq!(unpacked, check);
277 }
278
279 #[test]
280 fn initialize_validation_pubkeys_packing() {
281 let extra_meta_len_bytes = &[
282 1, 0, 0, 0, ];
284 let extra_meta_bytes = &[
285 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
287 1, 1, 1, 0, 0, ];
291 let extra_account_metas =
292 vec![*pod_from_bytes::<ExtraAccountMeta>(extra_meta_bytes).unwrap()];
293 let check = TransferHookInstruction::InitializeExtraAccountMetaList {
294 extra_account_metas,
295 };
296 let packed = check.pack();
297 let preimage =
300 hash::hashv(&[format!("{NAMESPACE}:initialize-extra-account-metas").as_bytes()]);
301 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
302 let mut expect = vec![];
303 expect.extend_from_slice(discriminator.as_ref());
304 expect.extend_from_slice(extra_meta_len_bytes);
305 expect.extend_from_slice(extra_meta_bytes);
306 assert_eq!(packed, expect);
307 let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
308 assert_eq!(unpacked, check);
309 }
310}