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 {
256 super::*, crate::NAMESPACE, solana_sha256_hasher::hashv, spl_pod::bytemuck::pod_from_bytes,
257 };
258
259 #[test]
260 fn system_program_id() {
261 assert_eq!(solana_system_interface::program::id(), SYSTEM_PROGRAM_ID);
262 }
263
264 #[test]
265 fn validate_packing() {
266 let amount = 111_111_111;
267 let check = TransferHookInstruction::Execute { amount };
268 let packed = check.pack();
269 let preimage = hashv(&[format!("{NAMESPACE}:execute").as_bytes()]);
272 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
273 let mut expect = vec![];
274 expect.extend_from_slice(discriminator.as_ref());
275 expect.extend_from_slice(&amount.to_le_bytes());
276 assert_eq!(packed, expect);
277 let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
278 assert_eq!(unpacked, check);
279 }
280
281 #[test]
282 fn initialize_validation_pubkeys_packing() {
283 let extra_meta_len_bytes = &[
284 1, 0, 0, 0, ];
286 let extra_meta_bytes = &[
287 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,
289 1, 1, 1, 0, 0, ];
293 let extra_account_metas =
294 vec![*pod_from_bytes::<ExtraAccountMeta>(extra_meta_bytes).unwrap()];
295 let check = TransferHookInstruction::InitializeExtraAccountMetaList {
296 extra_account_metas,
297 };
298 let packed = check.pack();
299 let preimage = hashv(&[format!("{NAMESPACE}:initialize-extra-account-metas").as_bytes()]);
302 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
303 let mut expect = vec![];
304 expect.extend_from_slice(discriminator.as_ref());
305 expect.extend_from_slice(extra_meta_len_bytes);
306 expect.extend_from_slice(extra_meta_bytes);
307 assert_eq!(packed, expect);
308 let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
309 assert_eq!(unpacked, check);
310 }
311}