diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index 54364142..0ccb5459 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -433,6 +433,19 @@ export class InvalidLogWrapperProgramError extends ProgramError { codeToErrorMap.set(0x1e, InvalidLogWrapperProgramError); nameToErrorMap.set('InvalidLogWrapperProgram', InvalidLogWrapperProgramError); +/** ExternalPluginNotFound: External Plugin not found */ +export class ExternalPluginNotFoundError extends ProgramError { + override readonly name: string = 'ExternalPluginNotFound'; + + readonly code: number = 0x1f; // 31 + + constructor(program: Program, cause?: Error) { + super('External Plugin not found', program, cause); + } +} +codeToErrorMap.set(0x1f, ExternalPluginNotFoundError); +nameToErrorMap.set('ExternalPluginNotFound', ExternalPluginNotFoundError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index 12a8d9b9..ca432402 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -103,6 +103,9 @@ pub enum MplCoreError { /// 30 (0x1E) - Invalid Log Wrapper Program #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, + /// 31 (0x1F) - External Plugin not found + #[error("External Plugin not found")] + ExternalPluginNotFound, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index f16d20b0..12ba0d53 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -5,9 +5,10 @@ use std::{cmp::Ordering, io::ErrorKind}; use crate::{ accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1}, types::{ - Attributes, BurnDelegate, DataStore, Edition, ExternalCheckResult, FreezeDelegate, Key, - LifecycleHook, Oracle, PermanentBurnDelegate, PermanentFreezeDelegate, - PermanentTransferDelegate, PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, + Attributes, BurnDelegate, DataStore, Edition, ExternalCheckResult, ExternalPlugin, + ExternalPluginKey, FreezeDelegate, Key, LifecycleHook, Oracle, PermanentBurnDelegate, + PermanentFreezeDelegate, PermanentTransferDelegate, PluginAuthority, Royalties, + TransferDelegate, UpdateDelegate, }, }; @@ -250,3 +251,17 @@ impl PluginRegistryV1Safe { }) } } + +impl From<&ExternalPlugin> for ExternalPluginKey { + fn from(plugin: &ExternalPlugin) -> Self { + match plugin { + ExternalPlugin::DataStore(data_store) => { + ExternalPluginKey::DataStore(data_store.data_authority.clone()) + } + ExternalPlugin::Oracle(oracle) => ExternalPluginKey::Oracle(oracle.base_address), + ExternalPlugin::LifecycleHook(lifecycle_hook) => { + ExternalPluginKey::LifecycleHook(lifecycle_hook.hooked_program) + } + } + } +} diff --git a/clients/rust/tests/add_external_plugins.rs b/clients/rust/tests/add_external_plugins.rs index 3d2eb5a8..813c145d 100644 --- a/clients/rust/tests/add_external_plugins.rs +++ b/clients/rust/tests/add_external_plugins.rs @@ -3,10 +3,10 @@ pub mod setup; use mpl_core::{ instructions::AddExternalPluginV1Builder, types::{ - DataStoreInitInfo, ExternalCheckResult, ExternalPluginInitInfo, HookableLifecycleEvent, - LifecycleHookInitInfo, OracleInitInfo, PluginAuthority, UpdateAuthority, + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPlugin, ExternalPluginInitInfo, + ExternalPluginSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, + OracleInitInfo, PluginAuthority, UpdateAuthority, }, - Asset, }; pub use setup::*; @@ -49,6 +49,7 @@ async fn test_add_lifecycle_hook() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -80,15 +81,26 @@ async fn test_add_lifecycle_hook() { context.banks_client.process_transaction(tx).await.unwrap(); - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], + }, + ) + .await; } #[tokio::test] @@ -126,6 +138,7 @@ async fn test_add_oracle() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -154,15 +167,22 @@ async fn test_add_oracle() { context.banks_client.process_transaction(tx).await.unwrap(); - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::Oracle(Oracle { + base_address: Pubkey::default(), + pda: None, + })], + }, + ) + .await; } #[tokio::test] @@ -200,6 +220,7 @@ async fn test_add_data_store() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -223,13 +244,22 @@ async fn test_add_data_store() { context.banks_client.process_transaction(tx).await.unwrap(); - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], + }, + ) + .await; } diff --git a/clients/rust/tests/create.rs b/clients/rust/tests/create.rs index eeebc7da..83a7ca5b 100644 --- a/clients/rust/tests/create.rs +++ b/clients/rust/tests/create.rs @@ -43,6 +43,7 @@ async fn create_asset_in_account_state() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -85,6 +86,7 @@ async fn create_asset_with_different_payer() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -131,6 +133,7 @@ async fn create_asset_with_plugins() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: Some(PluginAuthority::Owner), }], + external_plugins: vec![], }, ) .await; @@ -174,6 +177,7 @@ async fn create_asset_with_different_update_authority() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -223,6 +227,7 @@ async fn create_asset_with_plugins_with_different_update_authority() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: Some(PluginAuthority::Owner), }], + external_plugins: vec![], }, ) .await; diff --git a/clients/rust/tests/create_with_external_plugins.rs b/clients/rust/tests/create_with_external_plugins.rs index e4fa6bed..4d24b353 100644 --- a/clients/rust/tests/create_with_external_plugins.rs +++ b/clients/rust/tests/create_with_external_plugins.rs @@ -1,11 +1,9 @@ #![cfg(feature = "test-sbf")] pub mod setup; -use mpl_core::{ - types::{ - DataStoreInitInfo, ExternalCheckResult, ExternalPluginInitInfo, HookableLifecycleEvent, - LifecycleHookInitInfo, OracleInitInfo, PluginAuthority, UpdateAuthority, - }, - Asset, +use mpl_core::types::{ + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPlugin, ExternalPluginInitInfo, + ExternalPluginSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, + OracleInitInfo, PluginAuthority, UpdateAuthority, }; pub use setup::*; @@ -14,7 +12,7 @@ use solana_program_test::tokio; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; #[tokio::test] -async fn test_add_lifecycle_hook() { +async fn test_create_lifecycle_hook() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -60,23 +58,21 @@ async fn test_add_lifecycle_hook() { name: None, uri: None, plugins: vec![], + external_plugins: vec![ExternalPlugin::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], }, ) .await; - - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); } #[tokio::test] -async fn test_add_oracle() { +async fn test_create_oracle() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -119,23 +115,17 @@ async fn test_add_oracle() { name: None, uri: None, plugins: vec![], + external_plugins: vec![ExternalPlugin::Oracle(Oracle { + base_address: Pubkey::default(), + pda: None, + })], }, ) .await; - - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); } #[tokio::test] -async fn test_add_data_store() { +async fn test_create_data_store() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -173,17 +163,13 @@ async fn test_add_data_store() { name: None, uri: None, plugins: vec![], + external_plugins: vec![ExternalPlugin::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], }, ) .await; - - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .unwrap() - .unwrap(); - - let asset_data = Asset::from_bytes(&asset_account.data).unwrap(); - println!("{:#?}", asset_data); } diff --git a/clients/rust/tests/plugins.rs b/clients/rust/tests/plugins.rs index b52d3df2..5c789ce2 100644 --- a/clients/rust/tests/plugins.rs +++ b/clients/rust/tests/plugins.rs @@ -84,6 +84,7 @@ async fn test_fetch_plugin() { }), }, ], + external_plugins: vec![], }, ) .await; @@ -193,6 +194,7 @@ async fn test_fetch_plugins() { }), }, ], + external_plugins: vec![], }, ) .await; @@ -303,6 +305,7 @@ async fn test_list_plugins() { }), }, ], + external_plugins: vec![], }, ) .await; diff --git a/clients/rust/tests/remove_external_plugins.rs b/clients/rust/tests/remove_external_plugins.rs new file mode 100644 index 00000000..ec696118 --- /dev/null +++ b/clients/rust/tests/remove_external_plugins.rs @@ -0,0 +1,268 @@ +#![cfg(feature = "test-sbf")] +pub mod setup; +use mpl_core::{ + instructions::RemoveExternalPluginV1Builder, + types::{ + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPlugin, ExternalPluginInitInfo, + ExternalPluginKey, ExternalPluginSchema, HookableLifecycleEvent, LifecycleHook, + LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, UpdateAuthority, + }, +}; +pub use setup::*; + +use solana_program::pubkey; +use solana_program_test::tokio; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; + +#[tokio::test] +async fn test_remove_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugins: vec![ExternalPluginInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: Some(vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )]), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginKey::LifecycleHook(pubkey!( + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + ))) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_remove_oracle() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugins: vec![ExternalPluginInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: Some(vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )]), + pda: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::Oracle(Oracle { + base_address: Pubkey::default(), + pda: None, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginKey::Oracle(Pubkey::default())) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_remove_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugins: vec![ExternalPluginInitInfo::DataStore(DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![ExternalPlugin::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginSchema::Binary, + data_offset: 119, + data_len: 0, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginKey::DataStore( + PluginAuthority::UpdateAuthority, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugins: vec![], + }, + ) + .await; +} diff --git a/clients/rust/tests/setup/mod.rs b/clients/rust/tests/setup/mod.rs index 451301ac..84a2455b 100644 --- a/clients/rust/tests/setup/mod.rs +++ b/clients/rust/tests/setup/mod.rs @@ -1,6 +1,9 @@ use mpl_core::{ instructions::{CreateCollectionV1Builder, CreateV2Builder}, - types::{DataState, ExternalPluginInitInfo, Key, Plugin, PluginAuthorityPair, UpdateAuthority}, + types::{ + DataState, ExternalPlugin, ExternalPluginInitInfo, Key, Plugin, PluginAuthorityPair, + UpdateAuthority, + }, Asset, Collection, }; use solana_program_test::{BanksClientError, ProgramTest, ProgramTestContext}; @@ -77,6 +80,7 @@ pub struct AssertAssetHelperArgs { pub uri: Option, // TODO use PluginList type here pub plugins: Vec, + pub external_plugins: Vec, } pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHelperArgs) { @@ -127,6 +131,26 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe _ => panic!("unsupported plugin type"), } } + + assert_eq!( + input.external_plugins.len(), + asset.external_plugin_list.lifecycle_hooks.len() + + asset.external_plugin_list.oracles.len() + + asset.external_plugin_list.data_stores.len() + ); + for plugin in input.external_plugins { + match plugin { + ExternalPlugin::LifecycleHook(hook) => { + assert!(asset.external_plugin_list.lifecycle_hooks.contains(&hook)) + } + ExternalPlugin::Oracle(oracle) => { + assert!(asset.external_plugin_list.oracles.contains(&oracle)) + } + ExternalPlugin::DataStore(data_store) => { + assert!(asset.external_plugin_list.data_stores.contains(&data_store)) + } + } + } } #[derive(Debug)] diff --git a/clients/rust/tests/transfer.rs b/clients/rust/tests/transfer.rs index 2cf6469d..ab51390a 100644 --- a/clients/rust/tests/transfer.rs +++ b/clients/rust/tests/transfer.rs @@ -59,6 +59,7 @@ async fn transfer_asset_as_owner() { name: None, uri: None, plugins: vec![], + external_plugins: vec![], }, ) .await; @@ -134,6 +135,7 @@ async fn transfer_asset_with_royalties() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugins: vec![], }, ) .await; diff --git a/idls/mpl_core.json b/idls/mpl_core.json index fb26b598..d3d4e54f 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -4107,6 +4107,11 @@ "code": 30, "name": "InvalidLogWrapperProgram", "msg": "Invalid Log Wrapper Program" + }, + { + "code": 31, + "name": "ExternalPluginNotFound", + "msg": "External Plugin not found" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 66c783a1..c5fae33f 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -132,6 +132,10 @@ pub enum MplCoreError { /// 30 - Invalid Log Wrapper Program #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, + + /// 31 - External Plugin not found + #[error("External Plugin not found")] + ExternalPluginNotFound, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/plugins/external_plugins.rs b/programs/mpl-core/src/plugins/external_plugins.rs index 8e684f9b..fcfa98ec 100644 --- a/programs/mpl-core/src/plugins/external_plugins.rs +++ b/programs/mpl-core/src/plugins/external_plugins.rs @@ -28,6 +28,16 @@ pub enum ExternalPluginType { DataStore, } +impl From<&ExternalPluginKey> for ExternalPluginType { + fn from(key: &ExternalPluginKey) -> Self { + match key { + ExternalPluginKey::LifecycleHook(_) => ExternalPluginType::LifecycleHook, + ExternalPluginKey::Oracle(_) => ExternalPluginType::Oracle, + ExternalPluginKey::DataStore(_) => ExternalPluginType::DataStore, + } + } +} + impl From<&ExternalPluginInitInfo> for ExternalPluginType { fn from(init_info: &ExternalPluginInitInfo) -> Self { match init_info { diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index dce51ac8..1f6c7b82 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -1,7 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::sol_memcpy, + program_memory::sol_memcpy, pubkey::Pubkey, }; use crate::{ @@ -11,8 +11,8 @@ use crate::{ }; use super::{ - ExternalPlugin, ExternalPluginInitInfo, ExternalRegistryRecord, Plugin, PluginHeaderV1, - PluginRegistryV1, PluginType, RegistryRecord, + ExternalPlugin, ExternalPluginInitInfo, ExternalPluginKey, ExternalPluginType, + ExternalRegistryRecord, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, RegistryRecord, }; /// Create plugin header and registry if it doesn't exist @@ -177,6 +177,42 @@ pub fn fetch_wrapped_plugin( Ok((registry_record.authority, plugin)) } +/// Fetch the external plugin from the registry. +pub fn fetch_wrapped_external_plugin( + account: &AccountInfo, + core: Option<&T>, + plugin_key: &ExternalPluginKey, +) -> Result<(Authority, ExternalPlugin), ProgramError> { + let size = match core { + Some(core) => core.get_size(), + None => { + let asset = T::load(account, 0)?; + + if asset.get_size() == account.data_len() { + return Err(MplCoreError::ExternalPluginNotFound.into()); + } + + asset.get_size() + } + }; + + let header = PluginHeaderV1::load(account, size)?; + let plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; + + // Find the plugin in the registry. + let result = find_external_plugin(&plugin_registry, plugin_key, account)?; + + if let (_, Some(record)) = result { + // Deserialize the plugin. + let plugin = ExternalPlugin::deserialize(&mut &(*account.data).borrow()[record.offset..])?; + + // Return the plugin and its authority. + Ok((record.authority, plugin)) + } else { + Err(MplCoreError::ExternalPluginNotFound.into()) + } +} + /// Fetch the plugin registry. pub fn fetch_plugins(account: &AccountInfo) -> Result, ProgramError> { let asset = AssetV1::load(account, 0)?; @@ -415,6 +451,12 @@ pub fn delete_plugin<'a, T: DataBlob>( } } + for record in &mut plugin_registry.external_registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + plugin_registry.save(account, new_registry_offset)?; resize_or_reallocate_account(account, payer, system_program, new_size)?; @@ -425,6 +467,90 @@ pub fn delete_plugin<'a, T: DataBlob>( Ok(()) } +/// Remove a plugin from the registry and delete it. +pub fn delete_external_plugin<'a, T: DataBlob>( + plugin_key: &ExternalPluginKey, + asset: &T, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + if asset.get_size() == account.data_len() { + return Err(MplCoreError::ExternalPluginNotFound.into()); + } + + //TODO: Bytemuck this. + let mut header = PluginHeaderV1::load(account, asset.get_size())?; + let mut plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; + + let result = find_external_plugin(&plugin_registry, plugin_key, account)?; + + if let (Some(index), _) = result { + let registry_record = plugin_registry.external_registry.remove(index); + let serialized_registry_record = registry_record.try_to_vec()?; + + // Fetch the offset of the plugin to be removed. + let plugin_offset = registry_record.offset; + let plugin = ExternalPlugin::load(account, plugin_offset)?; + let serialized_plugin = plugin.try_to_vec()?; + + // Get the offset of the plugin after the one being removed. + let next_plugin_offset = plugin_offset + .checked_add(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + // Calculate the new size of the account. + let new_size = account + .data_len() + .checked_sub(serialized_registry_record.len()) + .ok_or(MplCoreError::NumericalOverflow)? + .checked_sub(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + let new_registry_offset = header + .plugin_registry_offset + .checked_sub(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + let data_to_move = header + .plugin_registry_offset + .checked_sub(next_plugin_offset) + .ok_or(MplCoreError::NumericalOverflow)?; + + //TODO: This is memory intensive, we should use memmove instead probably. + let src = account.data.borrow()[next_plugin_offset..].to_vec(); + sol_memcpy( + &mut account.data.borrow_mut()[plugin_offset..], + &src, + data_to_move, + ); + + header.plugin_registry_offset = new_registry_offset; + header.save(account, asset.get_size())?; + + // Move offsets for existing registry records. + for record in &mut plugin_registry.external_registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + + for record in &mut plugin_registry.registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + + plugin_registry.save(account, new_registry_offset)?; + + resize_or_reallocate_account(account, payer, system_program, new_size)?; + } else { + return Err(MplCoreError::ExternalPluginNotFound.into()); + } + + Ok(()) +} + /// Add an authority to a plugin. #[allow(clippy::too_many_arguments)] pub fn approve_authority_on_plugin<'a, T: CoreAsset>( @@ -490,3 +616,47 @@ pub fn revoke_authority_on_plugin<'a>( Ok(()) } + +fn find_external_plugin<'b>( + plugin_registry: &'b PluginRegistryV1, + plugin_key: &ExternalPluginKey, + account: &AccountInfo<'_>, +) -> Result<(Option, Option<&'b ExternalRegistryRecord>), ProgramError> { + let mut result = (None, None); + for (i, record) in plugin_registry.external_registry.iter().enumerate() { + if record.plugin_type == ExternalPluginType::from(plugin_key) + && (match plugin_key { + ExternalPluginKey::LifecycleHook(address) | ExternalPluginKey::Oracle(address) => { + let pubkey_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + address + == &match Pubkey::deserialize(&mut &account.data.borrow()[pubkey_offset..]) + { + Ok(address) => address, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } + ExternalPluginKey::DataStore(authority) => { + let authority_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + authority + == &match Authority::deserialize( + &mut &account.data.borrow()[authority_offset..], + ) { + Ok(authority) => authority, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } + }) + { + result = (Some(i), Some(record)); + break; + } + } + + Ok(result) +} diff --git a/programs/mpl-core/src/processor/add_external_plugin.rs b/programs/mpl-core/src/processor/add_external_plugin.rs index 292cf8f1..626d7ee3 100644 --- a/programs/mpl-core/src/processor/add_external_plugin.rs +++ b/programs/mpl-core/src/processor/add_external_plugin.rs @@ -63,6 +63,8 @@ pub(crate) fn add_external_plugin<'a>( return Err(MplCoreError::InvalidAuthority.into()); } + let external_plugin = ExternalPlugin::from(&args.init_info); + // Validate asset permissions. let (mut asset, _, _) = validate_asset_permissions( authority, @@ -70,7 +72,7 @@ pub(crate) fn add_external_plugin<'a>( ctx.accounts.collection, None, None, - Some(&args.init_info), + Some(&external_plugin), AssetV1::check_add_external_plugin, CollectionV1::check_add_external_plugin, PluginType::check_add_external_plugin, @@ -134,12 +136,14 @@ pub(crate) fn add_collection_external_plugin<'a>( return Err(MplCoreError::InvalidAuthority.into()); } + let external_plugin = ExternalPlugin::from(&args.init_info); + // Validate collection permissions. let _ = validate_collection_permissions( authority, ctx.accounts.collection, None, - Some(&args.init_info), + Some(&external_plugin), CollectionV1::check_add_external_plugin, PluginType::check_add_external_plugin, CollectionV1::validate_add_external_plugin, diff --git a/programs/mpl-core/src/processor/remove_external_plugin.rs b/programs/mpl-core/src/processor/remove_external_plugin.rs index d2cc23f3..e5c58cbd 100644 --- a/programs/mpl-core/src/processor/remove_external_plugin.rs +++ b/programs/mpl-core/src/processor/remove_external_plugin.rs @@ -1,7 +1,22 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; +use mpl_utils::assert_signer; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; -use crate::{error::MplCoreError, plugins::ExternalPluginKey}; +use crate::{ + error::MplCoreError, + instruction::accounts::{ + RemoveCollectionExternalPluginV1Accounts, RemoveExternalPluginV1Accounts, + }, + plugins::{ + delete_external_plugin, fetch_wrapped_external_plugin, ExternalPluginKey, Plugin, + PluginType, + }, + state::{AssetV1, CollectionV1, DataBlob, Key}, + utils::{ + fetch_core_data, load_key, resolve_authority, validate_asset_permissions, + validate_collection_permissions, + }, +}; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] @@ -11,10 +26,63 @@ pub(crate) struct RemoveExternalPluginV1Args { } pub(crate) fn remove_external_plugin<'a>( - _accounts: &'a [AccountInfo<'a>], - _args: RemoveExternalPluginV1Args, + accounts: &'a [AccountInfo<'a>], + args: RemoveExternalPluginV1Args, ) -> ProgramResult { - Err(MplCoreError::NotAvailable.into()) + let ctx = RemoveExternalPluginV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + if let Key::HashedAssetV1 = load_key(ctx.accounts.asset, 0)? { + msg!("Error: Remove plugin for compressed is not available"); + return Err(MplCoreError::NotAvailable.into()); + } + + let (asset, plugin_header, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; + + // We don't have anything to delete if there's no plugin meta. + if plugin_header.is_none() || plugin_registry.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + let (_, plugin_to_remove) = + fetch_wrapped_external_plugin::(ctx.accounts.asset, Some(&asset), &args.key)?; + + // Validate asset permissions. + let _ = validate_asset_permissions( + authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + None, + Some(&plugin_to_remove), + AssetV1::check_remove_external_plugin, + CollectionV1::check_remove_external_plugin, + PluginType::check_remove_external_plugin, + AssetV1::validate_remove_external_plugin, + CollectionV1::validate_remove_external_plugin, + Plugin::validate_remove_external_plugin, + )?; + + process_remove_external_plugin( + &args.key, + &asset, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + ) } #[repr(C)] @@ -25,8 +93,66 @@ pub(crate) struct RemoveCollectionExternalPluginV1Args { } pub(crate) fn remove_collection_external_plugin<'a>( - _accounts: &'a [AccountInfo<'a>], - _args: RemoveCollectionExternalPluginV1Args, + accounts: &'a [AccountInfo<'a>], + args: RemoveCollectionExternalPluginV1Args, +) -> ProgramResult { + let ctx = RemoveCollectionExternalPluginV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + let (collection, plugin_header, plugin_registry) = + fetch_core_data::(ctx.accounts.collection)?; + + // We don't have anything to delete if there's no plugin meta. + if plugin_header.is_none() || plugin_registry.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + let (_, plugin_to_remove) = fetch_wrapped_external_plugin::( + ctx.accounts.collection, + Some(&collection), + &args.key, + )?; + + // Validate asset permissions. + let _ = validate_collection_permissions( + authority, + ctx.accounts.collection, + None, + Some(&plugin_to_remove), + CollectionV1::check_remove_external_plugin, + PluginType::check_remove_external_plugin, + CollectionV1::validate_remove_external_plugin, + Plugin::validate_remove_external_plugin, + )?; + + process_remove_external_plugin( + &args.key, + &collection, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +fn process_remove_external_plugin<'a, T: DataBlob>( + plugin_key: &ExternalPluginKey, + core: &T, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, ) -> ProgramResult { - Err(MplCoreError::NotAvailable.into()) + delete_external_plugin(plugin_key, core, account, payer, system_program) } diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index 8d9a07ee..d0aa1a9f 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -8,7 +8,7 @@ use std::mem::size_of; use crate::{ error::MplCoreError, - plugins::{CheckResult, ExternalPluginInitInfo, Plugin, ValidationResult}, + plugins::{CheckResult, ExternalPlugin, Plugin, ValidationResult}, state::{Compressible, CompressionProof, DataBlob, Key, SolanaAccount}, }; @@ -133,7 +133,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, new_plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let new_plugin = match new_plugin { Some(plugin) => plugin, @@ -158,7 +158,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin_to_remove: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let plugin = match plugin_to_remove { Some(plugin) => plugin, @@ -181,7 +181,7 @@ impl AssetV1 { &self, _authority_info: &AccountInfo, _plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -191,7 +191,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if let Some(plugin) = plugin { if (plugin.manager() == Authority::UpdateAuthority @@ -213,7 +213,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if let Some(plugin) = plugin { if (plugin.manager() == Authority::UpdateAuthority @@ -235,7 +235,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.update_authority.key() { solana_program::msg!("Asset: Approved"); @@ -250,7 +250,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -265,7 +265,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -280,7 +280,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -295,7 +295,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -310,7 +310,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _new_plugin: Option<&ExternalPluginInitInfo>, + _new_plugin: Option<&ExternalPlugin>, ) -> Result { // If it's not in a collection, then it can be added. if UpdateAuthority::Address(*authority_info.key) == self.update_authority { @@ -326,7 +326,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _plugin_to_remove: Option<&ExternalPluginInitInfo>, + _plugin_to_remove: Option<&ExternalPlugin>, ) -> Result { if self.update_authority == UpdateAuthority::Address(*authority_info.key) { solana_program::msg!("Asset: Approved"); @@ -341,7 +341,7 @@ impl AssetV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _plugin: Option<&ExternalPluginInitInfo>, + _plugin: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index 8723c2bb..d882a9fd 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -4,7 +4,7 @@ use solana_program::{account_info::AccountInfo, program_error::ProgramError, pub use crate::{ error::MplCoreError, - plugins::{CheckResult, ExternalPluginInitInfo, Plugin, ValidationResult}, + plugins::{CheckResult, ExternalPlugin, Plugin, ValidationResult}, }; use super::{Authority, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority}; @@ -118,7 +118,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, new_plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let new_plugin = match new_plugin { Some(plugin) => plugin, @@ -140,7 +140,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin_to_remove: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let plugin_to_remove = match plugin_to_remove { Some(plugin) => plugin, @@ -162,7 +162,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -172,7 +172,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let plugin = match plugin { Some(plugin) => plugin, @@ -194,7 +194,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { let plugin = match plugin { Some(plugin) => plugin, @@ -216,7 +216,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -226,7 +226,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -236,7 +236,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { if authority_info.key == &self.update_authority { solana_program::msg!("Collection: Approved"); @@ -251,7 +251,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -261,7 +261,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _: Option<&ExternalPluginInitInfo>, + _: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } @@ -271,7 +271,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _new_plugin: Option<&ExternalPluginInitInfo>, + _new_plugin: Option<&ExternalPlugin>, ) -> Result { // Approve if the update authority matches the authority. if *authority_info.key == self.update_authority { @@ -287,7 +287,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, - _plugin_to_remove: Option<&ExternalPluginInitInfo>, + _plugin_to_remove: Option<&ExternalPlugin>, ) -> Result { if self.update_authority == *authority_info.key { solana_program::msg!("Asset: Approved"); @@ -302,7 +302,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, - _plugin: Option<&ExternalPluginInitInfo>, + _plugin: Option<&ExternalPlugin>, ) -> Result { Ok(ValidationResult::Pass) } diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 65c47a97..256391d8 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -13,8 +13,8 @@ use crate::{ error::MplCoreError, plugins::{ create_meta_idempotent, initialize_plugin, validate_plugin_checks, CheckResult, - ExternalPlugin, ExternalPluginInitInfo, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, PluginValidationContext, RegistryRecord, ValidationResult, + ExternalPlugin, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, + PluginValidationContext, RegistryRecord, ValidationResult, }, state::{ AssetV1, Authority, CollectionV1, Compressible, CompressionProof, CoreAsset, DataBlob, @@ -200,7 +200,7 @@ pub(crate) fn validate_asset_permissions<'a>( collection: Option<&AccountInfo<'a>>, new_owner: Option<&'a AccountInfo<'a>>, new_plugin: Option<&Plugin>, - new_external_plugin: Option<&ExternalPluginInitInfo>, + new_external_plugin: Option<&ExternalPlugin>, asset_check_fp: fn() -> CheckResult, collection_check_fp: fn() -> CheckResult, plugin_check_fp: fn(&PluginType) -> CheckResult, @@ -208,13 +208,13 @@ pub(crate) fn validate_asset_permissions<'a>( &AssetV1, &AccountInfo, Option<&Plugin>, - Option<&ExternalPluginInitInfo>, + Option<&ExternalPlugin>, ) -> Result, collection_validate_fp: fn( &CollectionV1, &AccountInfo, Option<&Plugin>, - Option<&ExternalPluginInitInfo>, + Option<&ExternalPlugin>, ) -> Result, plugin_validate_fp: fn( &Plugin, @@ -353,14 +353,14 @@ pub(crate) fn validate_collection_permissions<'a>( authority_info: &'a AccountInfo<'a>, collection: &AccountInfo<'a>, new_plugin: Option<&Plugin>, - new_external_plugin: Option<&ExternalPluginInitInfo>, + new_external_plugin: Option<&ExternalPlugin>, collection_check_fp: fn() -> CheckResult, plugin_check_fp: fn(&PluginType) -> CheckResult, collection_validate_fp: fn( &CollectionV1, &AccountInfo, Option<&Plugin>, - Option<&ExternalPluginInitInfo>, + Option<&ExternalPlugin>, ) -> Result, plugin_validate_fp: fn( &Plugin,