mpl_token_metadata/processor/metadata/
update.rs1use 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_signer(ctx.accounts.authority_info)?;
58 assert_signer(ctx.accounts.payer_info)?;
59
60 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 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 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 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 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 let mut metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
124 if metadata.mint != *ctx.accounts.mint_info.key {
126 return Err(MetadataError::MintMismatch.into());
127 }
128
129 if let Some(edition) = ctx.accounts.edition_info {
131 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 let existing_collection_mint = metadata
149 .collection
150 .as_ref()
151 .map(|Collection { key, .. }| key);
152
153 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 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_update(&args, &authority_type, metadata_delegate_role)?;
206
207 let desired_token_standard = match args {
209 UpdateArgs::AsUpdateAuthorityV2 { token_standard, .. }
210 | UpdateArgs::AsAuthorityItemDelegateV2 { token_standard, .. } => token_standard,
211 _ => None,
212 };
213
214 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 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 if matches!(token_standard, TokenStandard::ProgrammableNonFungible) {
233 if let Some(config) = &metadata.programmable_config {
237 if let ProgrammableConfig::V1 { rule_set: Some(_) } = config {
239 assert_valid_authorization(ctx.accounts.authorization_rules_info, config)?;
240 }
241 }
242 }
243
244 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
257fn validate_update(
260 args: &UpdateArgs,
261 authority_type: &AuthorityType,
262 metadata_delegate_role: Option<MetadataDelegateRole>,
263) -> ProgramResult {
264 match authority_type {
266 AuthorityType::Metadata => {
267 msg!("Auth type: Metadata");
269 }
270 AuthorityType::Holder => {
271 msg!("Auth type: Holder");
273 return Err(MetadataError::FeatureNotSupported.into());
274 }
275 AuthorityType::MetadataDelegate => {
276 msg!("Auth type: Delegate");
278 }
279 _ => return Err(MetadataError::InvalidAuthorityType.into()),
280 }
281
282 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 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}