diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index 0c7414a4..d7894152 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -689,6 +689,35 @@ export class CannotAddDataSectionError extends ProgramError { codeToErrorMap.set(0x2f, CannotAddDataSectionError); nameToErrorMap.set('CannotAddDataSection', CannotAddDataSectionError); +/** PluginNotAllowedOnAsset: Plugin is not allowed to be added to an Asset */ +export class PluginNotAllowedOnAssetError extends ProgramError { + override readonly name: string = 'PluginNotAllowedOnAsset'; + + readonly code: number = 0x30; // 48 + + constructor(program: Program, cause?: Error) { + super('Plugin is not allowed to be added to an Asset', program, cause); + } +} +codeToErrorMap.set(0x30, PluginNotAllowedOnAssetError); +nameToErrorMap.set('PluginNotAllowedOnAsset', PluginNotAllowedOnAssetError); + +/** PluginNotAllowedOnCollection: Plugin is not allowed to be added to a Collection */ +export class PluginNotAllowedOnCollectionError extends ProgramError { + override readonly name: string = 'PluginNotAllowedOnCollection'; + + readonly code: number = 0x31; // 49 + + constructor(program: Program, cause?: Error) { + super('Plugin is not allowed to be added to a Collection', program, cause); + } +} +codeToErrorMap.set(0x31, PluginNotAllowedOnCollectionError); +nameToErrorMap.set( + 'PluginNotAllowedOnCollection', + PluginNotAllowedOnCollectionError +); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/test/plugins/collection/masterEdition.test.ts b/clients/js/test/plugins/collection/masterEdition.test.ts index 2544d47e..3fc193ba 100644 --- a/clients/js/test/plugins/collection/masterEdition.test.ts +++ b/clients/js/test/plugins/collection/masterEdition.test.ts @@ -128,7 +128,7 @@ test('it cannot add masterEdition to asset', async (t) => { }).sendAndConfirm(umi); await t.throwsAsync(result, { - name: 'InvalidPlugin', + name: 'PluginNotAllowedOnAsset', }); }); @@ -150,6 +150,6 @@ test('it cannot create asset with masterEdition', async (t) => { }); await t.throwsAsync(result, { - name: 'InvalidPlugin', + name: 'PluginNotAllowedOnAsset', }); }); diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index 93e72b13..d6f96f3b 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -154,6 +154,12 @@ pub enum MplCoreError { /// 47 (0x2F) - Cannot add a Data Section without a linked external plugin #[error("Cannot add a Data Section without a linked external plugin")] CannotAddDataSection, + /// 48 (0x30) - Plugin is not allowed to be added to an Asset + #[error("Plugin is not allowed to be added to an Asset")] + PluginNotAllowedOnAsset, + /// 49 (0x31) - Plugin is not allowed to be added to a Collection + #[error("Plugin is not allowed to be added to a Collection")] + PluginNotAllowedOnCollection, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/idls/mpl_core.json b/idls/mpl_core.json index 85960483..fd46057c 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -4852,6 +4852,16 @@ "code": 47, "name": "CannotAddDataSection", "msg": "Cannot add a Data Section without a linked external plugin" + }, + { + "code": 48, + "name": "PluginNotAllowedOnAsset", + "msg": "Plugin is not allowed to be added to an Asset" + }, + { + "code": 49, + "name": "PluginNotAllowedOnCollection", + "msg": "Plugin is not allowed to be added to a Collection" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 266684c2..43fa5d5d 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -200,6 +200,14 @@ pub enum MplCoreError { /// 47 - Cannot add a Data Section without a linked external plugin #[error("Cannot add a Data Section without a linked external plugin")] CannotAddDataSection, + + /// 48 - Plugin is not allowed to be added to an Asset + #[error("Plugin is not allowed to be added to an Asset")] + PluginNotAllowedOnAsset, + + /// 49 - Plugin is not allowed to be added to a Collection + #[error("Plugin is not allowed to be added to a Collection")] + PluginNotAllowedOnCollection, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 2f47c5ed..5758f741 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -135,6 +135,7 @@ impl PluginType { PluginType::UpdateDelegate => CheckResult::CanApprove, PluginType::Autograph => CheckResult::CanReject, PluginType::VerifiedCreators => CheckResult::CanReject, + PluginType::MasterEdition => CheckResult::CanReject, _ => CheckResult::None, } } @@ -809,6 +810,7 @@ impl From for ValidationResult { /// The required context for a plugin validation. #[allow(dead_code)] +#[derive(Debug)] pub(crate) struct PluginValidationContext<'a, 'b> { /// This list of all the accounts passed into the instruction. pub accounts: &'a [AccountInfo<'a>], @@ -828,7 +830,7 @@ pub(crate) struct PluginValidationContext<'a, 'b> { pub new_asset_authority: Option<&'b UpdateAuthority>, /// The new collection authority address. pub new_collection_authority: Option<&'b Pubkey>, - /// The plugin being acted upon with new data from the ix if any. This None for create. + /// The plugin being acted upon with new data from the ix if any. This is None for create. pub target_plugin: Option<&'b Plugin>, } @@ -968,7 +970,7 @@ pub(crate) fn validate_plugin_checks<'a>( new_owner: Option<&'a AccountInfo<'a>>, new_asset_authority: Option<&UpdateAuthority>, new_collection_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, + target_plugin: Option<&Plugin>, asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], @@ -1002,7 +1004,7 @@ pub(crate) fn validate_plugin_checks<'a>( new_owner, new_asset_authority, new_collection_authority, - target_plugin: new_plugin, + target_plugin, }; let result = plugin_validate_fp( @@ -1042,7 +1044,7 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( new_owner: Option<&'a AccountInfo<'a>>, new_asset_authority: Option<&UpdateAuthority>, new_collection_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, + target_plugin: Option<&Plugin>, asset: Option<&'a AccountInfo<'a>>, collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], @@ -1074,7 +1076,7 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( new_owner, new_asset_authority, new_collection_authority, - target_plugin: new_plugin, + target_plugin, }; let result = external_plugin_adapter_validate_fp( diff --git a/programs/mpl-core/src/plugins/master_edition.rs b/programs/mpl-core/src/plugins/master_edition.rs index 74735125..60ce8896 100644 --- a/programs/mpl-core/src/plugins/master_edition.rs +++ b/programs/mpl-core/src/plugins/master_edition.rs @@ -1,6 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; -use super::PluginValidation; +use crate::{error::MplCoreError, utils::load_key}; + +use super::{PluginType, PluginValidation, PluginValidationContext, ValidationResult}; /// The master edition plugin allows the creator to specify details on the master edition including max supply, name, and uri. /// The default authority for this plugin is the creator. @@ -15,4 +18,39 @@ pub struct MasterEdition { pub uri: Option, } -impl PluginValidation for MasterEdition {} +impl PluginValidation for MasterEdition { + fn validate_create( + &self, + ctx: &PluginValidationContext, + ) -> Result { + // Target plugin doesn't need to be populated for create, so we check if it exists, otherwise we pass. + if let Some(target_plugin) = ctx.target_plugin { + // You can't create the master edition plugin on an asset. + if PluginType::from(target_plugin) == PluginType::MasterEdition + && ctx.asset_info.is_some() + { + Err(MplCoreError::PluginNotAllowedOnAsset.into()) + } else { + Ok(ValidationResult::Pass) + } + } else { + Ok(ValidationResult::Pass) + } + } + + fn validate_add_plugin( + &self, + ctx: &PluginValidationContext, + ) -> Result { + // Target plugin must be populated for add_plugin. + let target_plugin = ctx.target_plugin.ok_or(MplCoreError::InvalidPlugin)?; + + // You can't add the master edition plugin to an asset. + if PluginType::from(target_plugin) == PluginType::MasterEdition && ctx.asset_info.is_some() + { + Err(MplCoreError::PluginNotAllowedOnAsset.into()) + } else { + Ok(ValidationResult::Pass) + } + } +} diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index 77c1626b..790ba313 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -47,12 +47,6 @@ pub(crate) fn add_plugin<'a>( return Err(MplCoreError::NotAvailable.into()); } - // TODO move into plugin validation when asset/collection is part of validation context - let plugin_type = PluginType::from(&args.plugin); - if plugin_type == PluginType::MasterEdition { - return Err(MplCoreError::InvalidPlugin.into()); - } - // TODO: Seed with Rejected // TODO: refactor to allow add_plugin to approve additions let validation_ctx = PluginValidationContext { diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 52130b9b..a40d4ff1 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -180,11 +180,6 @@ pub(crate) fn process_create<'a>( ctx.accounts.system_program, )?; for plugin in &plugins { - // TODO move into plugin validation when asset/collection is part of validation context - let plugin_type = PluginType::from(&plugin.plugin); - if plugin_type == PluginType::MasterEdition { - return Err(MplCoreError::InvalidPlugin.into()); - } if PluginType::check_create(&PluginType::from(&plugin.plugin)) != CheckResult::None { @@ -198,7 +193,7 @@ pub(crate) fn process_create<'a>( new_owner: None, new_asset_authority: None, new_collection_authority: None, - target_plugin: None, + target_plugin: Some(&plugin.plugin), }; match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { ValidationResult::Rejected => approved = false, diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index 33d9a732..4b886d8d 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -142,7 +142,7 @@ pub(crate) fn process_create_collection<'a>( new_owner: None, new_asset_authority: None, new_collection_authority: None, - target_plugin: None, + target_plugin: Some(&plugin.plugin), }; match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { ValidationResult::Rejected => approved = false, diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 340b6dea..915dc319 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -208,7 +208,7 @@ pub(crate) fn validate_asset_permissions<'a>( collection: Option<&'a AccountInfo<'a>>, new_owner: Option<&'a AccountInfo<'a>>, new_authority: Option<&UpdateAuthority>, - new_plugin: Option<&Plugin>, + target_plugin: Option<&Plugin>, new_external_plugin_adapter: Option<&ExternalPluginAdapter>, asset_check_fp: fn() -> CheckResult, collection_check_fp: fn() -> CheckResult, @@ -311,7 +311,7 @@ pub(crate) fn validate_asset_permissions<'a>( match asset_validate_fp( &deserialized_asset, authority_info, - new_plugin, + target_plugin, new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, @@ -327,7 +327,7 @@ pub(crate) fn validate_asset_permissions<'a>( match collection_validate_fp( &CollectionV1::load(collection.ok_or(MplCoreError::MissingCollection)?, 0)?, authority_info, - new_plugin, + target_plugin, new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, @@ -347,7 +347,7 @@ pub(crate) fn validate_asset_permissions<'a>( new_owner, new_authority, None, - new_plugin, + target_plugin, Some(asset), collection, &resolved_authorities, @@ -369,7 +369,7 @@ pub(crate) fn validate_asset_permissions<'a>( new_owner, new_authority, None, - new_plugin, + target_plugin, Some(asset), collection, &resolved_authorities, @@ -392,7 +392,7 @@ pub(crate) fn validate_asset_permissions<'a>( new_owner, new_authority, None, - new_plugin, + target_plugin, Some(asset), collection, &resolved_authorities, @@ -413,7 +413,7 @@ pub(crate) fn validate_asset_permissions<'a>( new_owner, new_authority, None, - new_plugin, + target_plugin, Some(asset), collection, &resolved_authorities, @@ -443,7 +443,7 @@ pub(crate) fn validate_collection_permissions<'a>( authority_info: &'a AccountInfo<'a>, collection: &'a AccountInfo<'a>, new_authority: Option<&Pubkey>, - new_plugin: Option<&Plugin>, + target_plugin: Option<&Plugin>, new_external_plugin_adapter: Option<&ExternalPluginAdapter>, collection_check_fp: fn() -> CheckResult, plugin_check_fp: fn(&PluginType) -> CheckResult, @@ -517,7 +517,7 @@ pub(crate) fn validate_collection_permissions<'a>( Key::CollectionV1 => collection_validate_fp( &deserialized_collection, authority_info, - new_plugin, + target_plugin, new_external_plugin_adapter, )?, _ => return Err(MplCoreError::IncorrectAccount.into()), @@ -540,7 +540,7 @@ pub(crate) fn validate_collection_permissions<'a>( None, None, new_authority, - new_plugin, + target_plugin, None, Some(collection), &resolved_authorities, @@ -563,7 +563,7 @@ pub(crate) fn validate_collection_permissions<'a>( None, None, new_authority, - new_plugin, + target_plugin, None, Some(collection), &resolved_authorities,