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