spl_token_group_interface/
instruction.rs1use {
4 alloc::{vec, vec::Vec},
5 bytemuck::{Pod, Zeroable},
6 solana_address::Address,
7 solana_instruction::{account_meta::AccountMeta, Instruction},
8 solana_nullable::MaybeNull,
9 solana_program_error::ProgramError,
10 solana_zero_copy::unaligned::U64,
11 spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
12};
13
14#[repr(C)]
16#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
17#[discriminator_hash_input("spl_token_group_interface:initialize_token_group")]
18pub struct InitializeGroup {
19 pub update_authority: MaybeNull<Address>,
21 pub max_size: U64,
23}
24
25#[repr(C)]
27#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
28#[discriminator_hash_input("spl_token_group_interface:update_group_max_size")]
29pub struct UpdateGroupMaxSize {
30 pub max_size: U64,
32}
33
34#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
37#[discriminator_hash_input("spl_token_group_interface:update_authority")]
38pub struct UpdateGroupAuthority {
39 pub new_authority: MaybeNull<Address>,
41}
42
43#[repr(C)]
45#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, SplDiscriminate)]
46#[discriminator_hash_input("spl_token_group_interface:initialize_member")]
47pub struct InitializeMember;
48
49#[derive(Clone, Debug, PartialEq)]
51pub enum TokenGroupInstruction {
52 InitializeGroup(InitializeGroup),
63
64 UpdateGroupMaxSize(UpdateGroupMaxSize),
71
72 UpdateGroupAuthority(UpdateGroupAuthority),
79
80 InitializeMember(InitializeMember),
93}
94impl TokenGroupInstruction {
95 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
97 if input.len() < ArrayDiscriminator::LENGTH {
98 return Err(ProgramError::InvalidInstructionData);
99 }
100 let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
101 Ok(match discriminator {
102 InitializeGroup::SPL_DISCRIMINATOR_SLICE => {
103 let data = bytemuck::try_from_bytes::<InitializeGroup>(rest)
104 .map_err(|_| ProgramError::InvalidArgument)?;
105 Self::InitializeGroup(*data)
106 }
107 UpdateGroupMaxSize::SPL_DISCRIMINATOR_SLICE => {
108 let data = bytemuck::try_from_bytes::<UpdateGroupMaxSize>(rest)
109 .map_err(|_| ProgramError::InvalidArgument)?;
110 Self::UpdateGroupMaxSize(*data)
111 }
112 UpdateGroupAuthority::SPL_DISCRIMINATOR_SLICE => {
113 let data = bytemuck::try_from_bytes::<UpdateGroupAuthority>(rest)
114 .map_err(|_| ProgramError::InvalidArgument)?;
115 Self::UpdateGroupAuthority(*data)
116 }
117 InitializeMember::SPL_DISCRIMINATOR_SLICE => {
118 let data = bytemuck::try_from_bytes::<InitializeMember>(rest)
119 .map_err(|_| ProgramError::InvalidArgument)?;
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(bytemuck::bytes_of(data));
133 }
134 Self::UpdateGroupMaxSize(data) => {
135 buf.extend_from_slice(UpdateGroupMaxSize::SPL_DISCRIMINATOR_SLICE);
136 buf.extend_from_slice(bytemuck::bytes_of(data));
137 }
138 Self::UpdateGroupAuthority(data) => {
139 buf.extend_from_slice(UpdateGroupAuthority::SPL_DISCRIMINATOR_SLICE);
140 buf.extend_from_slice(bytemuck::bytes_of(data));
141 }
142 Self::InitializeMember(data) => {
143 buf.extend_from_slice(InitializeMember::SPL_DISCRIMINATOR_SLICE);
144 buf.extend_from_slice(bytemuck::bytes_of(data));
145 }
146 };
147 buf
148 }
149}
150
151pub fn initialize_group(
153 program_id: &Address,
154 group: &Address,
155 mint: &Address,
156 mint_authority: &Address,
157 update_authority: Option<Address>,
158 max_size: u64,
159) -> Instruction {
160 let update_authority = MaybeNull::<Address>::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: &Address,
181 group: &Address,
182 update_authority: &Address,
183 max_size: u64,
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: &Address,
202 group: &Address,
203 current_authority: &Address,
204 new_authority: Option<Address>,
205) -> Instruction {
206 let new_authority = MaybeNull::<Address>::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: &Address,
224 member: &Address,
225 member_mint: &Address,
226 member_mint_authority: &Address,
227 group: &Address,
228 group_update_authority: &Address,
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, alloc::format, solana_sha256_hasher::hashv};
247
248 fn instruction_pack_unpack<I>(instruction: TokenGroupInstruction, discriminator: &[u8], data: I)
249 where
250 I: core::fmt::Debug + PartialEq + Pod + Zeroable + SplDiscriminate,
251 {
252 let mut expect = vec![];
253 expect.extend_from_slice(discriminator.as_ref());
254 expect.extend_from_slice(bytemuck::bytes_of(&data));
255 let packed = instruction.pack();
256 assert_eq!(packed, expect);
257 let unpacked = TokenGroupInstruction::unpack(&expect).unwrap();
258 assert_eq!(unpacked, instruction);
259 }
260
261 #[test]
262 fn initialize_group_pack() {
263 let data = InitializeGroup {
264 update_authority: MaybeNull::<Address>::default(),
265 max_size: 100.into(),
266 };
267 let instruction = TokenGroupInstruction::InitializeGroup(data);
268 let preimage = hashv(&[format!("{NAMESPACE}:initialize_token_group").as_bytes()]);
269 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
270 instruction_pack_unpack::<InitializeGroup>(instruction, discriminator, data);
271 }
272
273 #[test]
274 fn update_group_max_size_pack() {
275 let data = UpdateGroupMaxSize {
276 max_size: 200.into(),
277 };
278 let instruction = TokenGroupInstruction::UpdateGroupMaxSize(data);
279 let preimage = hashv(&[format!("{NAMESPACE}:update_group_max_size").as_bytes()]);
280 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
281 instruction_pack_unpack::<UpdateGroupMaxSize>(instruction, discriminator, data);
282 }
283
284 #[test]
285 fn update_authority_pack() {
286 let data = UpdateGroupAuthority {
287 new_authority: MaybeNull::<Address>::default(),
288 };
289 let instruction = TokenGroupInstruction::UpdateGroupAuthority(data);
290 let preimage = hashv(&[format!("{NAMESPACE}:update_authority").as_bytes()]);
291 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
292 instruction_pack_unpack::<UpdateGroupAuthority>(instruction, discriminator, data);
293 }
294
295 #[test]
296 fn initialize_member_pack() {
297 let data = InitializeMember {};
298 let instruction = TokenGroupInstruction::InitializeMember(data);
299 let preimage = hashv(&[format!("{NAMESPACE}:initialize_member").as_bytes()]);
300 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
301 instruction_pack_unpack::<InitializeMember>(instruction, discriminator, data);
302 }
303}