diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index cf0b9c28..8ac21abe 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -6,10 +6,10 @@ use std::collections::BTreeMap; use crate::{ error::MplCoreError, plugins::{ - ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, Plugin, - PluginType, RegistryRecord, + ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, + HookableLifecycleEvent, Plugin, PluginType, RegistryRecord, }, - state::{Authority, DataBlob, Key, UpdateAuthority}, + state::{AssetV1, Authority, CollectionV1, DataBlob, Key, UpdateAuthority}, }; /// Lifecycle permissions @@ -663,29 +663,234 @@ pub(crate) trait PluginValidation { } } +/// Context describing what is being changed or targeted by a lifecycle event. +/// +/// All fields default to `None` — callers only set the fields relevant to their +/// specific lifecycle event. This eliminates long chains of `None` parameters +/// at call sites. +/// +/// # Examples +/// ```ignore +/// // Transfer only needs new_owner: +/// let ctx = LifecycleContext { new_owner: Some(new_owner), ..Default::default() }; +/// +/// // Plugin operations need plugin + authority: +/// let ctx = LifecycleContext { +/// new_plugin: Some(&plugin), +/// new_plugin_authority: Some(&authority), +/// ..Default::default() +/// }; +/// ``` +pub(crate) struct LifecycleContext<'a, 'b> { + /// The new owner account for transfers. + pub new_owner: Option<&'a AccountInfo<'a>>, + /// The new asset update authority. + pub new_asset_authority: Option<&'b UpdateAuthority>, + /// The new collection update authority. + pub new_collection_authority: Option<&'b Pubkey>, + /// The plugin being acted upon (added, removed, updated, etc.). + pub new_plugin: Option<&'b Plugin>, + /// The authority of the target plugin. + pub new_plugin_authority: Option<&'b Authority>, + /// The external plugin adapter being acted upon. + pub new_external_plugin_adapter: Option<&'b ExternalPluginAdapter>, + /// The authority of the target external plugin adapter. + pub new_external_plugin_adapter_authority: Option<&'b Authority>, +} + +impl Default for LifecycleContext<'_, '_> { + fn default() -> Self { + Self { + new_owner: None, + new_asset_authority: None, + new_collection_authority: None, + new_plugin: None, + new_plugin_authority: None, + new_external_plugin_adapter: None, + new_external_plugin_adapter_authority: None, + } + } +} + +/// Trait that bundles all check/validate function pointers for a specific lifecycle event +/// (e.g. Transfer, Burn, Update) into a single generic type parameter. +/// +/// Instead of passing 6-8 function pointers to validation functions, callers specify the +/// lifecycle event as a type parameter: `validate_asset_permissions::(...)`. +pub(crate) trait LifecycleEvent { + /// Check whether the asset type itself can approve/reject this event. + fn check_asset() -> CheckResult; + + /// Check whether the collection type itself can approve/reject this event. + fn check_collection() -> CheckResult; + + /// Check whether a given plugin type can approve/reject this event. + fn check_plugin(plugin_type: &PluginType) -> CheckResult; + + /// Validate the event against the asset. + fn validate_asset( + asset: &AssetV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result; + + /// Validate the event against the collection. + fn validate_collection( + collection: &CollectionV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result; + + /// Validate the event against a plugin. + fn validate_plugin( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result; + + /// Validate the event against an external plugin adapter. + /// Default implementation abstains (for events that don't use external plugins). + fn validate_external_plugin_adapter( + _adapter: &ExternalPluginAdapter, + _ctx: &PluginValidationContext, + ) -> Result { + abstain!() + } + + /// The hookable lifecycle event for external plugins, if applicable. + /// Returns `None` for events that don't support external plugin validation. + fn hookable_lifecycle_event() -> Option { + None + } +} + +/// Macro to define a lifecycle event type and its `LifecycleEvent` implementation. +/// +/// For events without external plugin adapter support: +/// `define_lifecycle!(TypeName, check_fn, validate_fn);` +/// +/// For events with external plugin adapter support: +/// `define_lifecycle!(TypeName, check_fn, validate_fn, ext_validate_fn, HookableLifecycleEvent::Variant);` +macro_rules! define_lifecycle { + // Without external plugin adapter validation + ($name:ident, $check:ident, $validate:ident) => { + pub(crate) struct $name; + + impl LifecycleEvent for $name { + fn check_asset() -> CheckResult { + AssetV1::$check() + } + fn check_collection() -> CheckResult { + CollectionV1::$check() + } + fn check_plugin(plugin_type: &PluginType) -> CheckResult { + PluginType::$check(plugin_type) + } + fn validate_asset( + asset: &AssetV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result { + asset.$validate(authority_info, new_plugin, new_external_plugin_adapter) + } + fn validate_collection( + collection: &CollectionV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result { + collection.$validate(authority_info, new_plugin, new_external_plugin_adapter) + } + fn validate_plugin( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result { + Plugin::$validate(plugin, ctx) + } + } + }; + // With external plugin adapter validation + ($name:ident, $check:ident, $validate:ident, $ext_validate:ident, $hookable:expr) => { + pub(crate) struct $name; + + impl LifecycleEvent for $name { + fn check_asset() -> CheckResult { + AssetV1::$check() + } + fn check_collection() -> CheckResult { + CollectionV1::$check() + } + fn check_plugin(plugin_type: &PluginType) -> CheckResult { + PluginType::$check(plugin_type) + } + fn validate_asset( + asset: &AssetV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result { + asset.$validate(authority_info, new_plugin, new_external_plugin_adapter) + } + fn validate_collection( + collection: &CollectionV1, + authority_info: &AccountInfo, + new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, + ) -> Result { + collection.$validate(authority_info, new_plugin, new_external_plugin_adapter) + } + fn validate_plugin( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result { + Plugin::$validate(plugin, ctx) + } + fn validate_external_plugin_adapter( + adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + ExternalPluginAdapter::$ext_validate(adapter, ctx) + } + fn hookable_lifecycle_event() -> Option { + Some($hookable) + } + } + }; +} + +// Events WITH external plugin adapter validation +define_lifecycle!(CreateLifecycle, check_create, validate_create, validate_create, HookableLifecycleEvent::Create); +define_lifecycle!(TransferLifecycle, check_transfer, validate_transfer, validate_transfer, HookableLifecycleEvent::Transfer); +define_lifecycle!(BurnLifecycle, check_burn, validate_burn, validate_burn, HookableLifecycleEvent::Burn); +define_lifecycle!(UpdateLifecycle, check_update, validate_update, validate_update, HookableLifecycleEvent::Update); + +// Events WITHOUT external plugin adapter validation +define_lifecycle!(AddPluginLifecycle, check_add_plugin, validate_add_plugin); +define_lifecycle!(RemovePluginLifecycle, check_remove_plugin, validate_remove_plugin); +define_lifecycle!(UpdatePluginLifecycle, check_update_plugin, validate_update_plugin); +define_lifecycle!(ApprovePluginAuthorityLifecycle, check_approve_plugin_authority, validate_approve_plugin_authority); +define_lifecycle!(RevokePluginAuthorityLifecycle, check_revoke_plugin_authority, validate_revoke_plugin_authority); +define_lifecycle!(CompressLifecycle, check_compress, validate_compress); +define_lifecycle!(DecompressLifecycle, check_decompress, validate_decompress); +define_lifecycle!(ExecuteLifecycle, check_execute, validate_execute); +define_lifecycle!(AddExternalPluginAdapterLifecycle, check_add_external_plugin_adapter, validate_add_external_plugin_adapter); +define_lifecycle!(RemoveExternalPluginAdapterLifecycle, check_remove_external_plugin_adapter, validate_remove_external_plugin_adapter); + /// This function iterates through all plugin checks passed in and performs the validation /// by deserializing and calling validate on the plugin. /// The STRONGEST result is returned. -#[allow(clippy::too_many_arguments, clippy::type_complexity)] -pub(crate) fn validate_plugin_checks<'a>( +#[allow(clippy::type_complexity)] +pub(crate) fn validate_plugin_checks<'a, E: LifecycleEvent>( key: Key, accounts: &'a [AccountInfo<'a>], checks: &BTreeMap, authority: &'a AccountInfo<'a>, - new_owner: Option<&'a AccountInfo<'a>>, - new_asset_authority: Option<&UpdateAuthority>, - new_collection_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, - new_plugin_authority: Option<&Authority>, - new_external_plugin: Option<&ExternalPluginAdapter>, - new_external_plugin_authority: Option<&Authority>, asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], - plugin_validate_fp: fn( - &Plugin, - &PluginValidationContext, - ) -> Result, + ctx: &LifecycleContext<'a, '_>, ) -> Result { let mut approved = false; let mut rejected = false; @@ -709,16 +914,16 @@ pub(crate) fn validate_plugin_checks<'a>( self_authority: ®istry_record.authority, authority_info: authority, resolved_authorities: Some(resolved_authorities), - new_owner, - new_asset_authority, - new_collection_authority, - target_plugin: new_plugin, - target_plugin_authority: new_plugin_authority, - target_external_plugin: new_external_plugin, - target_external_plugin_authority: new_external_plugin_authority, + new_owner: ctx.new_owner, + new_asset_authority: ctx.new_asset_authority, + new_collection_authority: ctx.new_collection_authority, + target_plugin: ctx.new_plugin, + target_plugin_authority: ctx.new_plugin_authority, + target_external_plugin: ctx.new_external_plugin_adapter, + target_external_plugin_authority: ctx.new_external_plugin_adapter_authority, }; - let result = plugin_validate_fp( + let result = E::validate_plugin( &Plugin::load(account, registry_record.offset)?, &validation_ctx, )?; @@ -743,8 +948,8 @@ pub(crate) fn validate_plugin_checks<'a>( /// This function iterates through all external plugin adapter checks passed in and performs the validation /// by deserializing and calling validate on the plugin. /// The STRONGEST result is returned. -#[allow(clippy::too_many_arguments, clippy::type_complexity)] -pub(crate) fn validate_external_plugin_adapter_checks<'a>( +#[allow(clippy::type_complexity)] +pub(crate) fn validate_external_plugin_adapter_checks<'a, E: LifecycleEvent>( key: Key, accounts: &'a [AccountInfo<'a>], external_checks: &BTreeMap< @@ -752,20 +957,10 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( (Key, ExternalCheckResultBits, ExternalRegistryRecord), >, authority: &'a AccountInfo<'a>, - new_owner: Option<&'a AccountInfo<'a>>, - new_asset_authority: Option<&UpdateAuthority>, - new_collection_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, - new_plugin_authority: Option<&Authority>, - new_external_plugin: Option<&ExternalPluginAdapter>, - new_external_plugin_authority: Option<&Authority>, asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], - external_plugin_adapter_validate_fp: fn( - &ExternalPluginAdapter, - &PluginValidationContext, - ) -> Result, + ctx: &LifecycleContext<'a, '_>, ) -> Result { let mut approved = false; for (check_key, check_result, external_registry_record) in external_checks.values() { @@ -787,16 +982,16 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( self_authority: &external_registry_record.authority, authority_info: authority, resolved_authorities: Some(resolved_authorities), - new_owner, - new_asset_authority, - new_collection_authority, - target_plugin: new_plugin, - target_plugin_authority: new_plugin_authority, - target_external_plugin: new_external_plugin, - target_external_plugin_authority: new_external_plugin_authority, + new_owner: ctx.new_owner, + new_asset_authority: ctx.new_asset_authority, + new_collection_authority: ctx.new_collection_authority, + target_plugin: ctx.new_plugin, + target_plugin_authority: ctx.new_plugin_authority, + target_external_plugin: ctx.new_external_plugin_adapter, + target_external_plugin_authority: ctx.new_external_plugin_adapter_authority, }; - let result = external_plugin_adapter_validate_fp( + let result = E::validate_external_plugin_adapter( &ExternalPluginAdapter::load(account, external_registry_record.offset)?, &validation_ctx, )?; diff --git a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs index 4d68474f..bcb4070f 100644 --- a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs @@ -8,9 +8,9 @@ use crate::{ AddCollectionExternalPluginAdapterV1Accounts, AddExternalPluginAdapterV1Accounts, }, plugins::{ - create_meta_idempotent, initialize_external_plugin_adapter, ExternalPluginAdapter, - ExternalPluginAdapterInitInfo, Plugin, PluginType, PluginValidationContext, - ValidationResult, + create_meta_idempotent, initialize_external_plugin_adapter, + AddExternalPluginAdapterLifecycle, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, + LifecycleContext, PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -101,26 +101,16 @@ pub(crate) fn add_external_plugin_adapter<'a>( } // Validate asset permissions. - // Validate asset permissions. - let (mut asset, _, _) = validate_asset_permissions( + let (mut asset, _, _) = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - Some(&external_plugin_adapter), - Some(&external_plugin_adapter_authority), - AssetV1::check_add_external_plugin_adapter, - CollectionV1::check_add_external_plugin_adapter, - PluginType::check_add_external_plugin_adapter, - AssetV1::validate_add_external_plugin_adapter, - CollectionV1::validate_add_external_plugin_adapter, - Plugin::validate_add_external_plugin_adapter, - None, - None, + &LifecycleContext { + new_external_plugin_adapter: Some(&external_plugin_adapter), + new_external_plugin_adapter_authority: Some(&external_plugin_adapter_authority), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -206,21 +196,15 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - None, - None, - Some(&external_plugin_adapter), - Some(&external_plugin_adapter_authority), - CollectionV1::check_add_external_plugin_adapter, - PluginType::check_add_external_plugin_adapter, - CollectionV1::validate_add_external_plugin_adapter, - Plugin::validate_add_external_plugin_adapter, - None, - None, + &LifecycleContext { + new_external_plugin_adapter: Some(&external_plugin_adapter), + new_external_plugin_adapter_authority: Some(&external_plugin_adapter_authority), + ..Default::default() + }, )?; process_add_external_plugin_adapter::( diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index 8587f2fe..65a31a05 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -6,8 +6,8 @@ use crate::{ error::MplCoreError, instruction::accounts::{AddCollectionPluginV1Accounts, AddPluginV1Accounts}, plugins::{ - create_meta_idempotent, initialize_plugin, Plugin, PluginType, PluginValidationContext, - ValidationResult, + create_meta_idempotent, initialize_plugin, AddPluginLifecycle, LifecycleContext, Plugin, + PluginType, PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -77,25 +77,16 @@ pub(crate) fn add_plugin<'a>( } // Validate asset permissions. - let (mut asset, _, _) = validate_asset_permissions( + let (mut asset, _, _) = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - None, - None, - AssetV1::check_add_plugin, - CollectionV1::check_add_plugin, - PluginType::check_add_plugin, - AssetV1::validate_add_plugin, - CollectionV1::validate_add_plugin, - Plugin::validate_add_plugin, - None, - None, + &LifecycleContext { + new_plugin: Some(&args.plugin), + new_plugin_authority: Some(&target_plugin_authority), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -163,21 +154,15 @@ pub(crate) fn add_collection_plugin<'a>( } // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - None, - None, - CollectionV1::check_add_plugin, - PluginType::check_add_plugin, - CollectionV1::validate_add_plugin, - Plugin::validate_add_plugin, - None, - None, + &LifecycleContext { + new_plugin: Some(&args.plugin), + new_plugin_authority: Some(&target_plugin_authority), + ..Default::default() + }, )?; process_add_plugin::( diff --git a/programs/mpl-core/src/processor/approve_plugin_authority.rs b/programs/mpl-core/src/processor/approve_plugin_authority.rs index 715c0de3..5b47048a 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -7,7 +7,10 @@ use crate::{ instruction::accounts::{ ApproveCollectionPluginAuthorityV1Accounts, ApprovePluginAuthorityV1Accounts, }, - plugins::{approve_authority_on_plugin, fetch_wrapped_plugin, Plugin, PluginType}, + plugins::{ + approve_authority_on_plugin, fetch_wrapped_plugin, ApprovePluginAuthorityLifecycle, + LifecycleContext, PluginType, + }, state::{AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount}, utils::{ fetch_core_data, load_key, resolve_authority, validate_asset_permissions, @@ -51,25 +54,16 @@ pub(crate) fn approve_plugin_authority<'a>( fetch_wrapped_plugin::(ctx.accounts.asset, None, args.plugin_type)?; // Validate asset permissions. - let (mut asset, _, _) = validate_asset_permissions( + let (mut asset, _, _) = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin), - Some(&plugin_authority), - None, - None, - AssetV1::check_approve_plugin_authority, - CollectionV1::check_approve_plugin_authority, - PluginType::check_approve_plugin_authority, - AssetV1::validate_approve_plugin_authority, - CollectionV1::validate_approve_plugin_authority, - Plugin::validate_approve_plugin_authority, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -115,21 +109,15 @@ pub(crate) fn approve_collection_plugin_authority<'a>( fetch_wrapped_plugin::(ctx.accounts.collection, None, args.plugin_type)?; // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin), - Some(&plugin_authority), - None, - None, - CollectionV1::check_approve_plugin_authority, - PluginType::check_approve_plugin_authority, - CollectionV1::validate_approve_plugin_authority, - Plugin::validate_approve_plugin_authority, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; process_approve_plugin_authority::( diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index fe61bde8..74b272b4 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -5,8 +5,8 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::{BurnCollectionV1Accounts, BurnV1Accounts}, - plugins::{ExternalPluginAdapter, HookableLifecycleEvent, Plugin, PluginType}, - state::{AssetV1, CollectionV1, CompressionProof, Key, SolanaAccount, Wrappable}, + plugins::BurnLifecycle, + state::{CollectionV1, CompressionProof, Key, SolanaAccount, Wrappable}, utils::{ close_program_account, load_key, rebuild_account_state_from_proof_data, resolve_authority, validate_asset_permissions, verify_proof, @@ -83,25 +83,12 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnV1Args) -> Pro } // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, - AssetV1::check_burn, - CollectionV1::check_burn, - PluginType::check_burn, - AssetV1::validate_burn, - CollectionV1::validate_burn, - Plugin::validate_burn, - Some(ExternalPluginAdapter::validate_burn), - Some(HookableLifecycleEvent::Burn), + &Default::default(), )?; process_burn(ctx.accounts.asset, ctx.accounts.payer)?; diff --git a/programs/mpl-core/src/processor/compress.rs b/programs/mpl-core/src/processor/compress.rs index 5267ffc0..9c698ebc 100644 --- a/programs/mpl-core/src/processor/compress.rs +++ b/programs/mpl-core/src/processor/compress.rs @@ -5,8 +5,8 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::CompressV1Accounts, - plugins::{Plugin, PluginType}, - state::{AssetV1, CollectionV1, Key, Wrappable}, + plugins::CompressLifecycle, + state::{AssetV1, Key, Wrappable}, utils::{ compress_into_account_space, fetch_core_data, load_key, resolve_authority, validate_asset_permissions, @@ -43,25 +43,12 @@ pub(crate) fn compress<'a>( let (asset, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, - AssetV1::check_compress, - CollectionV1::check_compress, - PluginType::check_compress, - AssetV1::validate_compress, - CollectionV1::validate_compress, - Plugin::validate_compress, - None, - None, + &Default::default(), )?; // Compress the asset and plugin registry into account space. diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index e57a0394..ca7e314a 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -10,8 +10,8 @@ use crate::{ instruction::accounts::CreateV2Accounts, plugins::{ create_meta_idempotent, create_plugin_meta, initialize_external_plugin_adapter, - initialize_plugin, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, - ExternalPluginAdapterInitInfo, HookableLifecycleEvent, Plugin, PluginAuthorityPair, + initialize_plugin, CheckResult, CreateLifecycle, ExternalCheckResultBits, + ExternalPluginAdapter, ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair, PluginType, PluginValidationContext, ValidationResult, }, state::{ @@ -148,25 +148,12 @@ pub(crate) fn process_create<'a>( if args.data_state == DataState::AccountState { // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, - AssetV1::check_create, - CollectionV1::check_create, - PluginType::check_create, - AssetV1::validate_create, - CollectionV1::validate_create, - Plugin::validate_create, - Some(ExternalPluginAdapter::validate_create), - Some(HookableLifecycleEvent::Create), + &Default::default(), )?; // Validate permissions for the created asset. diff --git a/programs/mpl-core/src/processor/decompress.rs b/programs/mpl-core/src/processor/decompress.rs index ad286b6a..7a4add4e 100644 --- a/programs/mpl-core/src/processor/decompress.rs +++ b/programs/mpl-core/src/processor/decompress.rs @@ -5,8 +5,8 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, use crate::{ error::MplCoreError, instruction::accounts::DecompressV1Accounts, - plugins::{Plugin, PluginType}, - state::{AssetV1, CollectionV1, CompressionProof, Key}, + plugins::DecompressLifecycle, + state::{CompressionProof, Key}, utils::{ load_key, rebuild_account_state_from_proof_data, resolve_authority, validate_asset_permissions, verify_proof, @@ -59,25 +59,12 @@ pub(crate) fn decompress<'a>( )?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, - AssetV1::check_decompress, - CollectionV1::check_decompress, - PluginType::check_decompress, - AssetV1::validate_decompress, - CollectionV1::validate_decompress, - Plugin::validate_decompress, - None, - None, + &Default::default(), )?; // TODO Enable compression. diff --git a/programs/mpl-core/src/processor/execute.rs b/programs/mpl-core/src/processor/execute.rs index f4923dfa..c3f15a5f 100644 --- a/programs/mpl-core/src/processor/execute.rs +++ b/programs/mpl-core/src/processor/execute.rs @@ -13,8 +13,8 @@ use solana_program::{ use crate::{ error::MplCoreError, instruction::accounts::ExecuteV1Accounts, - plugins::{Plugin, PluginType}, - state::{get_execute_fee, AssetV1, CollectionV1, Key}, + plugins::ExecuteLifecycle, + state::{get_execute_fee, Key}, utils::{load_key, resolve_authority, validate_asset_permissions}, }; @@ -47,25 +47,12 @@ pub(crate) fn execute<'a>(accounts: &'a [AccountInfo<'a>], args: ExecuteV1Args) return Err(MplCoreError::NotAvailable.into()); } - let (mut asset, _, _) = validate_asset_permissions( + let (mut asset, _, _) = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, - AssetV1::check_execute, - CollectionV1::check_execute, - PluginType::check_execute, - AssetV1::validate_execute, - CollectionV1::validate_execute, - Plugin::validate_execute, - None, - None, + &Default::default(), )?; // Increment sequence number and save only if it is `Some(_)`. diff --git a/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs b/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs index 4d1b7eed..77046cc2 100644 --- a/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs @@ -9,7 +9,7 @@ use crate::{ }, plugins::{ delete_external_plugin_adapter, fetch_wrapped_external_plugin_adapter, - ExternalPluginAdapterKey, Plugin, PluginType, + ExternalPluginAdapterKey, LifecycleContext, RemoveExternalPluginAdapterLifecycle, }, state::{AssetV1, CollectionV1, DataBlob, Key}, utils::{ @@ -64,25 +64,16 @@ pub(crate) fn remove_external_plugin_adapter<'a>( )?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - Some(&plugin_to_remove), - Some(&record.authority), - AssetV1::check_remove_external_plugin_adapter, - CollectionV1::check_remove_external_plugin_adapter, - PluginType::check_remove_external_plugin_adapter, - AssetV1::validate_remove_external_plugin_adapter, - CollectionV1::validate_remove_external_plugin_adapter, - Plugin::validate_remove_external_plugin_adapter, - None, - None, + &LifecycleContext { + new_external_plugin_adapter: Some(&plugin_to_remove), + new_external_plugin_adapter_authority: Some(&record.authority), + ..Default::default() + }, )?; process_remove_external_plugin_adapter( @@ -135,22 +126,16 @@ pub(crate) fn remove_collection_external_plugin_adapter<'a>( &args.key, )?; - // Validate asset permissions. - let _ = validate_collection_permissions( + // Validate collection permissions. + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - None, - None, - Some(&plugin_to_remove), - Some(&record.authority), - CollectionV1::check_remove_external_plugin_adapter, - PluginType::check_remove_external_plugin_adapter, - CollectionV1::validate_remove_external_plugin_adapter, - Plugin::validate_remove_external_plugin_adapter, - None, - None, + &LifecycleContext { + new_external_plugin_adapter: Some(&plugin_to_remove), + new_external_plugin_adapter_authority: Some(&record.authority), + ..Default::default() + }, )?; process_remove_external_plugin_adapter( diff --git a/programs/mpl-core/src/processor/remove_plugin.rs b/programs/mpl-core/src/processor/remove_plugin.rs index 9ad623c6..72ac185f 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -5,7 +5,9 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::{RemoveCollectionPluginV1Accounts, RemovePluginV1Accounts}, - plugins::{delete_plugin, fetch_wrapped_plugin, Plugin, PluginType}, + plugins::{ + delete_plugin, fetch_wrapped_plugin, LifecycleContext, PluginType, RemovePluginLifecycle, + }, state::{AssetV1, CollectionV1, DataBlob, Key}, utils::{ fetch_core_data, load_key, resolve_authority, validate_asset_permissions, @@ -56,25 +58,16 @@ pub(crate) fn remove_plugin<'a>( fetch_wrapped_plugin::(ctx.accounts.asset, Some(&asset), args.plugin_type)?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin_to_remove), - Some(&plugin_authority), - None, - None, - AssetV1::check_remove_plugin, - CollectionV1::check_remove_plugin, - PluginType::check_remove_plugin, - AssetV1::validate_remove_plugin, - CollectionV1::validate_remove_plugin, - Plugin::validate_remove_plugin, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin_to_remove), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -130,21 +123,15 @@ pub(crate) fn remove_collection_plugin<'a>( )?; // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin_to_remove), - Some(&plugin_authority), - None, - None, - CollectionV1::check_remove_plugin, - PluginType::check_remove_plugin, - CollectionV1::validate_remove_plugin, - Plugin::validate_remove_plugin, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin_to_remove), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; process_remove_plugin( diff --git a/programs/mpl-core/src/processor/revoke_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_plugin_authority.rs index a5e8bfc3..28682467 100644 --- a/programs/mpl-core/src/processor/revoke_plugin_authority.rs +++ b/programs/mpl-core/src/processor/revoke_plugin_authority.rs @@ -8,8 +8,8 @@ use crate::{ RevokeCollectionPluginAuthorityV1Accounts, RevokePluginAuthorityV1Accounts, }, plugins::{ - fetch_wrapped_plugin, revoke_authority_on_plugin, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, + fetch_wrapped_plugin, revoke_authority_on_plugin, LifecycleContext, PluginHeaderV1, + PluginRegistryV1, PluginType, RevokePluginAuthorityLifecycle, }, state::{AssetV1, CollectionV1, Key}, utils::{ @@ -57,25 +57,16 @@ pub(crate) fn revoke_plugin_authority<'a>( fetch_wrapped_plugin::(ctx.accounts.asset, Some(&asset), args.plugin_type)?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin), - Some(&plugin_authority), - None, - None, - AssetV1::check_revoke_plugin_authority, - CollectionV1::check_revoke_plugin_authority, - PluginType::check_revoke_plugin_authority, - AssetV1::validate_revoke_plugin_authority, - CollectionV1::validate_revoke_plugin_authority, - Plugin::validate_revoke_plugin_authority, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -135,21 +126,15 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( )?; // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin), - Some(&plugin_authority), - None, - None, - CollectionV1::check_revoke_plugin_authority, - PluginType::check_revoke_plugin_authority, - CollectionV1::validate_revoke_plugin_authority, - Plugin::validate_revoke_plugin_authority, - None, - None, + &LifecycleContext { + new_plugin: Some(&plugin), + new_plugin_authority: Some(&plugin_authority), + ..Default::default() + }, )?; let resolved_authorities = diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index 1f4e58bb..48c50fa3 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -5,8 +5,8 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::TransferV1Accounts, - plugins::{ExternalPluginAdapter, HookableLifecycleEvent, Plugin, PluginType}, - state::{AssetV1, Authority, CollectionV1, CompressionProof, Key, SolanaAccount, Wrappable}, + plugins::{LifecycleContext, TransferLifecycle}, + state::{Authority, CompressionProof, Key, SolanaAccount, Wrappable}, utils::{ compress_into_account_space, load_key, rebuild_account_state_from_proof_data, resolve_authority, validate_asset_permissions, verify_proof, @@ -76,26 +76,17 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferV1Args } // Validate asset permissions. - let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( - accounts, - authority, - ctx.accounts.asset, - ctx.accounts.collection, - Some(ctx.accounts.new_owner), - None, - None, - None, - None, - None, - AssetV1::check_transfer, - CollectionV1::check_transfer, - PluginType::check_transfer, - AssetV1::validate_transfer, - CollectionV1::validate_transfer, - Plugin::validate_transfer, - Some(ExternalPluginAdapter::validate_transfer), - Some(HookableLifecycleEvent::Transfer), - )?; + let (mut asset, plugin_header, plugin_registry) = + validate_asset_permissions::( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + &LifecycleContext { + new_owner: Some(ctx.accounts.new_owner), + ..Default::default() + }, + )?; // Reset every owner-managed plugin in the registry. if let (Some(plugin_header), Some(mut plugin_registry)) = diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 99728da6..8e95fd76 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -11,10 +11,10 @@ use crate::{ Context, UpdateCollectionV1Accounts, UpdateV1Accounts, UpdateV2Accounts, }, plugins::{ - fetch_plugin, list_plugins, ExternalPluginAdapter, HookableLifecycleEvent, Plugin, - PluginHeaderV1, PluginRegistryV1, PluginType, UpdateDelegate, PERMANENT_DELEGATES, + fetch_plugin, list_plugins, LifecycleContext, PluginHeaderV1, PluginRegistryV1, PluginType, + UpdateDelegate, UpdateLifecycle, PERMANENT_DELEGATES, }, - state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount, UpdateAuthority}, + state::{CollectionV1, DataBlob, Key, SolanaAccount, UpdateAuthority}, utils::{ assert_collection_authority, load_key, resize_or_reallocate_account, resolve_authority, validate_asset_permissions, validate_collection_permissions, @@ -107,26 +107,17 @@ fn update<'a>( return Err(MplCoreError::NotAvailable.into()); } - let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( - accounts, - authority, - ctx.accounts.asset, - ctx.accounts.collection, - None, - args.new_update_authority.as_ref(), - None, - None, - None, - None, - AssetV1::check_update, - CollectionV1::check_update, - PluginType::check_update, - AssetV1::validate_update, - CollectionV1::validate_update, - Plugin::validate_update, - Some(ExternalPluginAdapter::validate_update), - Some(HookableLifecycleEvent::Update), - )?; + let (mut asset, plugin_header, plugin_registry) = + validate_asset_permissions::( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + &LifecycleContext { + new_asset_authority: args.new_update_authority.as_ref(), + ..Default::default() + }, + )?; // Increment sequence number and save only if it is `Some(_)`. asset.increment_seq_and_save(ctx.accounts.asset)?; @@ -295,22 +286,16 @@ pub(crate) fn update_collection<'a>( } } - let (mut collection, plugin_header, plugin_registry) = validate_collection_permissions( - accounts, - authority, - ctx.accounts.collection, - ctx.accounts.new_update_authority.map(|a| a.key), - None, - None, - None, - None, - CollectionV1::check_update, - PluginType::check_update, - CollectionV1::validate_update, - Plugin::validate_update, - Some(ExternalPluginAdapter::validate_update), - Some(HookableLifecycleEvent::Update), - )?; + let (mut collection, plugin_header, plugin_registry) = + validate_collection_permissions::( + accounts, + authority, + ctx.accounts.collection, + &LifecycleContext { + new_collection_authority: ctx.accounts.new_update_authority.map(|a| a.key), + ..Default::default() + }, + )?; let collection_size = collection.len() as isize; diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index 91bd7329..860e9d37 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -7,7 +7,10 @@ use solana_program::{ use crate::{ error::MplCoreError, instruction::accounts::{UpdateCollectionPluginV1Accounts, UpdatePluginV1Accounts}, - plugins::{fetch_wrapped_plugin, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType}, + plugins::{ + fetch_wrapped_plugin, LifecycleContext, Plugin, PluginHeaderV1, PluginRegistryV1, + PluginType, UpdatePluginLifecycle, + }, state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ load_key, resize_or_reallocate_account, resolve_authority, validate_asset_permissions, @@ -50,26 +53,18 @@ pub(crate) fn update_plugin<'a>( let (target_plugin_authority, _) = fetch_wrapped_plugin::(ctx.accounts.asset, None, PluginType::from(&args.plugin))?; - let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( - accounts, - authority, - ctx.accounts.asset, - ctx.accounts.collection, - None, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - None, - None, - AssetV1::check_update_plugin, - CollectionV1::check_update_plugin, - PluginType::check_update_plugin, - AssetV1::validate_update_plugin, - CollectionV1::validate_update_plugin, - Plugin::validate_update_plugin, - None, - None, - )?; + let (mut asset, plugin_header, plugin_registry) = + validate_asset_permissions::( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + &LifecycleContext { + new_plugin: Some(&args.plugin), + new_plugin_authority: Some(&target_plugin_authority), + ..Default::default() + }, + )?; // Increment sequence number and save only if it is `Some(_)`. asset.increment_seq_and_save(ctx.accounts.asset)?; @@ -119,22 +114,17 @@ pub(crate) fn update_collection_plugin<'a>( )?; // Validate collection permissions. - let (collection, plugin_header, plugin_registry) = validate_collection_permissions( - accounts, - authority, - ctx.accounts.collection, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - None, - None, - CollectionV1::check_update_plugin, - PluginType::check_update_plugin, - CollectionV1::validate_update_plugin, - Plugin::validate_update_plugin, - None, - None, - )?; + let (collection, plugin_header, plugin_registry) = + validate_collection_permissions::( + accounts, + authority, + ctx.accounts.collection, + &LifecycleContext { + new_plugin: Some(&args.plugin), + new_plugin_authority: Some(&target_plugin_authority), + ..Default::default() + }, + )?; process_update_plugin( collection, diff --git a/programs/mpl-core/src/utils/mod.rs b/programs/mpl-core/src/utils/mod.rs index 21577551..1eca9562 100644 --- a/programs/mpl-core/src/utils/mod.rs +++ b/programs/mpl-core/src/utils/mod.rs @@ -8,9 +8,9 @@ use crate::{ error::MplCoreError, plugins::{ validate_external_plugin_adapter_checks, validate_plugin_checks, CheckResult, - ExternalCheckResultBits, ExternalPluginAdapter, ExternalPluginAdapterKey, - ExternalRegistryRecord, HookableLifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, PluginValidationContext, RegistryRecord, ValidationResult, + ExternalCheckResultBits, ExternalPluginAdapterKey, ExternalRegistryRecord, + LifecycleContext, LifecycleEvent, PluginHeaderV1, PluginRegistryV1, PluginType, + RegistryRecord, ValidationResult, }, state::{ AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority, @@ -20,7 +20,6 @@ use mpl_utils::assert_signer; use num_traits::FromPrimitive; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, }; use std::collections::BTreeMap; @@ -100,52 +99,23 @@ pub fn fetch_core_data( } } -#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[allow(clippy::type_complexity)] /// Validate asset permissions using lifecycle validations for asset, collection, and plugins. -pub(crate) fn validate_asset_permissions<'a>( +/// +/// The type parameter `E` specifies the lifecycle event being validated (e.g. +/// `TransferLifecycle`, `BurnLifecycle`), which determines which check/validate +/// methods are called on assets, collections, and plugins. +/// +/// The `ctx` parameter carries lifecycle-specific context (new owner, plugin +/// targets, etc.). Use `Default::default()` when no lifecycle-specific context +/// is needed. +pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, asset: &'a AccountInfo<'a>, collection: Option<&'a AccountInfo<'a>>, - new_owner: Option<&'a AccountInfo<'a>>, - new_authority: Option<&UpdateAuthority>, - new_plugin: Option<&Plugin>, - new_plugin_authority: Option<&Authority>, - new_external_plugin_adapter: Option<&ExternalPluginAdapter>, - new_external_plugin_adapter_authority: Option<&Authority>, - asset_check_fp: fn() -> CheckResult, - collection_check_fp: fn() -> CheckResult, - plugin_check_fp: fn(&PluginType) -> CheckResult, - asset_validate_fp: fn( - &AssetV1, - &AccountInfo, - Option<&Plugin>, - Option<&ExternalPluginAdapter>, - ) -> Result, - collection_validate_fp: fn( - &CollectionV1, - &AccountInfo, - Option<&Plugin>, - Option<&ExternalPluginAdapter>, - ) -> Result, - plugin_validate_fp: fn( - &Plugin, - &PluginValidationContext, - ) -> Result, - external_plugin_adapter_validate_fp: Option< - fn( - &ExternalPluginAdapter, - &PluginValidationContext, - ) -> Result, - >, - hookable_lifecycle_event: Option, + ctx: &LifecycleContext<'a, '_>, ) -> Result<(AssetV1, Option, Option), ProgramError> { - if external_plugin_adapter_validate_fp.is_some() && hookable_lifecycle_event.is_none() - || external_plugin_adapter_validate_fp.is_none() && hookable_lifecycle_event.is_some() - { - panic!("Missing function parameters to validate_asset_permissions"); - } - let (deserialized_asset, plugin_header, plugin_registry) = fetch_core_data::(asset)?; let resolved_authorities = resolve_pubkey_to_authorities(authority_info, collection, &deserialized_asset)?; @@ -168,19 +138,21 @@ pub(crate) fn validate_asset_permissions<'a>( > = BTreeMap::new(); // The asset approval overrides the collection approval. - let asset_check = asset_check_fp(); + let asset_check = E::check_asset(); let collection_check = if collection.is_some() { - collection_check_fp() + E::check_collection() } else { CheckResult::None }; + let hookable_lifecycle_event = E::hookable_lifecycle_event(); + // Check the collection plugins first. if let Some(collection_info) = collection { let (_, _, registry) = fetch_core_data::(collection_info)?; if let Some(r) = registry { - r.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); + r.check_registry(Key::CollectionV1, E::check_plugin, &mut checks); if let Some(lifecycle_event) = &hookable_lifecycle_event { r.check_adapter_registry( @@ -196,7 +168,7 @@ pub(crate) fn validate_asset_permissions<'a>( // 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::AssetV1, plugin_check_fp, &mut checks); + registry.check_registry(Key::AssetV1, E::check_plugin, &mut checks); if let Some(lifecycle_event) = &hookable_lifecycle_event { registry.check_adapter_registry( asset, @@ -211,11 +183,11 @@ pub(crate) fn validate_asset_permissions<'a>( let mut approved = false; let mut rejected = false; if asset_check != CheckResult::None { - match asset_validate_fp( + match E::validate_asset( &deserialized_asset, authority_info, - new_plugin, - new_external_plugin_adapter, + ctx.new_plugin, + ctx.new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -227,11 +199,11 @@ pub(crate) fn validate_asset_permissions<'a>( }; if collection_check != CheckResult::None { - match collection_validate_fp( + match E::validate_collection( &CollectionV1::load(collection.ok_or(MplCoreError::MissingCollection)?, 0)?, authority_info, - new_plugin, - new_external_plugin_adapter, + ctx.new_plugin, + ctx.new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -242,22 +214,15 @@ pub(crate) fn validate_asset_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::CollectionV1, accounts, &checks, authority_info, - new_owner, - new_authority, - None, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, Some(asset), collection, &resolved_authorities, - plugin_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -267,22 +232,15 @@ pub(crate) fn validate_asset_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::AssetV1, accounts, &checks, authority_info, - new_owner, - new_authority, - None, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, Some(asset), collection, &resolved_authorities, - plugin_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -292,23 +250,16 @@ pub(crate) fn validate_asset_permissions<'a>( } }; - if let Some(external_plugin_adapter_validate_fp) = external_plugin_adapter_validate_fp { - match validate_external_plugin_adapter_checks( + if hookable_lifecycle_event.is_some() { + match validate_external_plugin_adapter_checks::( Key::CollectionV1, accounts, &external_checks, authority_info, - new_owner, - new_authority, - None, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, Some(asset), collection, &resolved_authorities, - external_plugin_adapter_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -317,22 +268,15 @@ pub(crate) fn validate_asset_permissions<'a>( ValidationResult::ForceApproved => unreachable!(), }; - match validate_external_plugin_adapter_checks( + match validate_external_plugin_adapter_checks::( Key::AssetV1, accounts, &external_checks, authority_info, - new_owner, - new_authority, - None, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, Some(asset), collection, &resolved_authorities, - external_plugin_adapter_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -352,35 +296,19 @@ pub(crate) fn validate_asset_permissions<'a>( } /// Validate collection permissions using lifecycle validations for collection and plugins. -#[allow(clippy::type_complexity, clippy::too_many_arguments)] -pub(crate) fn validate_collection_permissions<'a>( +/// +/// The type parameter `E` specifies the lifecycle event being validated (e.g. +/// `UpdateLifecycle`, `AddPluginLifecycle`), which determines which check/validate +/// methods are called on collections and plugins. +/// +/// The `ctx` parameter carries lifecycle-specific context (plugin targets, etc.). +/// Use `Default::default()` when no lifecycle-specific context is needed. +#[allow(clippy::type_complexity)] +pub(crate) fn validate_collection_permissions<'a, E: LifecycleEvent>( accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, collection: &'a AccountInfo<'a>, - new_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, - new_plugin_authority: Option<&Authority>, - new_external_plugin_adapter: Option<&ExternalPluginAdapter>, - new_external_plugin_adapter_authority: Option<&Authority>, - collection_check_fp: fn() -> CheckResult, - plugin_check_fp: fn(&PluginType) -> CheckResult, - collection_validate_fp: fn( - &CollectionV1, - &AccountInfo, - Option<&Plugin>, - Option<&ExternalPluginAdapter>, - ) -> Result, - plugin_validate_fp: fn( - &Plugin, - &PluginValidationContext, - ) -> Result, - external_plugin_adapter_validate_fp: Option< - fn( - &ExternalPluginAdapter, - &PluginValidationContext, - ) -> Result, - >, - hookable_lifecycle_event: Option, + ctx: &LifecycleContext<'a, '_>, ) -> Result< ( CollectionV1, @@ -389,12 +317,6 @@ pub(crate) fn validate_collection_permissions<'a>( ), ProgramError, > { - if external_plugin_adapter_validate_fp.is_some() && hookable_lifecycle_event.is_none() - || external_plugin_adapter_validate_fp.is_none() && hookable_lifecycle_event.is_some() - { - panic!("Missing function parameters to validate_asset_permissions"); - } - let (deserialized_collection, plugin_header, plugin_registry) = fetch_core_data::(collection)?; let resolved_authorities = @@ -405,11 +327,12 @@ pub(crate) fn validate_collection_permissions<'a>( (Key, ExternalCheckResultBits, ExternalRegistryRecord), > = BTreeMap::new(); - let core_check = (Key::CollectionV1, collection_check_fp()); + let collection_check = E::check_collection(); + let hookable_lifecycle_event = E::hookable_lifecycle_event(); // Check the collection plugins. if let Some(registry) = plugin_registry.as_ref() { - registry.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); + registry.check_registry(Key::CollectionV1, E::check_plugin, &mut checks); if let Some(lifecycle_event) = hookable_lifecycle_event { registry.check_adapter_registry( collection, @@ -424,22 +347,15 @@ pub(crate) fn validate_collection_permissions<'a>( let mut approved = false; let mut rejected = false; if matches!( - core_check, - ( - Key::CollectionV1, - CheckResult::CanApprove | CheckResult::CanReject | CheckResult::CanForceApprove - ) + collection_check, + CheckResult::CanApprove | CheckResult::CanReject | CheckResult::CanForceApprove ) { - let result = match core_check.0 { - Key::CollectionV1 => collection_validate_fp( - &deserialized_collection, - authority_info, - new_plugin, - new_external_plugin_adapter, - )?, - _ => return Err(MplCoreError::IncorrectAccount.into()), - }; - match result { + match E::validate_collection( + &deserialized_collection, + authority_info, + ctx.new_plugin, + ctx.new_external_plugin_adapter, + )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, ValidationResult::Pass => (), @@ -449,22 +365,15 @@ pub(crate) fn validate_collection_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::CollectionV1, accounts, &checks, authority_info, None, - None, - new_authority, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, - None, Some(collection), &resolved_authorities, - plugin_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -474,23 +383,16 @@ pub(crate) fn validate_collection_permissions<'a>( } }; - if let Some(external_plugin_adapter_validate_fp) = external_plugin_adapter_validate_fp { - match validate_external_plugin_adapter_checks( + if E::hookable_lifecycle_event().is_some() { + match validate_external_plugin_adapter_checks::( Key::CollectionV1, accounts, &external_checks, authority_info, None, - None, - new_authority, - new_plugin, - new_plugin_authority, - new_external_plugin_adapter, - new_external_plugin_adapter_authority, - None, Some(collection), &resolved_authorities, - external_plugin_adapter_validate_fp, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true,