spl_token_collection/
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::{
14        extension::{
15            metadata_pointer::MetadataPointer, BaseStateWithExtensions, StateWithExtensions,
16        },
17        state::Mint,
18    },
19    spl_token_group_interface::{
20        error::TokenGroupError,
21        instruction::{InitializeGroup, TokenGroupInstruction},
22        state::{TokenGroup, TokenGroupMember},
23    },
24    spl_token_metadata_interface::state::TokenMetadata,
25    spl_type_length_value::state::TlvStateMut,
26};
27
28fn check_update_authority(
29    update_authority_info: &AccountInfo,
30    expected_update_authority: &OptionalNonZeroPubkey,
31) -> ProgramResult {
32    if !update_authority_info.is_signer {
33        return Err(ProgramError::MissingRequiredSignature);
34    }
35    let update_authority = Option::<Pubkey>::from(*expected_update_authority)
36        .ok_or(TokenGroupError::ImmutableGroup)?;
37    if update_authority != *update_authority_info.key {
38        return Err(TokenGroupError::IncorrectUpdateAuthority.into());
39    }
40    Ok(())
41}
42
43/// Checks that a mint is valid and contains metadata.
44fn check_mint_and_metadata(
45    mint_info: &AccountInfo,
46    mint_authority_info: &AccountInfo,
47) -> ProgramResult {
48    let mint_data = mint_info.try_borrow_data()?;
49    let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
50
51    if !mint_authority_info.is_signer {
52        return Err(ProgramError::MissingRequiredSignature);
53    }
54    if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) {
55        return Err(TokenGroupError::IncorrectMintAuthority.into());
56    }
57
58    let metadata_pointer = mint.get_extension::<MetadataPointer>()?;
59    let metadata_pointer_address = Option::<Pubkey>::from(metadata_pointer.metadata_address);
60
61    // If the metadata is inside the mint (Token2022), make sure it contains
62    // valid TokenMetadata
63    if metadata_pointer_address == Some(*mint_info.key) {
64        mint.get_variable_len_extension::<TokenMetadata>()?;
65    }
66
67    Ok(())
68}
69
70/// Processes an [InitializeGroup](enum.GroupInterfaceInstruction.html)
71/// instruction to initialize a collection.
72pub fn process_initialize_collection(
73    _program_id: &Pubkey,
74    accounts: &[AccountInfo],
75    data: InitializeGroup,
76) -> ProgramResult {
77    let account_info_iter = &mut accounts.iter();
78
79    let collection_info = next_account_info(account_info_iter)?;
80    let mint_info = next_account_info(account_info_iter)?;
81    let mint_authority_info = next_account_info(account_info_iter)?;
82
83    check_mint_and_metadata(mint_info, mint_authority_info)?;
84
85    // Initialize the collection
86    let mut buffer = collection_info.try_borrow_mut_data()?;
87    let mut state = TlvStateMut::unpack(&mut buffer)?;
88    let (collection, _) = state.init_value::<TokenGroup>(false)?;
89    *collection = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
90
91    Ok(())
92}
93
94/// Processes an [InitializeMember](enum.GroupInterfaceInstruction.html)
95/// instruction
96pub fn process_initialize_collection_member(
97    _program_id: &Pubkey,
98    accounts: &[AccountInfo],
99) -> ProgramResult {
100    let account_info_iter = &mut accounts.iter();
101
102    let member_info = next_account_info(account_info_iter)?;
103    let mint_info = next_account_info(account_info_iter)?;
104    let mint_authority_info = next_account_info(account_info_iter)?;
105    let collection_info = next_account_info(account_info_iter)?;
106    let collection_update_authority_info = next_account_info(account_info_iter)?;
107
108    check_mint_and_metadata(mint_info, mint_authority_info)?;
109
110    if member_info.key == collection_info.key {
111        return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
112    }
113
114    let mut buffer = collection_info.try_borrow_mut_data()?;
115    let mut state = TlvStateMut::unpack(&mut buffer)?;
116    let collection = state.get_first_value_mut::<TokenGroup>()?;
117
118    check_update_authority(
119        collection_update_authority_info,
120        &collection.update_authority,
121    )?;
122    let member_number = collection.increment_size()?;
123
124    let mut buffer = member_info.try_borrow_mut_data()?;
125    let mut state = TlvStateMut::unpack(&mut buffer)?;
126
127    // This program uses `allow_repetition: true` because the same mint can be
128    // a member of multiple collections.
129    let (member, _) = state.init_value::<TokenGroupMember>(/* allow_repetition */ true)?;
130    *member = TokenGroupMember::new(mint_info.key, collection_info.key, member_number);
131
132    Ok(())
133}
134
135/// Processes an `SplTokenGroupInstruction`
136pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
137    let instruction = TokenGroupInstruction::unpack(input)?;
138    match instruction {
139        TokenGroupInstruction::InitializeGroup(data) => {
140            msg!("Instruction: InitializeCollection");
141            process_initialize_collection(program_id, accounts, data)
142        }
143        TokenGroupInstruction::UpdateGroupMaxSize(data) => {
144            msg!("Instruction: UpdateCollectionMaxSize");
145            // Same functionality as the example program
146            spl_token_group_example::processor::process_update_group_max_size(
147                program_id, accounts, data,
148            )
149        }
150        TokenGroupInstruction::UpdateGroupAuthority(data) => {
151            msg!("Instruction: UpdateCollectionAuthority");
152            // Same functionality as the example program
153            spl_token_group_example::processor::process_update_group_authority(
154                program_id, accounts, data,
155            )
156        }
157        TokenGroupInstruction::InitializeMember(_) => {
158            msg!("Instruction: InitializeCollectionMember");
159            process_initialize_collection_member(program_id, accounts)
160        }
161    }
162}