mpl_token_metadata/processor/metadata/
update.rs

1use std::fmt::{Display, Formatter};
2
3use mpl_utils::assert_signer;
4use solana_program::{
5    account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
6    program_pack::Pack, pubkey::Pubkey, sysvar,
7};
8use spl_token::state::Account;
9
10use crate::{
11    assertions::{assert_owned_by, programmable::assert_valid_authorization},
12    error::MetadataError,
13    instruction::{
14        CollectionDetailsToggle, CollectionToggle, Context, MetadataDelegateRole, Update,
15        UpdateArgs, UsesToggle,
16    },
17    pda::{EDITION, PREFIX},
18    state::{
19        AuthorityRequest, AuthorityResponse, AuthorityType, Collection, Metadata,
20        ProgrammableConfig, TokenMetadataAccount, TokenStandard,
21    },
22    utils::{assert_derivation, check_token_standard},
23};
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum UpdateScenario {
27    MetadataAuth,
28    Delegate,
29    Proxy,
30}
31
32impl Display for UpdateScenario {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        match self {
35            UpdateScenario::MetadataAuth => write!(f, "MetadataAuth"),
36            UpdateScenario::Delegate => write!(f, "Delegate"),
37            UpdateScenario::Proxy => write!(f, "Proxy"),
38        }
39    }
40}
41
42pub fn update<'a>(
43    program_id: &Pubkey,
44    accounts: &'a [AccountInfo<'a>],
45    args: UpdateArgs,
46) -> ProgramResult {
47    let context = Update::to_context(accounts)?;
48
49    update_v1(program_id, context, args)
50}
51
52fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> ProgramResult {
53    // Assert signers
54
55    // Authority should always be a signer regardless of the authority type,
56    // because at least one signer is required to update the metadata.
57    assert_signer(ctx.accounts.authority_info)?;
58    assert_signer(ctx.accounts.payer_info)?;
59
60    // Assert program ownership
61
62    if let Some(delegate_record_info) = ctx.accounts.delegate_record_info {
63        assert_owned_by(delegate_record_info, &crate::ID)?;
64    }
65
66    if let Some(token_info) = ctx.accounts.token_info {
67        assert_owned_by(token_info, &spl_token::ID)?;
68    }
69
70    assert_owned_by(ctx.accounts.mint_info, &spl_token::ID)?;
71    assert_owned_by(ctx.accounts.metadata_info, program_id)?;
72
73    if let Some(edition) = ctx.accounts.edition_info {
74        assert_owned_by(edition, program_id)?;
75    }
76
77    // Note that we do NOT check the ownership of authorization rules account here as this allows
78    // `Update` to be used to correct a previously invalid `RuleSet`.  In practice the ownership of
79    // authorization rules is checked by the Auth Rules program each time the program is invoked to
80    // validate rules.
81
82    // Check program IDs
83
84    if ctx.accounts.system_program_info.key != &solana_program::system_program::ID {
85        return Err(ProgramError::IncorrectProgramId);
86    }
87    if ctx.accounts.sysvar_instructions_info.key != &sysvar::instructions::ID {
88        return Err(ProgramError::IncorrectProgramId);
89    }
90
91    // If the current rule set is passed in, also require the mpl-token-auth-rules program
92    // to be passed in (and check its program ID).
93    if ctx.accounts.authorization_rules_info.is_some() {
94        let authorization_rules_program = ctx
95            .accounts
96            .authorization_rules_program_info
97            .ok_or(MetadataError::MissingAuthorizationRulesProgram)?;
98
99        if authorization_rules_program.key != &mpl_token_auth_rules::ID {
100            return Err(ProgramError::IncorrectProgramId);
101        }
102    }
103
104    // Validate relationships
105
106    // Token
107    let (token_pubkey, token) = if let Some(token_info) = ctx.accounts.token_info {
108        let token = Account::unpack(&token_info.try_borrow_data()?)?;
109
110        // Token mint must match mint account key.  Token amount must be greater than 0.
111        if token.mint != *ctx.accounts.mint_info.key {
112            return Err(MetadataError::MintMismatch.into());
113        } else if token.amount == 0 {
114            return Err(MetadataError::AmountMustBeGreaterThanZero.into());
115        }
116
117        (Some(token_info.key), Some(token))
118    } else {
119        (None, None)
120    };
121
122    // Metadata
123    let mut metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
124    // Metadata mint must match mint account key.
125    if metadata.mint != *ctx.accounts.mint_info.key {
126        return Err(MetadataError::MintMismatch.into());
127    }
128
129    // Edition
130    if let Some(edition) = ctx.accounts.edition_info {
131        // checks that we got the correct edition account
132        assert_derivation(
133            program_id,
134            edition,
135            &[
136                PREFIX.as_bytes(),
137                program_id.as_ref(),
138                ctx.accounts.mint_info.key.as_ref(),
139                EDITION.as_bytes(),
140            ],
141        )?;
142    }
143
144    // Check authority.
145
146    // There is a special case for collection-level delegates, where the
147    // validation should use the collection key as the mint parameter.
148    let existing_collection_mint = metadata
149        .collection
150        .as_ref()
151        .map(|Collection { key, .. }| key);
152
153    // Check if caller passed in a collection and if so use that.  Note that
154    // `validate_update` checks that the authority has permission to pass in
155    // a new collection value.
156    let collection_mint = match &args {
157        UpdateArgs::V1 { collection, .. }
158        | UpdateArgs::AsUpdateAuthorityV2 { collection, .. }
159        | UpdateArgs::AsCollectionDelegateV2 { collection, .. }
160        | UpdateArgs::AsCollectionItemDelegateV2 { collection, .. } => match collection {
161            CollectionToggle::Set(Collection { key, .. }) => Some(key),
162            _ => existing_collection_mint,
163        },
164        _ => existing_collection_mint,
165    };
166
167    // Determines if we have a valid authority to perform the update. This must
168    // be either the update authority, a delegate or the holder. This call fails
169    // if no valid authority is present.
170    let AuthorityResponse {
171        authority_type,
172        metadata_delegate_role,
173        ..
174    } = AuthorityType::get_authority_type(AuthorityRequest {
175        authority: ctx.accounts.authority_info.key,
176        update_authority: &metadata.update_authority,
177        mint: ctx.accounts.mint_info.key,
178        collection_mint,
179        token: token_pubkey,
180        token_account: token.as_ref(),
181        metadata_delegate_record_info: ctx.accounts.delegate_record_info,
182        metadata_delegate_roles: vec![
183            MetadataDelegateRole::AuthorityItem,
184            MetadataDelegateRole::Data,
185            MetadataDelegateRole::DataItem,
186            MetadataDelegateRole::Collection,
187            MetadataDelegateRole::CollectionItem,
188            MetadataDelegateRole::ProgrammableConfig,
189            MetadataDelegateRole::ProgrammableConfigItem,
190        ],
191        collection_metadata_delegate_roles: vec![
192            MetadataDelegateRole::Data,
193            MetadataDelegateRole::Collection,
194            MetadataDelegateRole::ProgrammableConfig,
195        ],
196        precedence: &[
197            AuthorityType::Metadata,
198            AuthorityType::MetadataDelegate,
199            AuthorityType::Holder,
200        ],
201        ..Default::default()
202    })?;
203
204    // Validate that authority has permission to use the update args that were provided.
205    validate_update(&args, &authority_type, metadata_delegate_role)?;
206
207    // See if caller passed in a desired token standard.
208    let desired_token_standard = match args {
209        UpdateArgs::AsUpdateAuthorityV2 { token_standard, .. }
210        | UpdateArgs::AsAuthorityItemDelegateV2 { token_standard, .. } => token_standard,
211        _ => None,
212    };
213
214    // Find existing token standard from metadata or infer it.
215    let existing_or_inferred_token_std = if let Some(token_standard) = metadata.token_standard {
216        token_standard
217    } else {
218        check_token_standard(ctx.accounts.mint_info, ctx.accounts.edition_info)?
219    };
220
221    // If there is a desired token standard, use it if it passes the check.  If there is not a
222    // desired token standard, use the existing or inferred token standard.
223    let token_standard = match desired_token_standard {
224        Some(desired_token_standard) => {
225            check_desired_token_standard(existing_or_inferred_token_std, desired_token_standard)?;
226            desired_token_standard
227        }
228        None => existing_or_inferred_token_std,
229    };
230
231    // For pNFTs, we need to validate the authorization rules.
232    if matches!(token_standard, TokenStandard::ProgrammableNonFungible) {
233        // If the metadata account has a current rule set, we validate that
234        // the current rule set account is passed in and matches value on the
235        // metadata.
236        if let Some(config) = &metadata.programmable_config {
237            // if we have a programmable rule set
238            if let ProgrammableConfig::V1 { rule_set: Some(_) } = config {
239                assert_valid_authorization(ctx.accounts.authorization_rules_info, config)?;
240            }
241        }
242    }
243
244    // If we reach here without errors we have validated that the authority is allowed to
245    // perform an update.
246    metadata.update_v1(
247        args,
248        ctx.accounts.authority_info,
249        ctx.accounts.metadata_info,
250        token,
251        token_standard,
252    )?;
253
254    Ok(())
255}
256
257/// Validates that the authority is only updating metadata fields
258/// that it has access to.
259fn validate_update(
260    args: &UpdateArgs,
261    authority_type: &AuthorityType,
262    metadata_delegate_role: Option<MetadataDelegateRole>,
263) -> ProgramResult {
264    // validate the authority type
265    match authority_type {
266        AuthorityType::Metadata => {
267            // metadata authority is the paramount (update) authority
268            msg!("Auth type: Metadata");
269        }
270        AuthorityType::Holder => {
271            // support for holder update
272            msg!("Auth type: Holder");
273            return Err(MetadataError::FeatureNotSupported.into());
274        }
275        AuthorityType::MetadataDelegate => {
276            // support for delegate update
277            msg!("Auth type: Delegate");
278        }
279        _ => return Err(MetadataError::InvalidAuthorityType.into()),
280    }
281
282    // validate the delegate role: this consist in checking that
283    // the delegate is only updating fields that it has access to
284    if let Some(metadata_delegate_role) = metadata_delegate_role {
285        let valid_delegate_update = match (metadata_delegate_role, args) {
286            (MetadataDelegateRole::AuthorityItem, UpdateArgs::AsAuthorityItemDelegateV2 { .. }) => {
287                true
288            }
289            (MetadataDelegateRole::Data, UpdateArgs::AsDataDelegateV2 { .. }) => true,
290            (MetadataDelegateRole::DataItem, UpdateArgs::AsDataItemDelegateV2 { .. }) => true,
291            (MetadataDelegateRole::Collection, UpdateArgs::AsCollectionDelegateV2 { .. }) => true,
292            (
293                MetadataDelegateRole::CollectionItem,
294                UpdateArgs::AsCollectionItemDelegateV2 { .. },
295            ) => true,
296            (
297                // V1 supported Programmable config, leaving here for backwards
298                // compatibility.
299                MetadataDelegateRole::ProgrammableConfig,
300                UpdateArgs::V1 {
301                    new_update_authority: None,
302                    data: None,
303                    primary_sale_happened: None,
304                    is_mutable: None,
305                    collection: CollectionToggle::None,
306                    collection_details: CollectionDetailsToggle::None,
307                    uses: UsesToggle::None,
308                    ..
309                },
310            ) => true,
311            (
312                MetadataDelegateRole::ProgrammableConfig,
313                UpdateArgs::AsProgrammableConfigDelegateV2 { .. },
314            ) => true,
315            (
316                MetadataDelegateRole::ProgrammableConfigItem,
317                UpdateArgs::AsProgrammableConfigItemDelegateV2 { .. },
318            ) => true,
319            _ => false,
320        };
321
322        if !valid_delegate_update {
323            return Err(MetadataError::InvalidUpdateArgs.into());
324        }
325    }
326
327    Ok(())
328}
329
330fn check_desired_token_standard(
331    existing_or_inferred_token_std: TokenStandard,
332    desired_token_standard: TokenStandard,
333) -> ProgramResult {
334    match (existing_or_inferred_token_std, desired_token_standard) {
335        (
336            TokenStandard::Fungible | TokenStandard::FungibleAsset,
337            TokenStandard::Fungible | TokenStandard::FungibleAsset,
338        ) => Ok(()),
339        (existing, desired) if existing == desired => Ok(()),
340        _ => Err(MetadataError::InvalidTokenStandard.into()),
341    }
342}