Skip to main content

gor22_token/extension/token_metadata/
processor.rs

1//! Token-metadata processor
2
3use {
4    crate::{
5        check_program_account,
6        error::TokenError,
7        extension::{
8            alloc_and_serialize_variable_len_extension, metadata_pointer::MetadataPointer,
9            BaseStateWithExtensions, PodStateWithExtensions,
10        },
11        pod::{PodCOption, PodMint},
12    },
13    solana_program::{
14        account_info::{next_account_info, AccountInfo},
15        entrypoint::ProgramResult,
16        msg,
17        program::set_return_data,
18        program_error::ProgramError,
19        pubkey::Pubkey,
20    },
21    spl_pod::optional_keys::OptionalNonZeroPubkey,
22    spl_token_metadata_interface::{
23        error::TokenMetadataError,
24        instruction::{
25            Emit, Initialize, RemoveKey, TokenMetadataInstruction, UpdateAuthority, UpdateField,
26        },
27        state::TokenMetadata,
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(TokenMetadataError::ImmutableMetadata)?;
40    if update_authority != *update_authority_info.key {
41        return Err(TokenMetadataError::IncorrectUpdateAuthority.into());
42    }
43    Ok(())
44}
45
46/// Processes a [`Initialize`](enum.TokenMetadataInstruction.html) instruction.
47pub fn process_initialize(
48    _program_id: &Pubkey,
49    accounts: &[AccountInfo],
50    data: Initialize,
51) -> ProgramResult {
52    let account_info_iter = &mut accounts.iter();
53
54    let metadata_info = next_account_info(account_info_iter)?;
55    let update_authority_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 metadata accounts are the same, since the metadata
60    // extension should only describe itself
61    if metadata_info.key != mint_info.key {
62        msg!("Metadata 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(TokenMetadataError::IncorrectMintAuthority.into());
79        }
80
81        if mint.get_extension::<MetadataPointer>().is_err() {
82            msg!("A mint with metadata must have the metadata-pointer extension initialized");
83            return Err(TokenError::InvalidExtensionCombination.into());
84        }
85    }
86
87    // Create the token metadata
88    let update_authority = OptionalNonZeroPubkey::try_from(Some(*update_authority_info.key))?;
89    let token_metadata = TokenMetadata {
90        name: data.name,
91        symbol: data.symbol,
92        uri: data.uri,
93        update_authority,
94        mint: *mint_info.key,
95        ..Default::default()
96    };
97
98    // allocate a TLV entry for the space and write it in, assumes that there's
99    // enough SOL for the new rent-exemption
100    alloc_and_serialize_variable_len_extension::<PodMint, _>(
101        metadata_info,
102        &token_metadata,
103        false,
104    )?;
105
106    Ok(())
107}
108
109/// Processes an [`UpdateField`](enum.TokenMetadataInstruction.html)
110/// instruction.
111pub fn process_update_field(
112    _program_id: &Pubkey,
113    accounts: &[AccountInfo],
114    data: UpdateField,
115) -> ProgramResult {
116    let account_info_iter = &mut accounts.iter();
117    let metadata_info = next_account_info(account_info_iter)?;
118    let update_authority_info = next_account_info(account_info_iter)?;
119
120    // deserialize the metadata, but scope the data borrow since we'll probably
121    // realloc the account
122    let mut token_metadata = {
123        let buffer = metadata_info.try_borrow_data()?;
124        let mint = PodStateWithExtensions::<PodMint>::unpack(&buffer)?;
125        mint.get_variable_len_extension::<TokenMetadata>()?
126    };
127
128    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
129
130    // Update the field
131    token_metadata.update(data.field, data.value);
132
133    // Update / realloc the account
134    alloc_and_serialize_variable_len_extension::<PodMint, _>(metadata_info, &token_metadata, true)?;
135
136    Ok(())
137}
138
139/// Processes a [`RemoveKey`](enum.TokenMetadataInstruction.html) instruction.
140pub fn process_remove_key(
141    _program_id: &Pubkey,
142    accounts: &[AccountInfo],
143    data: RemoveKey,
144) -> ProgramResult {
145    let account_info_iter = &mut accounts.iter();
146    let metadata_info = next_account_info(account_info_iter)?;
147    let update_authority_info = next_account_info(account_info_iter)?;
148
149    // deserialize the metadata, but scope the data borrow since we'll probably
150    // realloc the account
151    let mut token_metadata = {
152        let buffer = metadata_info.try_borrow_data()?;
153        let mint = PodStateWithExtensions::<PodMint>::unpack(&buffer)?;
154        mint.get_variable_len_extension::<TokenMetadata>()?
155    };
156
157    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
158    if !token_metadata.remove_key(&data.key) && !data.idempotent {
159        return Err(TokenMetadataError::KeyNotFound.into());
160    }
161    alloc_and_serialize_variable_len_extension::<PodMint, _>(metadata_info, &token_metadata, true)?;
162    Ok(())
163}
164
165/// Processes a [`UpdateAuthority`](enum.TokenMetadataInstruction.html)
166/// instruction.
167pub fn process_update_authority(
168    _program_id: &Pubkey,
169    accounts: &[AccountInfo],
170    data: UpdateAuthority,
171) -> ProgramResult {
172    let account_info_iter = &mut accounts.iter();
173    let metadata_info = next_account_info(account_info_iter)?;
174    let update_authority_info = next_account_info(account_info_iter)?;
175
176    // deserialize the metadata, but scope the data borrow since we'll write
177    // to the account later
178    let mut token_metadata = {
179        let buffer = metadata_info.try_borrow_data()?;
180        let mint = PodStateWithExtensions::<PodMint>::unpack(&buffer)?;
181        mint.get_variable_len_extension::<TokenMetadata>()?
182    };
183
184    check_update_authority(update_authority_info, &token_metadata.update_authority)?;
185    token_metadata.update_authority = data.new_authority;
186    // Update the account, no realloc needed!
187    alloc_and_serialize_variable_len_extension::<PodMint, _>(metadata_info, &token_metadata, true)?;
188
189    Ok(())
190}
191
192/// Processes an [`Emit`](enum.TokenMetadataInstruction.html) instruction.
193pub fn process_emit(program_id: &Pubkey, accounts: &[AccountInfo], data: Emit) -> ProgramResult {
194    let account_info_iter = &mut accounts.iter();
195    let metadata_info = next_account_info(account_info_iter)?;
196
197    if metadata_info.owner != program_id {
198        return Err(ProgramError::IllegalOwner);
199    }
200
201    let buffer = metadata_info.try_borrow_data()?;
202    let state = PodStateWithExtensions::<PodMint>::unpack(&buffer)?;
203    let metadata_bytes = state.get_extension_bytes::<TokenMetadata>()?;
204
205    if let Some(range) = TokenMetadata::get_slice(metadata_bytes, data.start, data.end) {
206        set_return_data(range);
207    }
208    Ok(())
209}
210
211/// Processes an [`Instruction`](enum.Instruction.html).
212pub fn process_instruction(
213    program_id: &Pubkey,
214    accounts: &[AccountInfo],
215    instruction: TokenMetadataInstruction,
216) -> ProgramResult {
217    match instruction {
218        TokenMetadataInstruction::Initialize(data) => {
219            msg!("TokenMetadataInstruction: Initialize");
220            process_initialize(program_id, accounts, data)
221        }
222        TokenMetadataInstruction::UpdateField(data) => {
223            msg!("TokenMetadataInstruction: UpdateField");
224            process_update_field(program_id, accounts, data)
225        }
226        TokenMetadataInstruction::RemoveKey(data) => {
227            msg!("TokenMetadataInstruction: RemoveKey");
228            process_remove_key(program_id, accounts, data)
229        }
230        TokenMetadataInstruction::UpdateAuthority(data) => {
231            msg!("TokenMetadataInstruction: UpdateAuthority");
232            process_update_authority(program_id, accounts, data)
233        }
234        TokenMetadataInstruction::Emit(data) => {
235            msg!("TokenMetadataInstruction: Emit");
236            process_emit(program_id, accounts, data)
237        }
238    }
239}