From 181ef0dfcd457cb4b1907844399c80a69220c302 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 21:34:51 +0000 Subject: [PATCH 1/2] Replace function pointer params with LifecycleEvent trait for plugin validation Instead of passing 6-8 function pointers (check_fp, validate_fp, etc.) to validate_asset_permissions and validate_collection_permissions, callers now specify the lifecycle event as a type parameter using a LifecycleEvent trait. This eliminates the function pointer plumbing at every call site, replacing e.g. `validate_asset_permissions(..., 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))` with just `validate_asset_permissions::(...)`. A define_lifecycle! macro generates zero-sized types implementing the trait for all 14 lifecycle events, keeping the boilerplate minimal. The generics are monomorphized at compile time so there is zero runtime cost. https://claude.ai/code/session_01PvtubR12q5JC5MjWHHZdx9 --- programs/mpl-core/src/plugins/lifecycle.rs | 188 ++++++++++++++++-- .../processor/add_external_plugin_adapter.rs | 25 +-- programs/mpl-core/src/processor/add_plugin.rs | 22 +- .../src/processor/approve_plugin_authority.rs | 23 +-- programs/mpl-core/src/processor/burn.rs | 14 +- programs/mpl-core/src/processor/compress.rs | 14 +- programs/mpl-core/src/processor/create.rs | 14 +- programs/mpl-core/src/processor/decompress.rs | 14 +- programs/mpl-core/src/processor/execute.rs | 14 +- .../remove_external_plugin_adapter.rs | 22 +- .../mpl-core/src/processor/remove_plugin.rs | 20 +- .../src/processor/revoke_plugin_authority.rs | 22 +- programs/mpl-core/src/processor/transfer.rs | 37 ++-- programs/mpl-core/src/processor/update.rs | 66 +++--- .../mpl-core/src/processor/update_plugin.rs | 65 +++--- programs/mpl-core/src/utils/mod.rs | 137 ++++--------- 16 files changed, 323 insertions(+), 374 deletions(-) diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index cf0b9c28..01493457 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,11 +663,177 @@ pub(crate) trait PluginValidation { } } +/// 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>( +pub(crate) fn validate_plugin_checks<'a, E: LifecycleEvent>( key: Key, accounts: &'a [AccountInfo<'a>], checks: &BTreeMap, @@ -682,10 +848,6 @@ pub(crate) fn validate_plugin_checks<'a>( asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], - plugin_validate_fp: fn( - &Plugin, - &PluginValidationContext, - ) -> Result, ) -> Result { let mut approved = false; let mut rejected = false; @@ -718,7 +880,7 @@ pub(crate) fn validate_plugin_checks<'a>( target_external_plugin_authority: new_external_plugin_authority, }; - let result = plugin_validate_fp( + let result = E::validate_plugin( &Plugin::load(account, registry_record.offset)?, &validation_ctx, )?; @@ -744,7 +906,7 @@ pub(crate) fn validate_plugin_checks<'a>( /// 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>( +pub(crate) fn validate_external_plugin_adapter_checks<'a, E: LifecycleEvent>( key: Key, accounts: &'a [AccountInfo<'a>], external_checks: &BTreeMap< @@ -762,10 +924,6 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], - external_plugin_adapter_validate_fp: fn( - &ExternalPluginAdapter, - &PluginValidationContext, - ) -> Result, ) -> Result { let mut approved = false; for (check_key, check_result, external_registry_record) in external_checks.values() { @@ -796,7 +954,7 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( target_external_plugin_authority: new_external_plugin_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..3c2fdae2 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, + PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -101,8 +101,7 @@ 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, @@ -113,14 +112,6 @@ pub(crate) fn add_external_plugin_adapter<'a>( 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, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -206,7 +197,7 @@ 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, @@ -215,12 +206,6 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( 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, )?; 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..aff9f3cc 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, Plugin, PluginType, + PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -77,7 +77,7 @@ 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, @@ -88,14 +88,6 @@ pub(crate) fn add_plugin<'a>( 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, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -163,7 +155,7 @@ pub(crate) fn add_collection_plugin<'a>( } // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, @@ -172,12 +164,6 @@ pub(crate) fn add_collection_plugin<'a>( Some(&target_plugin_authority), None, None, - CollectionV1::check_add_plugin, - PluginType::check_add_plugin, - CollectionV1::validate_add_plugin, - Plugin::validate_add_plugin, - None, - None, )?; 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..f7b99ba2 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, + PluginType, + }, state::{AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount}, utils::{ fetch_core_data, load_key, resolve_authority, validate_asset_permissions, @@ -51,7 +54,7 @@ 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, @@ -62,14 +65,6 @@ pub(crate) fn approve_plugin_authority<'a>( 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, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -115,7 +110,7 @@ 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, @@ -124,12 +119,6 @@ pub(crate) fn approve_collection_plugin_authority<'a>( 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, )?; process_approve_plugin_authority::( diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index fe61bde8..57e395f7 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,7 +83,7 @@ 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, @@ -94,14 +94,6 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnV1Args) -> Pro 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), )?; 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..787f345e 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,7 +43,7 @@ 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, @@ -54,14 +54,6 @@ pub(crate) fn compress<'a>( None, None, None, - AssetV1::check_compress, - CollectionV1::check_compress, - PluginType::check_compress, - AssetV1::validate_compress, - CollectionV1::validate_compress, - Plugin::validate_compress, - None, - None, )?; // 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..0435a6fc 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,7 +148,7 @@ 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, @@ -159,14 +159,6 @@ pub(crate) fn process_create<'a>( 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), )?; // 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..76e43e7a 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,7 +59,7 @@ pub(crate) fn decompress<'a>( )?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, @@ -70,14 +70,6 @@ pub(crate) fn decompress<'a>( None, None, None, - AssetV1::check_decompress, - CollectionV1::check_decompress, - PluginType::check_decompress, - AssetV1::validate_decompress, - CollectionV1::validate_decompress, - Plugin::validate_decompress, - None, - None, )?; // TODO Enable compression. diff --git a/programs/mpl-core/src/processor/execute.rs b/programs/mpl-core/src/processor/execute.rs index f4923dfa..22473c6f 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,7 +47,7 @@ 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, @@ -58,14 +58,6 @@ pub(crate) fn execute<'a>(accounts: &'a [AccountInfo<'a>], args: ExecuteV1Args) None, None, None, - AssetV1::check_execute, - CollectionV1::check_execute, - PluginType::check_execute, - AssetV1::validate_execute, - CollectionV1::validate_execute, - Plugin::validate_execute, - None, - None, )?; // 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..ce3f33bd 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, RemoveExternalPluginAdapterLifecycle, }, state::{AssetV1, CollectionV1, DataBlob, Key}, utils::{ @@ -64,7 +64,7 @@ pub(crate) fn remove_external_plugin_adapter<'a>( )?; // Validate asset permissions. - let _ = validate_asset_permissions( + let _ = validate_asset_permissions::( accounts, authority, ctx.accounts.asset, @@ -75,14 +75,6 @@ pub(crate) fn remove_external_plugin_adapter<'a>( 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, )?; process_remove_external_plugin_adapter( @@ -135,8 +127,8 @@ 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, @@ -145,12 +137,6 @@ pub(crate) fn remove_collection_external_plugin_adapter<'a>( 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, )?; 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..3e8ec3e7 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -5,7 +5,7 @@ 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, PluginType, RemovePluginLifecycle}, state::{AssetV1, CollectionV1, DataBlob, Key}, utils::{ fetch_core_data, load_key, resolve_authority, validate_asset_permissions, @@ -56,7 +56,7 @@ 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, @@ -67,14 +67,6 @@ pub(crate) fn remove_plugin<'a>( 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, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -130,7 +122,7 @@ pub(crate) fn remove_collection_plugin<'a>( )?; // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, @@ -139,12 +131,6 @@ pub(crate) fn remove_collection_plugin<'a>( Some(&plugin_authority), None, None, - CollectionV1::check_remove_plugin, - PluginType::check_remove_plugin, - CollectionV1::validate_remove_plugin, - Plugin::validate_remove_plugin, - None, - None, )?; 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..69dc23f7 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, PluginHeaderV1, PluginRegistryV1, + PluginType, RevokePluginAuthorityLifecycle, }, state::{AssetV1, CollectionV1, Key}, utils::{ @@ -57,7 +57,7 @@ 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, @@ -68,14 +68,6 @@ pub(crate) fn revoke_plugin_authority<'a>( 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, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -135,7 +127,7 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( )?; // Validate collection permissions. - let _ = validate_collection_permissions( + let _ = validate_collection_permissions::( accounts, authority, ctx.accounts.collection, @@ -144,12 +136,6 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( 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, )?; let resolved_authorities = diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index 1f4e58bb..fba8d710 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::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,19 @@ 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, + Some(ctx.accounts.new_owner), + None, + None, + None, + None, + None, + )?; // 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..5acd4b30 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, 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,19 @@ 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, + None, + args.new_update_authority.as_ref(), + None, + None, + None, + None, + )?; // Increment sequence number and save only if it is `Some(_)`. asset.increment_seq_and_save(ctx.accounts.asset)?; @@ -295,22 +288,17 @@ 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, + ctx.accounts.new_update_authority.map(|a| a.key), + None, + None, + None, + None, + )?; 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..1085e8e9 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, 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,19 @@ 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, + None, + None, + Some(&args.plugin), + Some(&target_plugin_authority), + None, + None, + )?; // Increment sequence number and save only if it is `Some(_)`. asset.increment_seq_and_save(ctx.accounts.asset)?; @@ -119,22 +115,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, + None, + Some(&args.plugin), + Some(&target_plugin_authority), + None, + None, + )?; process_update_plugin( collection, diff --git a/programs/mpl-core/src/utils/mod.rs b/programs/mpl-core/src/utils/mod.rs index 21577551..53579805 100644 --- a/programs/mpl-core/src/utils/mod.rs +++ b/programs/mpl-core/src/utils/mod.rs @@ -9,8 +9,8 @@ use crate::{ plugins::{ validate_external_plugin_adapter_checks, validate_plugin_checks, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, ExternalPluginAdapterKey, - ExternalRegistryRecord, HookableLifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, PluginValidationContext, RegistryRecord, ValidationResult, + ExternalRegistryRecord, LifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, + PluginType, RegistryRecord, ValidationResult, }, state::{ AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority, @@ -102,7 +102,11 @@ pub fn fetch_core_data( #[allow(clippy::too_many_arguments, 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. +pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, asset: &'a AccountInfo<'a>, @@ -113,39 +117,7 @@ pub(crate) fn validate_asset_permissions<'a>( 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, ) -> 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 +140,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 +170,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,7 +185,7 @@ 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, @@ -227,7 +201,7 @@ 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, @@ -242,7 +216,7 @@ pub(crate) fn validate_asset_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::CollectionV1, accounts, &checks, @@ -257,7 +231,6 @@ pub(crate) fn validate_asset_permissions<'a>( Some(asset), collection, &resolved_authorities, - plugin_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -267,7 +240,7 @@ pub(crate) fn validate_asset_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::AssetV1, accounts, &checks, @@ -282,7 +255,6 @@ pub(crate) fn validate_asset_permissions<'a>( Some(asset), collection, &resolved_authorities, - plugin_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -292,8 +264,8 @@ 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, @@ -308,7 +280,6 @@ pub(crate) fn validate_asset_permissions<'a>( Some(asset), collection, &resolved_authorities, - external_plugin_adapter_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -317,7 +288,7 @@ 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, @@ -332,7 +303,6 @@ pub(crate) fn validate_asset_permissions<'a>( Some(asset), collection, &resolved_authorities, - external_plugin_adapter_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -352,8 +322,12 @@ pub(crate) fn validate_asset_permissions<'a>( } /// Validate collection permissions using lifecycle validations for collection and plugins. +/// +/// 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. #[allow(clippy::type_complexity, clippy::too_many_arguments)] -pub(crate) fn validate_collection_permissions<'a>( +pub(crate) fn validate_collection_permissions<'a, E: LifecycleEvent>( accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, collection: &'a AccountInfo<'a>, @@ -362,25 +336,6 @@ pub(crate) fn validate_collection_permissions<'a>( 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, ) -> Result< ( CollectionV1, @@ -389,12 +344,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 +354,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 +374,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, + new_plugin, + new_external_plugin_adapter, + )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, ValidationResult::Pass => (), @@ -449,7 +392,7 @@ pub(crate) fn validate_collection_permissions<'a>( } }; - match validate_plugin_checks( + match validate_plugin_checks::( Key::CollectionV1, accounts, &checks, @@ -464,7 +407,6 @@ pub(crate) fn validate_collection_permissions<'a>( None, Some(collection), &resolved_authorities, - plugin_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -474,8 +416,8 @@ 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, @@ -490,7 +432,6 @@ pub(crate) fn validate_collection_permissions<'a>( None, Some(collection), &resolved_authorities, - external_plugin_adapter_validate_fp, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, From 55c9fed9c38bf9ac9cdebbeb3d222873dc8a3791 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 21:57:12 +0000 Subject: [PATCH 2/2] Bundle optional validation params into LifecycleContext struct with Default Replaces 6-8 positional None parameters at each call site with a LifecycleContext struct that defaults all fields to None. Callers now only specify the fields relevant to their lifecycle event using struct initialization with ..Default::default(). This reduces validate_asset_permissions from 10 params to 5 and validate_collection_permissions from 8 params to 4, while also simplifying the internal validate_plugin_checks (14 -> 8 params) and validate_external_plugin_adapter_checks (14 -> 8 params). https://claude.ai/code/session_01PvtubR12q5JC5MjWHHZdx9 --- programs/mpl-core/src/plugins/lifecycle.rs | 97 +++++++++++++------ .../processor/add_external_plugin_adapter.rs | 23 +++-- programs/mpl-core/src/processor/add_plugin.rs | 25 +++-- .../src/processor/approve_plugin_authority.rs | 23 +++-- programs/mpl-core/src/processor/burn.rs | 7 +- programs/mpl-core/src/processor/compress.rs | 7 +- programs/mpl-core/src/processor/create.rs | 7 +- programs/mpl-core/src/processor/decompress.rs | 7 +- programs/mpl-core/src/processor/execute.rs | 7 +- .../remove_external_plugin_adapter.rs | 23 +++-- .../mpl-core/src/processor/remove_plugin.rs | 25 ++--- .../src/processor/revoke_plugin_authority.rs | 25 +++-- programs/mpl-core/src/processor/transfer.rs | 12 +-- programs/mpl-core/src/processor/update.rs | 23 ++--- .../mpl-core/src/processor/update_plugin.rs | 25 +++-- programs/mpl-core/src/utils/mod.rs | 91 +++++------------ 16 files changed, 195 insertions(+), 232 deletions(-) diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 01493457..8ac21abe 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -663,6 +663,55 @@ 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. /// @@ -832,22 +881,16 @@ define_lifecycle!(RemoveExternalPluginAdapterLifecycle, check_remove_external_pl /// 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)] +#[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], + ctx: &LifecycleContext<'a, '_>, ) -> Result { let mut approved = false; let mut rejected = false; @@ -871,13 +914,13 @@ pub(crate) fn validate_plugin_checks<'a, E: LifecycleEvent>( 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 = E::validate_plugin( @@ -905,7 +948,7 @@ pub(crate) fn validate_plugin_checks<'a, E: LifecycleEvent>( /// 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)] +#[allow(clippy::type_complexity)] pub(crate) fn validate_external_plugin_adapter_checks<'a, E: LifecycleEvent>( key: Key, accounts: &'a [AccountInfo<'a>], @@ -914,16 +957,10 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a, E: LifecycleEvent>( (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], + ctx: &LifecycleContext<'a, '_>, ) -> Result { let mut approved = false; for (check_key, check_result, external_registry_record) in external_checks.values() { @@ -945,13 +982,13 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a, E: LifecycleEvent>( 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 = E::validate_external_plugin_adapter( 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 3c2fdae2..bcb4070f 100644 --- a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs @@ -10,7 +10,7 @@ use crate::{ plugins::{ create_meta_idempotent, initialize_external_plugin_adapter, AddExternalPluginAdapterLifecycle, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, - PluginValidationContext, ValidationResult, + LifecycleContext, PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -106,12 +106,11 @@ pub(crate) fn add_external_plugin_adapter<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - Some(&external_plugin_adapter), - Some(&external_plugin_adapter_authority), + &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(_)`. @@ -201,11 +200,11 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( accounts, authority, ctx.accounts.collection, - None, - None, - None, - Some(&external_plugin_adapter), - Some(&external_plugin_adapter_authority), + &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 aff9f3cc..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, AddPluginLifecycle, Plugin, PluginType, - PluginValidationContext, ValidationResult, + create_meta_idempotent, initialize_plugin, AddPluginLifecycle, LifecycleContext, Plugin, + PluginType, PluginValidationContext, ValidationResult, }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -82,12 +82,11 @@ pub(crate) fn add_plugin<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - 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(_)`. @@ -159,11 +158,11 @@ pub(crate) fn add_collection_plugin<'a>( accounts, authority, ctx.accounts.collection, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - 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 f7b99ba2..5b47048a 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -9,7 +9,7 @@ use crate::{ }, plugins::{ approve_authority_on_plugin, fetch_wrapped_plugin, ApprovePluginAuthorityLifecycle, - PluginType, + LifecycleContext, PluginType, }, state::{AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount}, utils::{ @@ -59,12 +59,11 @@ pub(crate) fn approve_plugin_authority<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin), - Some(&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(_)`. @@ -114,11 +113,11 @@ pub(crate) fn approve_collection_plugin_authority<'a>( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin), - Some(&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 57e395f7..74b272b4 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -88,12 +88,7 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnV1Args) -> Pro authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, + &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 787f345e..9c698ebc 100644 --- a/programs/mpl-core/src/processor/compress.rs +++ b/programs/mpl-core/src/processor/compress.rs @@ -48,12 +48,7 @@ pub(crate) fn compress<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - 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 0435a6fc..ca7e314a 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -153,12 +153,7 @@ pub(crate) fn process_create<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - None, - None, + &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 76e43e7a..7a4add4e 100644 --- a/programs/mpl-core/src/processor/decompress.rs +++ b/programs/mpl-core/src/processor/decompress.rs @@ -64,12 +64,7 @@ pub(crate) fn decompress<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - 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 22473c6f..c3f15a5f 100644 --- a/programs/mpl-core/src/processor/execute.rs +++ b/programs/mpl-core/src/processor/execute.rs @@ -52,12 +52,7 @@ pub(crate) fn execute<'a>(accounts: &'a [AccountInfo<'a>], args: ExecuteV1Args) authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - 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 ce3f33bd..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, RemoveExternalPluginAdapterLifecycle, + ExternalPluginAdapterKey, LifecycleContext, RemoveExternalPluginAdapterLifecycle, }, state::{AssetV1, CollectionV1, DataBlob, Key}, utils::{ @@ -69,12 +69,11 @@ pub(crate) fn remove_external_plugin_adapter<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - None, - None, - Some(&plugin_to_remove), - Some(&record.authority), + &LifecycleContext { + new_external_plugin_adapter: Some(&plugin_to_remove), + new_external_plugin_adapter_authority: Some(&record.authority), + ..Default::default() + }, )?; process_remove_external_plugin_adapter( @@ -132,11 +131,11 @@ pub(crate) fn remove_collection_external_plugin_adapter<'a>( accounts, authority, ctx.accounts.collection, - None, - None, - None, - Some(&plugin_to_remove), - Some(&record.authority), + &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 3e8ec3e7..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, PluginType, RemovePluginLifecycle}, + 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, @@ -61,12 +63,11 @@ pub(crate) fn remove_plugin<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin_to_remove), - Some(&plugin_authority), - 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(_)`. @@ -126,11 +127,11 @@ pub(crate) fn remove_collection_plugin<'a>( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin_to_remove), - Some(&plugin_authority), - 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 69dc23f7..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, PluginHeaderV1, PluginRegistryV1, - PluginType, RevokePluginAuthorityLifecycle, + fetch_wrapped_plugin, revoke_authority_on_plugin, LifecycleContext, PluginHeaderV1, + PluginRegistryV1, PluginType, RevokePluginAuthorityLifecycle, }, state::{AssetV1, CollectionV1, Key}, utils::{ @@ -62,12 +62,11 @@ pub(crate) fn revoke_plugin_authority<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&plugin), - Some(&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(_)`. @@ -131,11 +130,11 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( accounts, authority, ctx.accounts.collection, - None, - Some(&plugin), - Some(&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 fba8d710..48c50fa3 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -5,7 +5,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::TransferV1Accounts, - plugins::TransferLifecycle, + plugins::{LifecycleContext, TransferLifecycle}, state::{Authority, CompressionProof, Key, SolanaAccount, Wrappable}, utils::{ compress_into_account_space, load_key, rebuild_account_state_from_proof_data, @@ -82,12 +82,10 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferV1Args authority, ctx.accounts.asset, ctx.accounts.collection, - Some(ctx.accounts.new_owner), - None, - None, - None, - None, - None, + &LifecycleContext { + new_owner: Some(ctx.accounts.new_owner), + ..Default::default() + }, )?; // Reset every owner-managed plugin in the registry. diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 5acd4b30..8e95fd76 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -11,8 +11,8 @@ use crate::{ Context, UpdateCollectionV1Accounts, UpdateV1Accounts, UpdateV2Accounts, }, plugins::{ - fetch_plugin, list_plugins, PluginHeaderV1, PluginRegistryV1, PluginType, UpdateDelegate, - UpdateLifecycle, PERMANENT_DELEGATES, + fetch_plugin, list_plugins, LifecycleContext, PluginHeaderV1, PluginRegistryV1, PluginType, + UpdateDelegate, UpdateLifecycle, PERMANENT_DELEGATES, }, state::{CollectionV1, DataBlob, Key, SolanaAccount, UpdateAuthority}, utils::{ @@ -113,12 +113,10 @@ fn update<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - args.new_update_authority.as_ref(), - None, - None, - None, - None, + &LifecycleContext { + new_asset_authority: args.new_update_authority.as_ref(), + ..Default::default() + }, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -293,11 +291,10 @@ pub(crate) fn update_collection<'a>( accounts, authority, ctx.accounts.collection, - ctx.accounts.new_update_authority.map(|a| a.key), - None, - None, - None, - None, + &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 1085e8e9..860e9d37 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -8,8 +8,8 @@ use crate::{ error::MplCoreError, instruction::accounts::{UpdateCollectionPluginV1Accounts, UpdatePluginV1Accounts}, plugins::{ - fetch_wrapped_plugin, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, - UpdatePluginLifecycle, + fetch_wrapped_plugin, LifecycleContext, Plugin, PluginHeaderV1, PluginRegistryV1, + PluginType, UpdatePluginLifecycle, }, state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ @@ -59,12 +59,11 @@ pub(crate) fn update_plugin<'a>( authority, ctx.accounts.asset, ctx.accounts.collection, - None, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - 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(_)`. @@ -120,11 +119,11 @@ pub(crate) fn update_collection_plugin<'a>( accounts, authority, ctx.accounts.collection, - None, - Some(&args.plugin), - Some(&target_plugin_authority), - None, - None, + &LifecycleContext { + new_plugin: Some(&args.plugin), + new_plugin_authority: Some(&target_plugin_authority), + ..Default::default() + }, )?; process_update_plugin( diff --git a/programs/mpl-core/src/utils/mod.rs b/programs/mpl-core/src/utils/mod.rs index 53579805..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, LifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, 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,23 +99,22 @@ 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. /// /// 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>, + ctx: &LifecycleContext<'a, '_>, ) -> Result<(AssetV1, Option, Option), ProgramError> { let (deserialized_asset, plugin_header, plugin_registry) = fetch_core_data::(asset)?; let resolved_authorities = @@ -188,8 +186,8 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, @@ -204,8 +202,8 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, @@ -221,16 +219,10 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -245,16 +237,10 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -270,16 +256,10 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -293,16 +273,10 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( 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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -326,16 +300,15 @@ pub(crate) fn validate_asset_permissions<'a, E: LifecycleEvent>( /// 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. -#[allow(clippy::type_complexity, clippy::too_many_arguments)] +/// +/// 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>, + ctx: &LifecycleContext<'a, '_>, ) -> Result< ( CollectionV1, @@ -380,8 +353,8 @@ pub(crate) fn validate_collection_permissions<'a, E: LifecycleEvent>( match E::validate_collection( &deserialized_collection, authority_info, - new_plugin, - new_external_plugin_adapter, + ctx.new_plugin, + ctx.new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -398,15 +371,9 @@ pub(crate) fn validate_collection_permissions<'a, E: LifecycleEvent>( &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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -423,15 +390,9 @@ pub(crate) fn validate_collection_permissions<'a, E: LifecycleEvent>( &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, + ctx, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true,