spl_token_metadata_example/
processor.rs

1//! Program state processor
2
3use {
4    solana_program::{
5        account_info::{next_account_info, AccountInfo},
6        borsh1::get_instance_packed_len,
7        entrypoint::ProgramResult,
8        msg,
9        program::set_return_data,
10        program_error::ProgramError,
11        program_option::COption,
12        pubkey::Pubkey,
13    },
14    spl_pod::optional_keys::OptionalNonZeroPubkey,
15    spl_token_2022::{extension::StateWithExtensions, state::Mint},
16    spl_token_metadata_interface::{
17        error::TokenMetadataError,
18        instruction::{
19            Emit, Initialize, RemoveKey, TokenMetadataInstruction, UpdateAuthority, UpdateField,
20        },
21        state::TokenMetadata,
22    },
23    spl_type_length_value::state::{
24        realloc_and_pack_first_variable_len, TlvState, TlvStateBorrowed, TlvStateMut,
25    },
26};
27
28fn check_update_authority(
29    update_authority_info: &AccountInfo,
30    expected_update_authority: &OptionalNonZeroPubkey,
31) -> Result<(), ProgramError> {
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(TokenMetadataError::ImmutableMetadata)?;
37    if update_authority != *update_authority_info.key {
38        return Err(TokenMetadataError::IncorrectUpdateAuthority.into());
39    }
40    Ok(())
41}
42
43/// Processes a [Initialize](enum.TokenMetadataInstruction.html) instruction.
44pub fn process_initialize(
45    _program_id: &Pubkey,
46    accounts: &[AccountInfo],
47    data: Initialize,
48) -> ProgramResult {
49    let account_info_iter = &mut accounts.iter();
50
51    let metadata_info = next_account_info(account_info_iter)?;
52    let update_authority_info = next_account_info(account_info_iter)?;
53    let mint_info = next_account_info(account_info_iter)?;
54    let mint_authority_info = next_account_info(account_info_iter)?;
55
56    // scope the mint authority check, in case the mint is in the same account!
57    {
58        // IMPORTANT: this example metadata program is designed to work with any
59        // program that implements the SPL token interface, so there is no
60        // ownership check on the mint account.
61        let mint_data = mint_info.try_borrow_data()?;
62        let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
63
64        if !mint_authority_info.is_signer {
65            return Err(ProgramError::MissingRequiredSignature);
66        }
67        if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) {
68            return Err(TokenMetadataError::IncorrectMintAuthority.into());
69        }
70    }
71
72    // get the required size, assumes that there's enough space for the entry
73    let update_authority = OptionalNonZeroPubkey::try_from(Some(*update_authority_info.key))?;
74    let token_metadata = TokenMetadata {
75        name: data.name,
76        symbol: data.symbol,
77        uri: data.uri,
78        update_authority,
79        mint: *mint_info.key,
80        ..Default::default()
81    };
82    let instance_size = get_instance_packed_len(&token_metadata)?;
83
84    // allocate a TLV entry for the space and write it in
85    let mut buffer = metadata_info.try_borrow_mut_data()?;
86    let mut state = TlvStateMut::unpack(&mut buffer)?;
87    state.alloc::<TokenMetadata>(instance_size, false)?;
88    state.pack_first_variable_len_value(&token_metadata)?;
89
90    Ok(())
91}
92
93/// Processes an [UpdateField](enum.TokenMetadataInstruction.html) instruction.
94pub fn process_update_field(
95    _program_id: &Pubkey,
96    accounts: &[AccountInfo],
97    data: UpdateField,
98) -> ProgramResult {
99    let account_info_iter = &mut accounts.iter();
100    let metadata_info = next_account_info(account_info_iter)?;
101    let update_authority_info = next_account_info(account_info_iter)?;
102
103    // deserialize the metadata, but scope the data borrow since we'll probably
104    // realloc the account
105    let mut token_metadata = {
106        let buffer = metadata_info.try_borrow_data()?;
107        let state = TlvStateBorrowed::unpack(&buffer)?;
108        state.get_first_variable_len_value::<TokenMetadata>()?
109    };
110
111    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
112
113    // Update the field
114    token_metadata.update(data.field, data.value);
115
116    // Update / realloc the account
117    realloc_and_pack_first_variable_len(metadata_info, &token_metadata)?;
118
119    Ok(())
120}
121
122/// Processes a [RemoveKey](enum.TokenMetadataInstruction.html) instruction.
123pub fn process_remove_key(
124    _program_id: &Pubkey,
125    accounts: &[AccountInfo],
126    data: RemoveKey,
127) -> ProgramResult {
128    let account_info_iter = &mut accounts.iter();
129    let metadata_info = next_account_info(account_info_iter)?;
130    let update_authority_info = next_account_info(account_info_iter)?;
131
132    // deserialize the metadata, but scope the data borrow since we'll probably
133    // realloc the account
134    let mut token_metadata = {
135        let buffer = metadata_info.try_borrow_data()?;
136        let state = TlvStateBorrowed::unpack(&buffer)?;
137        state.get_first_variable_len_value::<TokenMetadata>()?
138    };
139
140    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
141    if !token_metadata.remove_key(&data.key) && !data.idempotent {
142        return Err(TokenMetadataError::KeyNotFound.into());
143    }
144    realloc_and_pack_first_variable_len(metadata_info, &token_metadata)?;
145
146    Ok(())
147}
148
149/// Processes a [UpdateAuthority](enum.TokenMetadataInstruction.html)
150/// instruction.
151pub fn process_update_authority(
152    _program_id: &Pubkey,
153    accounts: &[AccountInfo],
154    data: UpdateAuthority,
155) -> ProgramResult {
156    let account_info_iter = &mut accounts.iter();
157    let metadata_info = next_account_info(account_info_iter)?;
158    let update_authority_info = next_account_info(account_info_iter)?;
159
160    // deserialize the metadata, but scope the data borrow since we'll probably
161    // realloc the account
162    let mut token_metadata = {
163        let buffer = metadata_info.try_borrow_data()?;
164        let state = TlvStateBorrowed::unpack(&buffer)?;
165        state.get_first_variable_len_value::<TokenMetadata>()?
166    };
167
168    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
169    token_metadata.update_authority = data.new_authority;
170    // Update the account, no realloc needed!
171    realloc_and_pack_first_variable_len(metadata_info, &token_metadata)?;
172
173    Ok(())
174}
175
176/// Processes an [Emit](enum.TokenMetadataInstruction.html) instruction.
177pub fn process_emit(program_id: &Pubkey, accounts: &[AccountInfo], data: Emit) -> ProgramResult {
178    let account_info_iter = &mut accounts.iter();
179    let metadata_info = next_account_info(account_info_iter)?;
180
181    if metadata_info.owner != program_id {
182        return Err(ProgramError::IllegalOwner);
183    }
184
185    let buffer = metadata_info.try_borrow_data()?;
186    let state = TlvStateBorrowed::unpack(&buffer)?;
187    let metadata_bytes = state.get_first_bytes::<TokenMetadata>()?;
188
189    if let Some(range) = TokenMetadata::get_slice(metadata_bytes, data.start, data.end) {
190        set_return_data(range);
191    }
192
193    Ok(())
194}
195
196/// Processes an [Instruction](enum.Instruction.html).
197pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
198    let instruction = TokenMetadataInstruction::unpack(input)?;
199
200    match instruction {
201        TokenMetadataInstruction::Initialize(data) => {
202            msg!("Instruction: Initialize");
203            process_initialize(program_id, accounts, data)
204        }
205        TokenMetadataInstruction::UpdateField(data) => {
206            msg!("Instruction: UpdateField");
207            process_update_field(program_id, accounts, data)
208        }
209        TokenMetadataInstruction::RemoveKey(data) => {
210            msg!("Instruction: RemoveKey");
211            process_remove_key(program_id, accounts, data)
212        }
213        TokenMetadataInstruction::UpdateAuthority(data) => {
214            msg!("Instruction: UpdateAuthority");
215            process_update_authority(program_id, accounts, data)
216        }
217        TokenMetadataInstruction::Emit(data) => {
218            msg!("Instruction: Emit");
219            process_emit(program_id, accounts, data)
220        }
221    }
222}