spl_token_group_example/
processor.rs1use {
4 solana_program::{
5 account_info::{next_account_info, AccountInfo},
6 entrypoint::ProgramResult,
7 msg,
8 program_error::ProgramError,
9 program_option::COption,
10 pubkey::Pubkey,
11 },
12 spl_pod::optional_keys::OptionalNonZeroPubkey,
13 spl_token_2022::{extension::StateWithExtensions, state::Mint},
14 spl_token_group_interface::{
15 error::TokenGroupError,
16 instruction::{
17 InitializeGroup, TokenGroupInstruction, UpdateGroupAuthority, UpdateGroupMaxSize,
18 },
19 state::{TokenGroup, TokenGroupMember},
20 },
21 spl_type_length_value::state::TlvStateMut,
22};
23
24fn check_update_authority(
25 update_authority_info: &AccountInfo,
26 expected_update_authority: &OptionalNonZeroPubkey,
27) -> Result<(), ProgramError> {
28 if !update_authority_info.is_signer {
29 return Err(ProgramError::MissingRequiredSignature);
30 }
31 let update_authority = Option::<Pubkey>::from(*expected_update_authority)
32 .ok_or(TokenGroupError::ImmutableGroup)?;
33 if update_authority != *update_authority_info.key {
34 return Err(TokenGroupError::IncorrectUpdateAuthority.into());
35 }
36 Ok(())
37}
38
39pub fn process_initialize_group(
42 _program_id: &Pubkey,
43 accounts: &[AccountInfo],
44 data: InitializeGroup,
45) -> ProgramResult {
46 let account_info_iter = &mut accounts.iter();
48
49 let group_info = next_account_info(account_info_iter)?;
55 let mint_info = next_account_info(account_info_iter)?;
56 let mint_authority_info = next_account_info(account_info_iter)?;
57
58 {
59 let mint_data = mint_info.try_borrow_data()?;
63 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
64
65 if !mint_authority_info.is_signer {
66 return Err(ProgramError::MissingRequiredSignature);
67 }
68 if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) {
69 return Err(TokenGroupError::IncorrectMintAuthority.into());
70 }
71 }
72
73 let mut buffer = group_info.try_borrow_mut_data()?;
75 let mut state = TlvStateMut::unpack(&mut buffer)?;
76 let (group, _) = state.init_value::<TokenGroup>(false)?;
77 *group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
78
79 Ok(())
80}
81
82pub fn process_update_group_max_size(
86 _program_id: &Pubkey,
87 accounts: &[AccountInfo],
88 data: UpdateGroupMaxSize,
89) -> ProgramResult {
90 let account_info_iter = &mut accounts.iter();
91
92 let group_info = next_account_info(account_info_iter)?;
97 let update_authority_info = next_account_info(account_info_iter)?;
98
99 let mut buffer = group_info.try_borrow_mut_data()?;
100 let mut state = TlvStateMut::unpack(&mut buffer)?;
101 let group = state.get_first_value_mut::<TokenGroup>()?;
102
103 check_update_authority(update_authority_info, &group.update_authority)?;
104
105 group.update_max_size(data.max_size.into())?;
107
108 Ok(())
109}
110
111pub fn process_update_group_authority(
115 _program_id: &Pubkey,
116 accounts: &[AccountInfo],
117 data: UpdateGroupAuthority,
118) -> ProgramResult {
119 let account_info_iter = &mut accounts.iter();
120
121 let group_info = next_account_info(account_info_iter)?;
126 let update_authority_info = next_account_info(account_info_iter)?;
127
128 let mut buffer = group_info.try_borrow_mut_data()?;
129 let mut state = TlvStateMut::unpack(&mut buffer)?;
130 let group = state.get_first_value_mut::<TokenGroup>()?;
131
132 check_update_authority(update_authority_info, &group.update_authority)?;
133
134 group.update_authority = data.new_authority;
136
137 Ok(())
138}
139
140pub fn process_initialize_member(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
143 let account_info_iter = &mut accounts.iter();
150
151 let member_info = next_account_info(account_info_iter)?;
159 let member_mint_info = next_account_info(account_info_iter)?;
160 let member_mint_authority_info = next_account_info(account_info_iter)?;
161 let group_info = next_account_info(account_info_iter)?;
162 let group_update_authority_info = next_account_info(account_info_iter)?;
163
164 {
166 let member_mint_data = member_mint_info.try_borrow_data()?;
170 let member_mint = StateWithExtensions::<Mint>::unpack(&member_mint_data)?;
171
172 if !member_mint_authority_info.is_signer {
173 return Err(ProgramError::MissingRequiredSignature);
174 }
175 if member_mint.base.mint_authority.as_ref() != COption::Some(member_mint_authority_info.key)
176 {
177 return Err(TokenGroupError::IncorrectMintAuthority.into());
178 }
179 }
180
181 if member_info.key == group_info.key {
183 return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
184 }
185
186 let mut buffer = group_info.try_borrow_mut_data()?;
188 let mut state = TlvStateMut::unpack(&mut buffer)?;
189 let group = state.get_first_value_mut::<TokenGroup>()?;
190
191 check_update_authority(group_update_authority_info, &group.update_authority)?;
192 let member_number = group.increment_size()?;
193
194 let mut buffer = member_info.try_borrow_mut_data()?;
196 let mut state = TlvStateMut::unpack(&mut buffer)?;
197 let (member, _) = state.init_value::<TokenGroupMember>(false)?;
200 *member = TokenGroupMember::new(member_mint_info.key, group_info.key, member_number);
201
202 Ok(())
203}
204
205pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
207 let instruction = TokenGroupInstruction::unpack(input)?;
208 match instruction {
209 TokenGroupInstruction::InitializeGroup(data) => {
210 msg!("Instruction: InitializeGroup");
211 process_initialize_group(program_id, accounts, data)
212 }
213 TokenGroupInstruction::UpdateGroupMaxSize(data) => {
214 msg!("Instruction: UpdateGroupMaxSize");
215 process_update_group_max_size(program_id, accounts, data)
216 }
217 TokenGroupInstruction::UpdateGroupAuthority(data) => {
218 msg!("Instruction: UpdateGroupAuthority");
219 process_update_group_authority(program_id, accounts, data)
220 }
221 TokenGroupInstruction::InitializeMember(_) => {
222 msg!("Instruction: InitializeMember");
223 process_initialize_member(program_id, accounts)
224 }
225 }
226}