Skip to content

Commit a6a3b29

Browse files
committed
Changes to Update to support new and changed delegates
* Modify authority check to separate out item and collection-level delegates. * Add V2 Update args struct to allow user to specify token standard. * Check that new delegates are only changing metadata for which they are meant to have access. * Modify Update handler to update metadata fields based on the delegate type.
1 parent c79fa6b commit a6a3b29

File tree

6 files changed

+298
-61
lines changed

6 files changed

+298
-61
lines changed

token-metadata/program/src/instruction/metadata.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
processor::AuthorizationData,
1616
state::{
1717
AssetData, Collection, CollectionDetails, Creator, Data, DataV2, MigrationType,
18-
PrintSupply, Uses,
18+
PrintSupply, TokenStandard, Uses,
1919
},
2020
};
2121

@@ -100,20 +100,45 @@ pub enum UpdateArgs {
100100
/// Required authorization data to validate the request.
101101
authorization_data: Option<AuthorizationData>,
102102
},
103+
V2 {
104+
/// The new update authority.
105+
new_update_authority: Option<Pubkey>,
106+
/// The metadata details.
107+
data: Option<Data>,
108+
/// Indicates whether the primary sale has happened or not (once set to `true`, it cannot be
109+
/// changed back).
110+
primary_sale_happened: Option<bool>,
111+
// Indicates Whether the data struct is mutable or not (once set to `true`, it cannot be
112+
/// changed back).
113+
is_mutable: Option<bool>,
114+
/// Collection information.
115+
collection: CollectionToggle,
116+
/// Additional details of the collection.
117+
collection_details: CollectionDetailsToggle,
118+
/// Uses information.
119+
uses: UsesToggle,
120+
// Programmable rule set configuration (only applicable to `Programmable` asset types).
121+
rule_set: RuleSetToggle,
122+
/// Token standard.
123+
token_standard: Option<TokenStandard>,
124+
/// Required authorization data to validate the request.
125+
authorization_data: Option<AuthorizationData>,
126+
},
103127
}
104128

105129
impl Default for UpdateArgs {
106130
fn default() -> Self {
107-
Self::V1 {
108-
authorization_data: None,
131+
Self::V2 {
109132
new_update_authority: None,
110133
data: None,
111134
primary_sale_happened: None,
112135
is_mutable: None,
113136
collection: CollectionToggle::None,
114-
uses: UsesToggle::None,
115137
collection_details: CollectionDetailsToggle::None,
138+
uses: UsesToggle::None,
116139
rule_set: RuleSetToggle::None,
140+
token_standard: None,
141+
authorization_data: None,
117142
}
118143
}
119144
}

token-metadata/program/src/processor/metadata/update.rs

Lines changed: 185 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
AuthorityRequest, AuthorityResponse, AuthorityType, Collection, Metadata,
1717
ProgrammableConfig, TokenMetadataAccount, TokenStandard,
1818
},
19-
utils::{assert_derivation, check_token_standard},
19+
utils::{assert_derivation, check_token_standard, mint_decimals_is_zero},
2020
};
2121

2222
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -45,6 +45,7 @@ pub fn update<'a>(
4545

4646
match args {
4747
UpdateArgs::V1 { .. } => update_v1(program_id, context, args),
48+
UpdateArgs::V2 { .. } => update_v1(program_id, context, args),
4849
}
4950
}
5051

@@ -63,7 +64,7 @@ fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> Pro
6364

6465
if let Some(edition) = ctx.accounts.edition_info {
6566
assert_owned_by(edition, program_id)?;
66-
// checks that we got the correct master account
67+
// checks that we got the correct edition account
6768
assert_derivation(
6869
program_id,
6970
edition,
@@ -114,12 +115,6 @@ fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> Pro
114115
return Err(MetadataError::MintMismatch.into());
115116
}
116117

117-
let token_standard = if let Some(token_standard) = metadata.token_standard {
118-
token_standard
119-
} else {
120-
check_token_standard(ctx.accounts.mint_info, ctx.accounts.edition_info)?
121-
};
122-
123118
let (token_pubkey, token) = if let Some(token_info) = ctx.accounts.token_info {
124119
(
125120
Some(token_info.key),
@@ -152,7 +147,18 @@ fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> Pro
152147
token: token_pubkey,
153148
token_account: token.as_ref(),
154149
metadata_delegate_record_info: ctx.accounts.delegate_record_info,
155-
metadata_delegate_roles: vec![MetadataDelegateRole::ProgrammableConfig],
150+
metadata_delegate_roles: vec![
151+
MetadataDelegateRole::Authority,
152+
MetadataDelegateRole::Data,
153+
MetadataDelegateRole::Collection,
154+
MetadataDelegateRole::CollectionItem,
155+
MetadataDelegateRole::ProgrammableConfig,
156+
MetadataDelegateRole::ProgrammableConfigItem,
157+
],
158+
collection_metadata_delegate_roles: vec![
159+
MetadataDelegateRole::Collection,
160+
MetadataDelegateRole::ProgrammableConfig,
161+
],
156162
precedence: &[
157163
AuthorityType::Metadata,
158164
AuthorityType::MetadataDelegate,
@@ -161,6 +167,34 @@ fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> Pro
161167
..Default::default()
162168
})?;
163169

170+
// Find existing token standard from metadata or infer it.
171+
let existing_or_inferred_token_standard = if let Some(token_standard) = metadata.token_standard
172+
{
173+
token_standard
174+
} else {
175+
check_token_standard(ctx.accounts.mint_info, ctx.accounts.edition_info)?
176+
};
177+
178+
// Check if caller passed in a desired token standard.
179+
let desired_token_standard = match args {
180+
UpdateArgs::V1 { .. } => None,
181+
UpdateArgs::V2 { token_standard, .. } => token_standard,
182+
};
183+
184+
// If there is a desired token standard, use it if it passes the check. If there is not a
185+
// desired token standard, use the existing or inferred token standard.
186+
let token_standard = match desired_token_standard {
187+
Some(desired_token_standard) => {
188+
check_desired_token_standard(
189+
mint_decimals_is_zero(ctx.accounts.mint_info)?,
190+
existing_or_inferred_token_standard,
191+
desired_token_standard,
192+
)?;
193+
desired_token_standard
194+
}
195+
None => existing_or_inferred_token_standard,
196+
};
197+
164198
// For pNFTs, we need to validate the authorization rules.
165199
if matches!(token_standard, TokenStandard::ProgrammableNonFungible) {
166200
// If the metadata account has a current rule set, we validate that
@@ -174,6 +208,8 @@ fn update_v1(program_id: &Pubkey, ctx: Context<Update>, args: UpdateArgs) -> Pro
174208
}
175209
}
176210

211+
// Validate that authority has permission to update the fields that have been specified in the
212+
// update args.
177213
validate_update(&args, &authority_type, metadata_delegate_role)?;
178214

179215
// If we reach here without errors we have validated that the authority is allowed to
@@ -199,58 +235,171 @@ fn validate_update(
199235
metadata_delegate_role: Option<MetadataDelegateRole>,
200236
) -> ProgramResult {
201237
// validate the authority type
202-
203238
match authority_type {
204239
AuthorityType::Metadata => {
205-
// metadata authority is the paramount (upadte) authority
240+
// metadata authority is the paramount (update) authority
206241
msg!("Auth type: Metadata");
207242
}
208-
AuthorityType::MetadataDelegate => {
209-
// support for delegate update
210-
msg!("Auth type: Delegate");
211-
}
212243
AuthorityType::Holder => {
213244
// support for holder update
214245
msg!("Auth type: Holder");
215246
return Err(MetadataError::FeatureNotSupported.into());
216247
}
248+
AuthorityType::MetadataDelegate => {
249+
// support for delegate update
250+
msg!("Auth type: Delegate");
251+
}
217252
_ => {
218253
return Err(MetadataError::InvalidAuthorityType.into());
219254
}
220255
}
221256

222-
let UpdateArgs::V1 {
257+
// Destructure args.
258+
let (
259+
new_update_authority,
223260
data,
224261
primary_sale_happened,
225262
is_mutable,
226263
collection,
227-
uses,
228-
new_update_authority,
229264
collection_details,
230-
..
231-
} = args;
265+
uses,
266+
rule_set,
267+
token_standard,
268+
) = match args {
269+
UpdateArgs::V1 {
270+
new_update_authority,
271+
data,
272+
primary_sale_happened,
273+
is_mutable,
274+
collection,
275+
collection_details,
276+
uses,
277+
rule_set,
278+
..
279+
} => (
280+
new_update_authority,
281+
data,
282+
primary_sale_happened,
283+
is_mutable,
284+
collection,
285+
collection_details,
286+
uses,
287+
rule_set,
288+
&None,
289+
),
290+
UpdateArgs::V2 {
291+
new_update_authority,
292+
data,
293+
primary_sale_happened,
294+
is_mutable,
295+
collection,
296+
collection_details,
297+
uses,
298+
rule_set,
299+
token_standard,
300+
..
301+
} => (
302+
new_update_authority,
303+
data,
304+
primary_sale_happened,
305+
is_mutable,
306+
collection,
307+
collection_details,
308+
uses,
309+
rule_set,
310+
token_standard,
311+
),
312+
};
232313

233314
// validate the delegate role: this consist in checking that
234315
// the delegate is only updating fields that it has access to
235-
match metadata_delegate_role {
236-
Some(MetadataDelegateRole::ProgrammableConfig) => {
237-
// can only update the programmable config
238-
if data.is_some()
239-
|| primary_sale_happened.is_some()
240-
|| is_mutable.is_some()
241-
|| collection.is_some()
242-
|| uses.is_some()
243-
|| new_update_authority.is_some()
244-
|| collection_details.is_some()
245-
{
246-
return Err(MetadataError::InvalidUpdateArgs.into());
316+
if let Some(metadata_delegate_role) = metadata_delegate_role {
317+
match metadata_delegate_role {
318+
MetadataDelegateRole::Authority => {
319+
// Fields allowed for `Authority`:
320+
// `new_update_authority`
321+
// `primary_sale_happened`
322+
// `is_mutable`
323+
// `token_standard`
324+
if data.is_some()
325+
|| collection.is_some()
326+
|| collection_details.is_some()
327+
|| uses.is_some()
328+
|| rule_set.is_some()
329+
{
330+
return Err(MetadataError::InvalidUpdateArgs.into());
331+
}
247332
}
333+
MetadataDelegateRole::Data => {
334+
// Fields allowed for `Data`:
335+
// `data`
336+
if new_update_authority.is_some()
337+
|| primary_sale_happened.is_some()
338+
|| is_mutable.is_some()
339+
|| collection.is_some()
340+
|| collection_details.is_some()
341+
|| uses.is_some()
342+
|| rule_set.is_some()
343+
|| token_standard.is_some()
344+
{
345+
return Err(MetadataError::InvalidUpdateArgs.into());
346+
}
347+
}
348+
349+
MetadataDelegateRole::Collection | MetadataDelegateRole::CollectionItem => {
350+
// Fields allowed for `Collection` and `CollectionItem`:
351+
// `collection`
352+
if new_update_authority.is_some()
353+
|| data.is_some()
354+
|| primary_sale_happened.is_some()
355+
|| is_mutable.is_some()
356+
|| collection_details.is_some()
357+
|| uses.is_some()
358+
|| rule_set.is_some()
359+
|| token_standard.is_some()
360+
{
361+
return Err(MetadataError::InvalidUpdateArgs.into());
362+
}
363+
}
364+
MetadataDelegateRole::ProgrammableConfig
365+
| MetadataDelegateRole::ProgrammableConfigItem => {
366+
// Fields allowed for `ProgrammableConfig` and `ProgrammableConfigItem`:
367+
// `rule_set`
368+
if new_update_authority.is_some()
369+
|| data.is_some()
370+
|| primary_sale_happened.is_some()
371+
|| is_mutable.is_some()
372+
|| collection.is_some()
373+
|| collection_details.is_some()
374+
|| uses.is_some()
375+
|| token_standard.is_some()
376+
{
377+
return Err(MetadataError::InvalidUpdateArgs.into());
378+
}
379+
}
380+
_ => return Err(MetadataError::InvalidAuthorityType.into()),
248381
}
249-
Some(_) => {
250-
return Err(MetadataError::InvalidAuthorityType.into());
251-
}
252-
None => { /* no delegate role to check */ }
253382
}
254383

255384
Ok(())
256385
}
386+
387+
fn check_desired_token_standard(
388+
mint_decimals_is_zero: bool,
389+
existing_or_inferred_token_standard: TokenStandard,
390+
desired_token_standard: TokenStandard,
391+
) -> ProgramResult {
392+
// This code only allows switching between Fungible and FungibleAsset, and only when
393+
// mint decimals is zero.
394+
if !mint_decimals_is_zero {
395+
return Err(MetadataError::InvalidTokenStandard.into());
396+
}
397+
398+
match existing_or_inferred_token_standard {
399+
TokenStandard::Fungible | TokenStandard::FungibleAsset => match desired_token_standard {
400+
TokenStandard::Fungible | TokenStandard::FungibleAsset => Ok(()),
401+
_ => Err(MetadataError::InvalidTokenStandard.into()),
402+
},
403+
_ => Err(MetadataError::InvalidTokenStandard.into()),
404+
}
405+
}

token-metadata/program/src/processor/verification/collection.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ pub(crate) fn unverify_collection_v1(program_id: &Pubkey, ctx: Context<Unverify>
164164
// or an update delegate for the item. This call fails if no valid authority is present.
165165
auth_request.mint = &metadata.mint;
166166
auth_request.update_authority = &metadata.update_authority;
167-
auth_request.metadata_delegate_roles = vec![MetadataDelegateRole::Update];
167+
auth_request.metadata_delegate_roles = vec![
168+
MetadataDelegateRole::Collection,
169+
MetadataDelegateRole::CollectionItem,
170+
];
168171
AuthorityType::get_authority_type(auth_request)
169172
} else {
170173
// If the parent is not burned, we need to ensure the collection metadata account is owned

0 commit comments

Comments
 (0)