spl_token_group_interface/
instruction.rs1use {
4 bytemuck::{Pod, Zeroable},
5 miraland_program::{
6 instruction::{AccountMeta, Instruction},
7 program_error::ProgramError,
8 pubkey::Pubkey,
9 },
10 spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
11 spl_pod::{
12 bytemuck::{pod_bytes_of, pod_from_bytes},
13 optional_keys::OptionalNonZeroPubkey,
14 primitives::PodU32,
15 },
16};
17
18#[repr(C)]
20#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
21#[discriminator_hash_input("spl_token_group_interface:initialize_token_group")]
22pub struct InitializeGroup {
23 pub update_authority: OptionalNonZeroPubkey,
25 pub max_size: PodU32,
27}
28
29#[repr(C)]
31#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
32#[discriminator_hash_input("spl_token_group_interface:update_group_max_size")]
33pub struct UpdateGroupMaxSize {
34 pub max_size: PodU32,
36}
37
38#[repr(C)]
40#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
41#[discriminator_hash_input("spl_token_group_interface:update_authority")]
42pub struct UpdateGroupAuthority {
43 pub new_authority: OptionalNonZeroPubkey,
45}
46
47#[repr(C)]
49#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
50#[discriminator_hash_input("spl_token_group_interface:initialize_member")]
51pub struct InitializeMember;
52
53#[derive(Clone, Debug, PartialEq)]
55pub enum TokenGroupInstruction {
56 InitializeGroup(InitializeGroup),
67
68 UpdateGroupMaxSize(UpdateGroupMaxSize),
75
76 UpdateGroupAuthority(UpdateGroupAuthority),
83
84 InitializeMember(InitializeMember),
97}
98impl TokenGroupInstruction {
99 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
101 if input.len() < ArrayDiscriminator::LENGTH {
102 return Err(ProgramError::InvalidInstructionData);
103 }
104 let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
105 Ok(match discriminator {
106 InitializeGroup::SPL_DISCRIMINATOR_SLICE => {
107 let data = pod_from_bytes::<InitializeGroup>(rest)?;
108 Self::InitializeGroup(*data)
109 }
110 UpdateGroupMaxSize::SPL_DISCRIMINATOR_SLICE => {
111 let data = pod_from_bytes::<UpdateGroupMaxSize>(rest)?;
112 Self::UpdateGroupMaxSize(*data)
113 }
114 UpdateGroupAuthority::SPL_DISCRIMINATOR_SLICE => {
115 let data = pod_from_bytes::<UpdateGroupAuthority>(rest)?;
116 Self::UpdateGroupAuthority(*data)
117 }
118 InitializeMember::SPL_DISCRIMINATOR_SLICE => {
119 let data = pod_from_bytes::<InitializeMember>(rest)?;
120 Self::InitializeMember(*data)
121 }
122 _ => return Err(ProgramError::InvalidInstructionData),
123 })
124 }
125
126 pub fn pack(&self) -> Vec<u8> {
128 let mut buf = vec![];
129 match self {
130 Self::InitializeGroup(data) => {
131 buf.extend_from_slice(InitializeGroup::SPL_DISCRIMINATOR_SLICE);
132 buf.extend_from_slice(pod_bytes_of(data));
133 }
134 Self::UpdateGroupMaxSize(data) => {
135 buf.extend_from_slice(UpdateGroupMaxSize::SPL_DISCRIMINATOR_SLICE);
136 buf.extend_from_slice(pod_bytes_of(data));
137 }
138 Self::UpdateGroupAuthority(data) => {
139 buf.extend_from_slice(UpdateGroupAuthority::SPL_DISCRIMINATOR_SLICE);
140 buf.extend_from_slice(pod_bytes_of(data));
141 }
142 Self::InitializeMember(data) => {
143 buf.extend_from_slice(InitializeMember::SPL_DISCRIMINATOR_SLICE);
144 buf.extend_from_slice(pod_bytes_of(data));
145 }
146 };
147 buf
148 }
149}
150
151pub fn initialize_group(
153 program_id: &Pubkey,
154 group: &Pubkey,
155 mint: &Pubkey,
156 mint_authority: &Pubkey,
157 update_authority: Option<Pubkey>,
158 max_size: u32,
159) -> Instruction {
160 let update_authority = OptionalNonZeroPubkey::try_from(update_authority)
161 .expect("Failed to deserialize `Option<Pubkey>`");
162 let data = TokenGroupInstruction::InitializeGroup(InitializeGroup {
163 update_authority,
164 max_size: max_size.into(),
165 })
166 .pack();
167 Instruction {
168 program_id: *program_id,
169 accounts: vec![
170 AccountMeta::new(*group, false),
171 AccountMeta::new_readonly(*mint, false),
172 AccountMeta::new_readonly(*mint_authority, true),
173 ],
174 data,
175 }
176}
177
178pub fn update_group_max_size(
180 program_id: &Pubkey,
181 group: &Pubkey,
182 update_authority: &Pubkey,
183 max_size: u32,
184) -> Instruction {
185 let data = TokenGroupInstruction::UpdateGroupMaxSize(UpdateGroupMaxSize {
186 max_size: max_size.into(),
187 })
188 .pack();
189 Instruction {
190 program_id: *program_id,
191 accounts: vec![
192 AccountMeta::new(*group, false),
193 AccountMeta::new_readonly(*update_authority, true),
194 ],
195 data,
196 }
197}
198
199pub fn update_group_authority(
201 program_id: &Pubkey,
202 group: &Pubkey,
203 current_authority: &Pubkey,
204 new_authority: Option<Pubkey>,
205) -> Instruction {
206 let new_authority = OptionalNonZeroPubkey::try_from(new_authority)
207 .expect("Failed to deserialize `Option<Pubkey>`");
208 let data =
209 TokenGroupInstruction::UpdateGroupAuthority(UpdateGroupAuthority { new_authority }).pack();
210 Instruction {
211 program_id: *program_id,
212 accounts: vec![
213 AccountMeta::new(*group, false),
214 AccountMeta::new_readonly(*current_authority, true),
215 ],
216 data,
217 }
218}
219
220#[allow(clippy::too_many_arguments)]
222pub fn initialize_member(
223 program_id: &Pubkey,
224 member: &Pubkey,
225 member_mint: &Pubkey,
226 member_mint_authority: &Pubkey,
227 group: &Pubkey,
228 group_update_authority: &Pubkey,
229) -> Instruction {
230 let data = TokenGroupInstruction::InitializeMember(InitializeMember {}).pack();
231 Instruction {
232 program_id: *program_id,
233 accounts: vec![
234 AccountMeta::new(*member, false),
235 AccountMeta::new_readonly(*member_mint, false),
236 AccountMeta::new_readonly(*member_mint_authority, true),
237 AccountMeta::new(*group, false),
238 AccountMeta::new_readonly(*group_update_authority, true),
239 ],
240 data,
241 }
242}
243
244#[cfg(test)]
245mod test {
246 use {super::*, crate::NAMESPACE, miraland_program::hash};
247
248 #[repr(C)]
249 #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable, SplDiscriminate)]
250 #[discriminator_hash_input("mock_group")]
251 struct MockGroup;
252
253 fn instruction_pack_unpack<I>(instruction: TokenGroupInstruction, discriminator: &[u8], data: I)
254 where
255 I: core::fmt::Debug + PartialEq + Pod + Zeroable + SplDiscriminate,
256 {
257 let mut expect = vec![];
258 expect.extend_from_slice(discriminator.as_ref());
259 expect.extend_from_slice(pod_bytes_of(&data));
260 let packed = instruction.pack();
261 assert_eq!(packed, expect);
262 let unpacked = TokenGroupInstruction::unpack(&expect).unwrap();
263 assert_eq!(unpacked, instruction);
264 }
265
266 #[test]
267 fn initialize_group_pack() {
268 let data = InitializeGroup {
269 update_authority: OptionalNonZeroPubkey::default(),
270 max_size: 100.into(),
271 };
272 let instruction = TokenGroupInstruction::InitializeGroup(data);
273 let preimage = hash::hashv(&[format!("{NAMESPACE}:initialize_token_group").as_bytes()]);
274 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
275 instruction_pack_unpack::<InitializeGroup>(instruction, discriminator, data);
276 }
277
278 #[test]
279 fn update_group_max_size_pack() {
280 let data = UpdateGroupMaxSize {
281 max_size: 200.into(),
282 };
283 let instruction = TokenGroupInstruction::UpdateGroupMaxSize(data);
284 let preimage = hash::hashv(&[format!("{NAMESPACE}:update_group_max_size").as_bytes()]);
285 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
286 instruction_pack_unpack::<UpdateGroupMaxSize>(instruction, discriminator, data);
287 }
288
289 #[test]
290 fn update_authority_pack() {
291 let data = UpdateGroupAuthority {
292 new_authority: OptionalNonZeroPubkey::default(),
293 };
294 let instruction = TokenGroupInstruction::UpdateGroupAuthority(data);
295 let preimage = hash::hashv(&[format!("{NAMESPACE}:update_authority").as_bytes()]);
296 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
297 instruction_pack_unpack::<UpdateGroupAuthority>(instruction, discriminator, data);
298 }
299
300 #[test]
301 fn initialize_member_pack() {
302 let data = InitializeMember {};
303 let instruction = TokenGroupInstruction::InitializeMember(data);
304 let preimage = hash::hashv(&[format!("{NAMESPACE}:initialize_member").as_bytes()]);
305 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
306 instruction_pack_unpack::<InitializeMember>(instruction, discriminator, data);
307 }
308}