spl_token_2022/extension/token_group/
processor.rs

1//! Token-group processor
2
3use {
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
46/// Processes a [`InitializeGroup`](enum.TokenGroupInstruction.html)
47/// instruction.
48pub 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    // check that the mint and group accounts are the same, since the group
60    // extension should only describe itself
61    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    // scope the mint authority check, since the mint is in the same account!
67    {
68        // This check isn't really needed since we'll be writing into the account,
69        // but auditors like it
70        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    // Allocate a TLV entry for the space and write it in
91    // Assumes that there's enough SOL for the new rent-exemption
92    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
98/// Processes an
99/// [`UpdateGroupMaxSize`](enum.TokenGroupInstruction.html)
100/// instruction
101pub 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
122/// Processes an
123/// [`UpdateGroupAuthority`](enum.TokenGroupInstruction.html)
124/// instruction
125pub 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
146/// Processes an [`InitializeMember`](enum.TokenGroupInstruction.html)
147/// instruction
148pub 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    // check that the mint and member accounts are the same, since the member
158    // extension should only describe itself
159    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    // scope the mint authority check, since the mint is in the same account!
165    {
166        // This check isn't really needed since we'll be writing into the account,
167        // but auditors like it
168        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    // Make sure the member mint is not the same as the group mint
189    if member_info.key == group_info.key {
190        return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
191    }
192
193    // Increment the size of the group
194    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    // Allocate a TLV entry for the space and write it in
202    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
208/// Processes an [`Instruction`](enum.Instruction.html).
209pub 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}