From 293db504533b6886c7cd6e49204de5e9afd2aeee Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 8 Mar 2024 02:56:24 -0800 Subject: [PATCH 1/3] Move asset and collection validations to util functions --- programs/mpl-core/src/error.rs | 4 + programs/mpl-core/src/plugins/lifecycle.rs | 18 ++- programs/mpl-core/src/processor/burn.rs | 142 +++-------------- programs/mpl-core/src/processor/transfer.rs | 98 ++---------- programs/mpl-core/src/processor/update.rs | 136 +++------------- programs/mpl-core/src/state/collection.rs | 5 +- programs/mpl-core/src/utils.rs | 165 +++++++++++++++++++- 7 files changed, 243 insertions(+), 325 deletions(-) diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 8348cdbd..f9464fc7 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -92,6 +92,10 @@ pub enum MplCoreError { /// 20 - Missing update authority #[error("Missing update authority")] MissingUpdateAuthority, + + /// 20 - Missing new owner + #[error("Missing new owner")] + MissingNewOwner, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 39b31be0..a4d0b65b 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -103,6 +103,7 @@ impl Plugin { pub(crate) fn validate_update( plugin: &Plugin, authority: &AccountInfo, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { @@ -145,6 +146,7 @@ impl Plugin { pub(crate) fn validate_burn( plugin: &Plugin, authority: &AccountInfo, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { @@ -160,12 +162,13 @@ impl Plugin { } /// Route the validation of the transfer action to the appropriate plugin. - pub(crate) fn validate_transfer<'a>( + pub(crate) fn validate_transfer( plugin: &Plugin, - authority: &AccountInfo<'a>, - new_owner: &AccountInfo<'a>, + authority: &AccountInfo, + new_owner: Option<&AccountInfo>, authorities: &Authority, ) -> Result { + let new_owner = new_owner.ok_or(MplCoreError::MissingNewOwner)?; match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), Plugin::Royalties(royalties) => { @@ -383,12 +386,18 @@ pub(crate) fn validate_plugin_checks<'a, F>( key: Key, checks: &BTreeMap, authority: &AccountInfo<'a>, + new_owner: Option<&AccountInfo>, asset: &AccountInfo<'a>, collection: Option<&AccountInfo<'a>>, validate_fp: F, ) -> Result where - F: Fn(&Plugin, &AccountInfo<'a>, &Authority) -> Result, + F: Fn( + &Plugin, + &AccountInfo<'a>, + Option<&AccountInfo>, + &Authority, + ) -> Result, { for (_, (check_key, check_result, registry_record)) in checks { if *check_key == key @@ -407,6 +416,7 @@ where let result = validate_fp( &Plugin::load(account, registry_record.offset)?, authority, + new_owner, ®istry_record.authority, )?; match result { diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index f45d67bf..fec4ee0d 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; @@ -7,11 +5,12 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplCoreError, instruction::accounts::{BurnAccounts, BurnCollectionAccounts}, - plugins::{ - validate_plugin_checks, CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult, + plugins::{Plugin, PluginType}, + state::{Asset, Collection, Compressible, CompressionProof, Key}, + utils::{ + close_program_account, load_key, validate_asset_permissions, + validate_collection_permissions, verify_proof, }, - state::{Asset, Collection, Compressible, CompressionProof, Key, SolanaAccount}, - utils::{close_program_account, fetch_core_data, load_key, verify_proof}, }; #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] @@ -45,82 +44,18 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnArgs) -> Progr asset.wrap()?; } Key::Asset => { - let (_, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - - let mut checks: BTreeMap = - BTreeMap::new(); - - // The asset approval overrides the collection approval. - let asset_approval = Asset::check_burn(); - let core_check = match asset_approval { - CheckResult::None => (Key::Collection, Collection::check_burn()), - _ => (Key::Asset, asset_approval), - }; - - // Check the collection plugins first. - ctx.accounts.collection.and_then(|collection_info| { - fetch_core_data::(collection_info) - .map(|(_, _, registry)| { - registry.map(|r| { - r.check_registry(Key::Collection, PluginType::check_burn, &mut checks); - r - }) - }) - .ok()? - }); - - // Next check the asset plugins. Plugins on the asset override the collection plugins, - // so we don't need to validate the collection plugins if the asset has a plugin. - if let Some(registry) = plugin_registry.as_ref() { - registry.check_registry(Key::Asset, PluginType::check_burn, &mut checks); - } - - solana_program::msg!("checks: {:#?}", checks); - - // Do the core validation. - let mut approved = matches!( - core_check, - ( - Key::Asset | Key::Collection, - CheckResult::CanApprove | CheckResult::CanReject - ) - ) && { - (match core_check.0 { - Key::Collection => Collection::load( - ctx.accounts - .collection - .ok_or(MplCoreError::InvalidCollection)?, - 0, - )? - .validate_burn(ctx.accounts.authority)?, - Key::Asset => { - Asset::load(ctx.accounts.asset, 0)?.validate_burn(ctx.accounts.authority)? - } - _ => return Err(MplCoreError::IncorrectAccount.into()), - }) == ValidationResult::Approved - }; - - approved = validate_plugin_checks( - Key::Collection, - &checks, - ctx.accounts.authority, - ctx.accounts.asset, - ctx.accounts.collection, - Box::new(Plugin::validate_burn), - )? || approved; - - approved = validate_plugin_checks( - Key::Asset, - &checks, + let _ = validate_asset_permissions( ctx.accounts.authority, ctx.accounts.asset, ctx.accounts.collection, - Box::new(Plugin::validate_burn), - )? || approved; - - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } + None, + Asset::check_burn, + Collection::check_burn, + PluginType::check_burn, + Asset::validate_burn, + Collection::validate_burn, + Plugin::validate_burn, + )?; } _ => return Err(MplCoreError::IncorrectAccount.into()), } @@ -146,47 +81,14 @@ pub(crate) fn burn_collection<'a>( assert_signer(payer)?; } - let (collection, _, plugin_registry) = fetch_core_data::(ctx.accounts.collection)?; - - // let checks: [(Key, CheckResult); PluginType::COUNT + 2]; - - let mut approved = false; - match Collection::check_burn() { - CheckResult::CanApprove | CheckResult::CanReject => { - match collection.validate_burn(ctx.accounts.authority)? { - ValidationResult::Approved => { - approved = true; - } - ValidationResult::Rejected => return Err(MplCoreError::InvalidAuthority.into()), - ValidationResult::Pass => (), - } - } - CheckResult::None => (), - }; - - if let Some(plugin_registry) = plugin_registry { - for record in plugin_registry.registry { - if matches!( - PluginType::check_burn(&record.plugin_type), - CheckResult::CanApprove | CheckResult::CanReject - ) { - let result = Plugin::validate_burn( - &Plugin::load(ctx.accounts.collection, record.offset)?, - ctx.accounts.authority, - &record.authority, - )?; - if result == ValidationResult::Rejected { - return Err(MplCoreError::InvalidAuthority.into()); - } else if result == ValidationResult::Approved { - approved = true; - } - } - } - }; - - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } + let _ = validate_collection_permissions( + ctx.accounts.authority, + ctx.accounts.collection, + Collection::check_burn, + PluginType::check_burn, + Collection::validate_burn, + Plugin::validate_burn, + )?; process_burn(ctx.accounts.collection, ctx.accounts.authority) } diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index 58d22614..594de83e 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; @@ -7,14 +5,9 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplCoreError, instruction::accounts::TransferAccounts, - plugins::{ - validate_plugin_checks, CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult, - }, - state::{ - Asset, Authority, Collection, Compressible, CompressionProof, HashedAsset, Key, - SolanaAccount, - }, - utils::{fetch_core_data, load_key, verify_proof}, + plugins::{Plugin, PluginType}, + state::{Asset, Collection, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount}, + utils::{load_key, validate_asset_permissions, verify_proof}, }; #[repr(C)] @@ -54,86 +47,19 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) HashedAsset::new(asset.hash()?).save(ctx.accounts.asset, 0) } Key::Asset => { - let (mut asset, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - - let mut checks: BTreeMap = - BTreeMap::new(); - - // The asset approval overrides the collection approval. - let asset_approval = Asset::check_transfer(); - let core_check = match asset_approval { - CheckResult::None => (Key::Collection, Collection::check_transfer()), - _ => (Key::Asset, asset_approval), - }; - - // Check the collection plugins first. - if let Some(collection_info) = ctx.accounts.collection { - fetch_core_data::(collection_info).map(|(_, _, registry)| { - registry.map(|r| { - r.check_registry(Key::Collection, PluginType::check_transfer, &mut checks); - r - }) - })?; - } - - // Next check the asset plugins. Plugins on the asset override the collection plugins, - // so we don't need to validate the collection plugins if the asset has a plugin. - if let Some(registry) = plugin_registry.as_ref() { - registry.check_registry(Key::Asset, PluginType::check_transfer, &mut checks); - } - - solana_program::msg!("checks: {:#?}", checks); - - // Do the core validation. - let mut approved = matches!( - core_check, - ( - Key::Asset | Key::Collection, - CheckResult::CanApprove | CheckResult::CanReject - ) - ) && { - (match core_check.0 { - Key::Collection => Collection::load( - ctx.accounts - .collection - .ok_or(MplCoreError::InvalidCollection)?, - 0, - )? - .validate_transfer()?, - Key::Asset => Asset::load(ctx.accounts.asset, 0)? - .validate_transfer(ctx.accounts.authority)?, - _ => return Err(MplCoreError::IncorrectAccount.into()), - }) == ValidationResult::Approved - }; - solana_program::msg!("approved: {:#?}", approved); - - let custom_args = |plugin: &Plugin, - authority_info: &AccountInfo<'a>, - authority: &Authority| { - Plugin::validate_transfer(plugin, authority_info, ctx.accounts.new_owner, authority) - }; - - approved = validate_plugin_checks( - Key::Collection, - &checks, - ctx.accounts.authority, - ctx.accounts.asset, - ctx.accounts.collection, - Box::new(custom_args), - )? || approved; - - approved = validate_plugin_checks( - Key::Asset, - &checks, + let (mut asset, _, _) = validate_asset_permissions( ctx.accounts.authority, ctx.accounts.asset, ctx.accounts.collection, - Box::new(custom_args), - )? || approved; + Some(ctx.accounts.new_owner), + Asset::check_transfer, + Collection::check_transfer, + PluginType::check_transfer, + Asset::validate_transfer, + Collection::validate_transfer, + Plugin::validate_transfer, + )?; - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } asset.owner = *ctx.accounts.new_owner.key; asset.save(ctx.accounts.asset, 0) } diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index dba821b8..4181593a 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ @@ -9,11 +7,11 @@ use solana_program::{ use crate::{ error::MplCoreError, instruction::accounts::{UpdateAccounts, UpdateCollectionAccounts}, - plugins::{ - validate_plugin_checks, CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult, + plugins::{Plugin, PluginType, RegistryRecord}, + state::{Asset, Collection, DataBlob, SolanaAccount, UpdateAuthority}, + utils::{ + resize_or_reallocate_account, validate_asset_permissions, validate_collection_permissions, }, - state::{Asset, Collection, DataBlob, Key, SolanaAccount, UpdateAuthority}, - utils::{fetch_core_data, resize_or_reallocate_account}, }; #[repr(C)] @@ -37,80 +35,20 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P None => ctx.accounts.authority, }; - let (mut asset, plugin_header, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - let asset_size = asset.get_size() as isize; - - let mut checks: BTreeMap = BTreeMap::new(); - - // The asset approval overrides the collection approval. - let asset_approval = Asset::check_update(); - let core_check = match asset_approval { - CheckResult::None => (Key::Collection, Collection::check_update()), - _ => (Key::Asset, asset_approval), - }; - - // Check the collection plugins first. - if let Some(collection_info) = ctx.accounts.collection { - fetch_core_data::(collection_info).map(|(_, _, registry)| { - registry.map(|r| { - r.check_registry(Key::Collection, PluginType::check_update, &mut checks); - r - }) - })?; - } - - // Next check the asset plugins. Plugins on the asset override the collection plugins, - // so we don't need to validate the collection plugins if the asset has a plugin. - if let Some(registry) = plugin_registry.as_ref() { - registry.check_registry(Key::Asset, PluginType::check_update, &mut checks); - } - - solana_program::msg!("checks: {:#?}", checks); - - // Do the core validation. - let mut approved = matches!( - core_check, - ( - Key::Asset | Key::Collection, - CheckResult::CanApprove | CheckResult::CanReject - ) - ) && { - (match core_check.0 { - Key::Collection => Collection::load( - ctx.accounts - .collection - .ok_or(MplCoreError::InvalidCollection)?, - 0, - )? - .validate_update(ctx.accounts.authority)?, - Key::Asset => { - Asset::load(ctx.accounts.asset, 0)?.validate_update(ctx.accounts.authority)? - } - _ => return Err(MplCoreError::IncorrectAccount.into()), - }) == ValidationResult::Approved - }; - - approved = validate_plugin_checks( - Key::Collection, - &checks, - ctx.accounts.authority, - ctx.accounts.asset, - ctx.accounts.collection, - Box::new(Plugin::validate_update), - )? || approved; - - approved = validate_plugin_checks( - Key::Asset, - &checks, + let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( ctx.accounts.authority, ctx.accounts.asset, ctx.accounts.collection, - Box::new(Plugin::validate_update), - )? || approved; + None, + Asset::check_update, + Collection::check_update, + PluginType::check_update, + Asset::validate_update, + Collection::validate_update, + Plugin::validate_update, + )?; - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } + let asset_size = asset.get_size() as isize; let mut dirty = false; if let Some(new_update_authority) = ctx.accounts.new_update_authority { @@ -221,44 +159,16 @@ pub(crate) fn update_collection<'a>( None => ctx.accounts.authority, }; - let (mut asset, plugin_header, plugin_registry) = - fetch_core_data::(ctx.accounts.collection)?; - let asset_size = asset.get_size() as isize; - - let mut approved = false; - match Asset::check_update() { - CheckResult::CanApprove => { - if asset.validate_update(ctx.accounts.authority)? == ValidationResult::Approved { - approved = true; - } - } - CheckResult::CanReject => return Err(MplCoreError::InvalidAuthority.into()), - CheckResult::None => (), - }; - - if let Some(plugin_registry) = plugin_registry.clone() { - for record in plugin_registry.registry { - if matches!( - PluginType::check_transfer(&record.plugin_type), - CheckResult::CanApprove | CheckResult::CanReject - ) { - let result = Plugin::validate_update( - &Plugin::load(ctx.accounts.collection, record.offset)?, - ctx.accounts.authority, - &record.authority, - )?; - if result == ValidationResult::Rejected { - return Err(MplCoreError::InvalidAuthority.into()); - } else if result == ValidationResult::Approved { - approved = true; - } - } - } - }; + let (mut asset, plugin_header, plugin_registry) = validate_collection_permissions( + ctx.accounts.authority, + ctx.accounts.collection, + Collection::check_update, + PluginType::check_update, + Collection::validate_update, + Plugin::validate_update, + )?; - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } + let asset_size = asset.get_size() as isize; let mut dirty = false; if let Some(new_update_authority) = ctx.accounts.new_update_authority { diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index 88e92413..f674cba2 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -71,7 +71,10 @@ impl Collection { } /// Validate the transfer lifecycle event. - pub fn validate_transfer(&self) -> Result { + pub fn validate_transfer( + &self, + _authority: &AccountInfo, + ) -> Result { Ok(ValidationResult::Pass) } diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 79f37da5..10d85645 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -3,10 +3,14 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, program_error::ProgramError, rent::Rent, system_instruction, sysvar::Sysvar, }; +use std::collections::BTreeMap; use crate::{ error::MplCoreError, - plugins::{PluginHeader, PluginRegistry}, + plugins::{ + validate_plugin_checks, CheckResult, Plugin, PluginHeader, PluginRegistry, PluginType, + RegistryRecord, ValidationResult, + }, state::{ Asset, Authority, Collection, Compressible, CompressionProof, CoreAsset, DataBlob, HashablePluginSchema, HashedAsset, HashedAssetSchema, Key, SolanaAccount, @@ -196,3 +200,162 @@ pub(crate) fn resize_or_reallocate_account<'a>( Ok(()) } + +#[allow(clippy::too_many_arguments)] +/// Validate asset permissions using asset, collection, and plugin lifecycle validations. +pub fn validate_asset_permissions<'a>( + authority: &AccountInfo<'a>, + asset: &AccountInfo<'a>, + collection: Option<&AccountInfo<'a>>, + new_owner: Option<&AccountInfo<'a>>, + asset_check_fp: fn() -> CheckResult, + collection_check_fp: fn() -> CheckResult, + plugin_check_fp: fn(&PluginType) -> CheckResult, + asset_validate_fp: fn(&Asset, &AccountInfo) -> Result, + collection_validate_fp: fn(&Collection, &AccountInfo) -> Result, + plugin_validate_fp: fn( + &Plugin, + &AccountInfo, + Option<&AccountInfo>, + &Authority, + ) -> Result, +) -> Result<(Asset, Option, Option), ProgramError> { + let (deserialized_asset, plugin_header, plugin_registry) = fetch_core_data::(asset)?; + + let mut checks: BTreeMap = BTreeMap::new(); + + // The asset approval overrides the collection approval. + let asset_approval = asset_check_fp(); + let core_check = match asset_approval { + CheckResult::None => (Key::Collection, collection_check_fp()), + _ => (Key::Asset, asset_approval), + }; + + // Check the collection plugins first. + if let Some(collection_info) = collection { + fetch_core_data::(collection_info).map(|(_, _, registry)| { + registry.map(|r| { + r.check_registry(Key::Collection, plugin_check_fp, &mut checks); + r + }) + })?; + } + + // Next check the asset plugins. Plugins on the asset override the collection plugins, + // so we don't need to validate the collection plugins if the asset has a plugin. + if let Some(registry) = plugin_registry.as_ref() { + registry.check_registry(Key::Asset, plugin_check_fp, &mut checks); + } + + solana_program::msg!("checks: {:#?}", checks); + + // Do the core validation. + let mut approved = matches!( + core_check, + ( + Key::Asset | Key::Collection, + CheckResult::CanApprove | CheckResult::CanReject + ) + ) && { + (match core_check.0 { + Key::Collection => collection_validate_fp( + &Collection::load(collection.ok_or(MplCoreError::InvalidCollection)?, 0)?, + authority, + )?, + Key::Asset => asset_validate_fp(&Asset::load(asset, 0)?, authority)?, + _ => return Err(MplCoreError::IncorrectAccount.into()), + }) == ValidationResult::Approved + }; + solana_program::msg!("approved: {:#?}", approved); + + // let custom_args = |plugin: &Plugin, authority_info: &AccountInfo<'a>, authority: &Authority| { + // Plugin::validate_transfer(plugin, authority_info, new_owner, authority) + // }; + + approved = validate_plugin_checks( + Key::Collection, + &checks, + authority, + new_owner, + asset, + collection, + plugin_validate_fp, + )? || approved; + + approved = validate_plugin_checks( + Key::Asset, + &checks, + authority, + new_owner, + asset, + collection, + plugin_validate_fp, + )? || approved; + + if !approved { + return Err(MplCoreError::InvalidAuthority.into()); + } + + Ok((deserialized_asset, plugin_header, plugin_registry)) +} + +/// Validate collection permissions using collection and plugin lifecycle validations. +pub fn validate_collection_permissions<'a>( + authority: &AccountInfo<'a>, + collection: &AccountInfo<'a>, + collection_check_fp: fn() -> CheckResult, + plugin_check_fp: fn(&PluginType) -> CheckResult, + collection_validate_fp: fn(&Collection, &AccountInfo) -> Result, + plugin_validate_fp: fn( + &Plugin, + &AccountInfo, + Option<&AccountInfo>, + &Authority, + ) -> Result, +) -> Result<(Collection, Option, Option), ProgramError> { + let (deserialized_collection, plugin_header, plugin_registry) = + fetch_core_data::(collection)?; + + // let checks: [(Key, CheckResult); PluginType::COUNT + 2]; + + let mut approved = false; + match collection_check_fp() { + CheckResult::CanApprove | CheckResult::CanReject => { + match collection_validate_fp(&deserialized_collection, authority)? { + ValidationResult::Approved => { + approved = true; + } + ValidationResult::Rejected => return Err(MplCoreError::InvalidAuthority.into()), + ValidationResult::Pass => (), + } + } + CheckResult::None => (), + }; + + if let Some(plugin_registry) = &plugin_registry { + for record in plugin_registry.registry.iter() { + if matches!( + plugin_check_fp(&record.plugin_type), + CheckResult::CanApprove | CheckResult::CanReject + ) { + let result = plugin_validate_fp( + &Plugin::load(collection, record.offset)?, + authority, + None, + &record.authority, + )?; + if result == ValidationResult::Rejected { + return Err(MplCoreError::InvalidAuthority.into()); + } else if result == ValidationResult::Approved { + approved = true; + } + } + } + }; + + if !approved { + return Err(MplCoreError::InvalidAuthority.into()); + } + + Ok((deserialized_collection, plugin_header, plugin_registry)) +} From 0b656569a141e97bd0c7474b8bdc3c48c69643c1 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 8 Mar 2024 02:57:27 -0800 Subject: [PATCH 2/3] Regenerate IDL and clients --- clients/js/src/generated/errors/mplCore.ts | 13 +++++++++++++ clients/rust/src/generated/errors/mpl_core.rs | 3 +++ idls/mpl_core.json | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index a0d497db..6befb4d4 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -291,6 +291,19 @@ export class MissingUpdateAuthorityError extends ProgramError { codeToErrorMap.set(0x14, MissingUpdateAuthorityError); nameToErrorMap.set('MissingUpdateAuthority', MissingUpdateAuthorityError); +/** MissingNewOwner: Missing new owner */ +export class MissingNewOwnerError extends ProgramError { + override readonly name: string = 'MissingNewOwner'; + + readonly code: number = 0x15; // 21 + + constructor(program: Program, cause?: Error) { + super('Missing new owner', program, cause); + } +} +codeToErrorMap.set(0x15, MissingNewOwnerError); +nameToErrorMap.set('MissingNewOwner', MissingNewOwnerError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index e65b56e4..83a89a41 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -73,6 +73,9 @@ pub enum MplCoreError { /// 20 (0x14) - Missing update authority #[error("Missing update authority")] MissingUpdateAuthority, + /// 21 (0x15) - Missing new owner + #[error("Missing new owner")] + MissingNewOwner, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/idls/mpl_core.json b/idls/mpl_core.json index aceaabab..ac21832b 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -2270,6 +2270,11 @@ "code": 20, "name": "MissingUpdateAuthority", "msg": "Missing update authority" + }, + { + "code": 21, + "name": "MissingNewOwner", + "msg": "Missing new owner" } ], "metadata": { From 75a0e9ba58c577c902145c6f945a2747dc0835fe Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 8 Mar 2024 03:15:14 -0800 Subject: [PATCH 3/3] Use primitive fn type since closure not needed --- programs/mpl-core/src/plugins/lifecycle.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index a4d0b65b..0ccb934e 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -382,23 +382,20 @@ pub(crate) trait PluginValidation { } } -pub(crate) fn validate_plugin_checks<'a, F>( +pub(crate) fn validate_plugin_checks<'a>( key: Key, checks: &BTreeMap, authority: &AccountInfo<'a>, new_owner: Option<&AccountInfo>, asset: &AccountInfo<'a>, collection: Option<&AccountInfo<'a>>, - validate_fp: F, -) -> Result -where - F: Fn( + validate_fp: fn( &Plugin, &AccountInfo<'a>, Option<&AccountInfo>, &Authority, ) -> Result, -{ +) -> Result { for (_, (check_key, check_result, registry_record)) in checks { if *check_key == key && matches!(