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