spl_token_2022/extension/token_group/
processor.rs1use {
4 crate::{
5 check_program_account,
6 error::TokenError,
7 extension::{
8 alloc_and_serialize, group_member_pointer::GroupMemberPointer,
9 group_pointer::GroupPointer, BaseStateWithExtensions, BaseStateWithExtensionsMut,
10 PodStateWithExtensions, PodStateWithExtensionsMut,
11 },
12 pod::{PodCOption, PodMint},
13 },
14 solana_program::{
15 account_info::{next_account_info, AccountInfo},
16 entrypoint::ProgramResult,
17 msg,
18 program_error::ProgramError,
19 pubkey::Pubkey,
20 },
21 spl_pod::optional_keys::OptionalNonZeroPubkey,
22 spl_token_group_interface::{
23 error::TokenGroupError,
24 instruction::{
25 InitializeGroup, TokenGroupInstruction, UpdateGroupAuthority, UpdateGroupMaxSize,
26 },
27 state::{TokenGroup, TokenGroupMember},
28 },
29};
30
31fn check_update_authority(
32 update_authority_info: &AccountInfo,
33 expected_update_authority: &OptionalNonZeroPubkey,
34) -> Result<(), ProgramError> {
35 if !update_authority_info.is_signer {
36 return Err(ProgramError::MissingRequiredSignature);
37 }
38 let update_authority = Option::<Pubkey>::from(*expected_update_authority)
39 .ok_or(TokenGroupError::ImmutableGroup)?;
40 if update_authority != *update_authority_info.key {
41 return Err(TokenGroupError::IncorrectUpdateAuthority.into());
42 }
43 Ok(())
44}
45
46pub fn process_initialize_group(
49 _program_id: &Pubkey,
50 accounts: &[AccountInfo],
51 data: InitializeGroup,
52) -> ProgramResult {
53 let account_info_iter = &mut accounts.iter();
54
55 let group_info = next_account_info(account_info_iter)?;
56 let mint_info = next_account_info(account_info_iter)?;
57 let mint_authority_info = next_account_info(account_info_iter)?;
58
59 if group_info.key != mint_info.key {
62 msg!("Group configurations for a mint must be initialized in the mint itself.");
63 return Err(TokenError::MintMismatch.into());
64 }
65
66 {
68 check_program_account(mint_info.owner)?;
71 let mint_data = mint_info.try_borrow_data()?;
72 let mint = PodStateWithExtensions::<PodMint>::unpack(&mint_data)?;
73
74 if !mint_authority_info.is_signer {
75 return Err(ProgramError::MissingRequiredSignature);
76 }
77 if mint.base.mint_authority != PodCOption::some(*mint_authority_info.key) {
78 return Err(TokenGroupError::IncorrectMintAuthority.into());
79 }
80
81 if mint.get_extension::<GroupPointer>().is_err() {
82 msg!(
83 "A mint with group configurations must have the group-pointer extension \
84 initialized"
85 );
86 return Err(TokenError::InvalidExtensionCombination.into());
87 }
88 }
89
90 let group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
93 alloc_and_serialize::<PodMint, TokenGroup>(group_info, &group, false)?;
94
95 Ok(())
96}
97
98pub fn process_update_group_max_size(
102 _program_id: &Pubkey,
103 accounts: &[AccountInfo],
104 data: UpdateGroupMaxSize,
105) -> ProgramResult {
106 let account_info_iter = &mut accounts.iter();
107
108 let group_info = next_account_info(account_info_iter)?;
109 let update_authority_info = next_account_info(account_info_iter)?;
110
111 let mut buffer = group_info.try_borrow_mut_data()?;
112 let mut state = PodStateWithExtensionsMut::<PodMint>::unpack(&mut buffer)?;
113 let group = state.get_extension_mut::<TokenGroup>()?;
114
115 check_update_authority(update_authority_info, &group.update_authority)?;
116
117 group.update_max_size(data.max_size.into())?;
118
119 Ok(())
120}
121
122pub fn process_update_group_authority(
126 _program_id: &Pubkey,
127 accounts: &[AccountInfo],
128 data: UpdateGroupAuthority,
129) -> ProgramResult {
130 let account_info_iter = &mut accounts.iter();
131
132 let group_info = next_account_info(account_info_iter)?;
133 let update_authority_info = next_account_info(account_info_iter)?;
134
135 let mut buffer = group_info.try_borrow_mut_data()?;
136 let mut state = PodStateWithExtensionsMut::<PodMint>::unpack(&mut buffer)?;
137 let group = state.get_extension_mut::<TokenGroup>()?;
138
139 check_update_authority(update_authority_info, &group.update_authority)?;
140
141 group.update_authority = data.new_authority;
142
143 Ok(())
144}
145
146pub fn process_initialize_member(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
149 let account_info_iter = &mut accounts.iter();
150
151 let member_info = next_account_info(account_info_iter)?;
152 let member_mint_info = next_account_info(account_info_iter)?;
153 let member_mint_authority_info = next_account_info(account_info_iter)?;
154 let group_info = next_account_info(account_info_iter)?;
155 let group_update_authority_info = next_account_info(account_info_iter)?;
156
157 if member_info.key != member_mint_info.key {
160 msg!("Group member configurations for a mint must be initialized in the mint itself.");
161 return Err(TokenError::MintMismatch.into());
162 }
163
164 {
166 check_program_account(member_mint_info.owner)?;
169 let member_mint_data = member_mint_info.try_borrow_data()?;
170 let member_mint = PodStateWithExtensions::<PodMint>::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 != PodCOption::some(*member_mint_authority_info.key) {
176 return Err(TokenGroupError::IncorrectMintAuthority.into());
177 }
178
179 if member_mint.get_extension::<GroupMemberPointer>().is_err() {
180 msg!(
181 "A mint with group member configurations must have the group-member-pointer \
182 extension initialized"
183 );
184 return Err(TokenError::InvalidExtensionCombination.into());
185 }
186 }
187
188 if member_info.key == group_info.key {
190 return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
191 }
192
193 let mut buffer = group_info.try_borrow_mut_data()?;
195 let mut state = PodStateWithExtensionsMut::<PodMint>::unpack(&mut buffer)?;
196 let group = state.get_extension_mut::<TokenGroup>()?;
197
198 check_update_authority(group_update_authority_info, &group.update_authority)?;
199 let member_number = group.increment_size()?;
200
201 let member = TokenGroupMember::new(member_mint_info.key, group_info.key, member_number);
203 alloc_and_serialize::<PodMint, TokenGroupMember>(member_info, &member, false)?;
204
205 Ok(())
206}
207
208pub fn process_instruction(
210 program_id: &Pubkey,
211 accounts: &[AccountInfo],
212 instruction: TokenGroupInstruction,
213) -> ProgramResult {
214 match instruction {
215 TokenGroupInstruction::InitializeGroup(data) => {
216 msg!("TokenGroupInstruction: InitializeGroup");
217 process_initialize_group(program_id, accounts, data)
218 }
219 TokenGroupInstruction::UpdateGroupMaxSize(data) => {
220 msg!("TokenGroupInstruction: UpdateGroupMaxSize");
221 process_update_group_max_size(program_id, accounts, data)
222 }
223 TokenGroupInstruction::UpdateGroupAuthority(data) => {
224 msg!("TokenGroupInstruction: UpdateGroupAuthority");
225 process_update_group_authority(program_id, accounts, data)
226 }
227 TokenGroupInstruction::InitializeMember(_) => {
228 msg!("TokenGroupInstruction: InitializeMember");
229 process_initialize_member(program_id, accounts)
230 }
231 }
232}