spl_token_group_example/
processor.rs

1//! Program state processor
2
3use {
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
39/// Processes an [InitializeGroup](enum.GroupInterfaceInstruction.html)
40/// instruction
41pub fn process_initialize_group(
42    _program_id: &Pubkey,
43    accounts: &[AccountInfo],
44    data: InitializeGroup,
45) -> ProgramResult {
46    // Assumes one has already created a mint for the group.
47    let account_info_iter = &mut accounts.iter();
48
49    // Accounts expected by this instruction:
50    //
51    //   0. `[w]`   Group
52    //   1. `[]`    Mint
53    //   2. `[s]`   Mint authority
54    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        // IMPORTANT: this example program is designed to work with any
60        // program that implements the SPL token interface, so there is no
61        // ownership check on the mint account.
62        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    // Allocate a TLV entry for the space and write it in
74    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
82/// Processes an
83/// [UpdateGroupMaxSize](enum.GroupInterfaceInstruction.html)
84/// instruction
85pub 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    // Accounts expected by this instruction:
93    //
94    //   0. `[w]`   Group
95    //   1. `[s]`   Update authority
96    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    // Update the max size (zero-copy)
106    group.update_max_size(data.max_size.into())?;
107
108    Ok(())
109}
110
111/// Processes an
112/// [UpdateGroupAuthority](enum.GroupInterfaceInstruction.html)
113/// instruction
114pub 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    // Accounts expected by this instruction:
122    //
123    //   0. `[w]`   Group
124    //   1. `[s]`   Current update authority
125    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    // Update the authority (zero-copy)
135    group.update_authority = data.new_authority;
136
137    Ok(())
138}
139
140/// Processes an [InitializeMember](enum.GroupInterfaceInstruction.html)
141/// instruction
142pub fn process_initialize_member(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
143    // For this group, we are going to assume the group has been
144    // initialized, and we're also assuming a mint has been created for the
145    // member.
146    // Group members in this example can have their own separate
147    // metadata that differs from the metadata of the group, since
148    // metadata is not involved here.
149    let account_info_iter = &mut accounts.iter();
150
151    // Accounts expected by this instruction:
152    //
153    //   0. `[w]`   Member
154    //   1. `[]`    Member Mint
155    //   2. `[s]`   Member Mint authority
156    //   3. `[w]`   Group
157    //   4. `[s]`   Group update authority
158    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    // Mint checks on the member
165    {
166        // IMPORTANT: this example program is designed to work with any
167        // program that implements the SPL token interface, so there is no
168        // ownership check on the mint account.
169        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    // Make sure the member account is not the same as the group account
182    if member_info.key == group_info.key {
183        return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
184    }
185
186    // Increment the size of the group
187    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    // Allocate a TLV entry for the space and write it in
195    let mut buffer = member_info.try_borrow_mut_data()?;
196    let mut state = TlvStateMut::unpack(&mut buffer)?;
197    // Note if `allow_repetition: true` is instead used here, one can initialize
198    // the same token as a member of multiple groups!
199    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
205/// Processes an `SplTokenGroupInstruction`
206pub 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}