From a13b32f888d143ae996b7ae64ac3a8047523b2a5 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:26:38 -0700 Subject: [PATCH 1/4] Add external plugin data to Asset and Collection in Rust client --- clients/rust/src/hooked/advanced_types.rs | 30 ++++++++- clients/rust/src/hooked/mod.rs | 30 +++++++++ clients/rust/src/hooked/plugin.rs | 80 +++++++++++++++-------- clients/rust/tests/setup/mod.rs | 9 ++- 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index c9cbb97b..86307742 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -181,12 +181,36 @@ pub struct PluginsList { #[derive(Debug, Default)] pub struct ExternalPluginAdaptersList { - pub lifecycle_hooks: Vec, + pub lifecycle_hooks: Vec, pub linked_lifecycle_hooks: Vec, pub oracles: Vec, - pub app_data: Vec, + pub app_data: Vec, pub linked_app_data: Vec, - pub data_sections: Vec, + pub data_sections: Vec, +} + +#[derive(Debug)] +pub struct LifecycleHookWithData { + pub base: LifecycleHook, + pub data_string: String, + pub data_offset: usize, + pub data_len: usize, +} + +#[derive(Debug)] +pub struct AppDataWithData { + pub base: AppData, + pub data_string: String, + pub data_offset: usize, + pub data_len: usize, +} + +#[derive(Debug)] +pub struct DataSectionWithData { + pub base: DataSection, + pub data_string: String, + pub data_offset: usize, + pub data_len: usize, } #[derive(Debug)] diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index 481dbbc4..9676722b 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -256,6 +256,36 @@ impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType { } } +// Create a data slice of the account based on the offset and length passed in. +pub(crate) fn slice_data<'a>( + data_offset: Option, + data_len: Option, + account_data: &'a [u8], +) -> Result<(&'a [u8], usize, usize), std::io::Error> { + let data_offset = data_offset.ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::InvalidPlugin.to_string(), + ))?; + + let data_len = data_len.ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::InvalidPlugin.to_string(), + ))?; + + let end = data_offset + .checked_add(data_len) + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::NumericalOverflow.to_string(), + ))?; + + Ok(( + &account_data[data_offset as usize..end as usize], + data_offset as usize, + data_len as usize, + )) +} + /// Use `ExternalPluginAdapterSchema` to convert data to string. If schema is binary or there is /// an error, then use Base64 encoding. pub fn convert_external_plugin_adapter_data_to_string( diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index bef6e028..ee75b0b9 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -9,13 +9,15 @@ use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, convert_external_plugin_adapter_data_to_string, errors::MplCoreError, + slice_data, types::{ ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalPluginAdapterType, LinkedDataKey, Plugin, PluginAuthority, PluginType, RegistryRecord, }, - AddBlockerPlugin, AttributesPlugin, AutographPlugin, BaseAuthority, BasePlugin, - BurnDelegatePlugin, DataBlob, EditionPlugin, ExternalPluginAdaptersList, - ExternalRegistryRecordSafe, FreezeDelegatePlugin, ImmutableMetadataPlugin, MasterEditionPlugin, + AddBlockerPlugin, AppDataWithData, AttributesPlugin, AutographPlugin, BaseAuthority, + BasePlugin, BurnDelegatePlugin, DataBlob, DataSectionWithData, EditionPlugin, + ExternalPluginAdaptersList, ExternalRegistryRecordSafe, FreezeDelegatePlugin, + ImmutableMetadataPlugin, LifecycleHookWithData, MasterEditionPlugin, PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe, PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, UpdateDelegatePlugin, VerifiedCreatorsPlugin, @@ -172,28 +174,16 @@ pub fn fetch_external_plugin_adapter_data( } }; - let data_offset = registry_record.data_offset.ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::InvalidPlugin.to_string(), - ))?; - - let data_len = registry_record.data_len.ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::InvalidPlugin.to_string(), - ))?; - - let end = data_offset - .checked_add(data_len) - .ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::NumericalOverflow.to_string(), - ))?; - - let data_slice = &(*account.data).borrow()[data_offset as usize..end as usize]; + let account_data = &(*account.data).borrow()[..]; + let (data_slice, data_offset, data_len) = slice_data( + registry_record.data_offset, + registry_record.data_len, + account_data, + )?; let data_string = convert_external_plugin_adapter_data_to_string(&schema, data_slice); - // Return the data and its offset. - Ok((data_string, data_offset as usize, data_len as usize)) + // Return the data, its offset, and length. + Ok((data_string, data_offset, data_len)) } // Internal helper to fetch just the external registry record for the external plugin key. @@ -365,18 +355,56 @@ pub(crate) fn registry_records_to_external_plugin_adapter_list( match plugin { ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { - acc.lifecycle_hooks.push(lifecycle_hook) + let (data_slice, data_offset, data_len) = + slice_data(record.data_offset, record.data_len, account_data)?; + let data_string = convert_external_plugin_adapter_data_to_string( + &lifecycle_hook.schema, + data_slice, + ); + + acc.lifecycle_hooks.push(LifecycleHookWithData { + base: lifecycle_hook, + data_string, + data_offset, + data_len, + }) } ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { acc.linked_lifecycle_hooks.push(lifecycle_hook) } ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle), - ExternalPluginAdapter::AppData(app_data) => acc.app_data.push(app_data), + ExternalPluginAdapter::AppData(app_data) => { + let (data_slice, data_offset, data_len) = + slice_data(record.data_offset, record.data_len, account_data)?; + let data_string = convert_external_plugin_adapter_data_to_string( + &app_data.schema, + data_slice, + ); + + acc.app_data.push(AppDataWithData { + base: app_data, + data_string, + data_offset, + data_len, + }) + } ExternalPluginAdapter::LinkedAppData(app_data) => { acc.linked_app_data.push(app_data) } ExternalPluginAdapter::DataSection(data_section) => { - acc.data_sections.push(data_section) + let (data_slice, data_offset, data_len) = + slice_data(record.data_offset, record.data_len, account_data)?; + let data_string = convert_external_plugin_adapter_data_to_string( + &data_section.schema, + data_slice, + ); + + acc.data_sections.push(DataSectionWithData { + base: data_section, + data_string, + data_offset, + data_len, + }) } } } diff --git a/clients/rust/tests/setup/mod.rs b/clients/rust/tests/setup/mod.rs index d25fe63b..0c215f1b 100644 --- a/clients/rust/tests/setup/mod.rs +++ b/clients/rust/tests/setup/mod.rs @@ -144,7 +144,8 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe assert!(asset .external_plugin_adapter_list .lifecycle_hooks - .contains(&hook)) + .iter() + .any(|lifecyle_hook_with_data| lifecyle_hook_with_data.base == hook)) } ExternalPluginAdapter::Oracle(oracle) => { assert!(asset.external_plugin_adapter_list.oracles.contains(&oracle)) @@ -153,7 +154,8 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe assert!(asset .external_plugin_adapter_list .app_data - .contains(&app_data)) + .iter() + .any(|app_data_with_data| app_data_with_data.base == app_data)) } ExternalPluginAdapter::LinkedLifecycleHook(hook) => { assert!(asset @@ -171,7 +173,8 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe assert!(asset .external_plugin_adapter_list .data_sections - .contains(&data)) + .iter() + .any(|data_sections_with_data| data_sections_with_data.base == data)) } } } From 43384be724a28a7f711c50098dea4f4c463df284 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:49:20 -0700 Subject: [PATCH 2/4] Add method that only returns data offset and len, remove data_string from Asset and Collection --- clients/rust/src/hooked/advanced_types.rs | 3 - clients/rust/src/hooked/mod.rs | 30 --- clients/rust/src/hooked/plugin.rs | 82 +++++--- clients/rust/tests/add_external_plugins.rs | 1 + clients/rust/tests/create_collection.rs | 5 + .../tests/create_with_external_plugins.rs | 189 +++++++++++++++++- clients/rust/tests/setup/mod.rs | 54 ++++- 7 files changed, 292 insertions(+), 72 deletions(-) diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 86307742..5dc81285 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -192,7 +192,6 @@ pub struct ExternalPluginAdaptersList { #[derive(Debug)] pub struct LifecycleHookWithData { pub base: LifecycleHook, - pub data_string: String, pub data_offset: usize, pub data_len: usize, } @@ -200,7 +199,6 @@ pub struct LifecycleHookWithData { #[derive(Debug)] pub struct AppDataWithData { pub base: AppData, - pub data_string: String, pub data_offset: usize, pub data_len: usize, } @@ -208,7 +206,6 @@ pub struct AppDataWithData { #[derive(Debug)] pub struct DataSectionWithData { pub base: DataSection, - pub data_string: String, pub data_offset: usize, pub data_len: usize, } diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index 9676722b..481dbbc4 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -256,36 +256,6 @@ impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType { } } -// Create a data slice of the account based on the offset and length passed in. -pub(crate) fn slice_data<'a>( - data_offset: Option, - data_len: Option, - account_data: &'a [u8], -) -> Result<(&'a [u8], usize, usize), std::io::Error> { - let data_offset = data_offset.ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::InvalidPlugin.to_string(), - ))?; - - let data_len = data_len.ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::InvalidPlugin.to_string(), - ))?; - - let end = data_offset - .checked_add(data_len) - .ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::NumericalOverflow.to_string(), - ))?; - - Ok(( - &account_data[data_offset as usize..end as usize], - data_offset as usize, - data_len as usize, - )) -} - /// Use `ExternalPluginAdapterSchema` to convert data to string. If schema is binary or there is /// an error, then use Base64 encoding. pub fn convert_external_plugin_adapter_data_to_string( diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index ee75b0b9..3bc99bce 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -9,7 +9,6 @@ use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, convert_external_plugin_adapter_data_to_string, errors::MplCoreError, - slice_data, types::{ ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalPluginAdapterType, LinkedDataKey, Plugin, PluginAuthority, PluginType, RegistryRecord, @@ -153,8 +152,27 @@ pub fn fetch_wrapped_external_plugin_adapter( Ok((registry_record, plugin)) } -/// Fetch the external_plugin_adapter data from the registry. -pub fn fetch_external_plugin_adapter_data( +// Helper to unwrap optional data offset and data length. +fn unwrap_data_offset_and_data_len( + data_offset: Option, + data_len: Option, +) -> Result<(usize, usize), std::io::Error> { + let data_offset = data_offset.ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::InvalidPlugin.to_string(), + ))?; + + let data_len = data_len.ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::InvalidPlugin.to_string(), + ))?; + + Ok((data_offset as usize, data_len as usize)) +} + +/// Fetch the external_plugin_adapter data from the registry and convert to string. +/// May not be suitable for large amounts of data. +pub fn fetch_external_plugin_adapter_data_as_string( account: &AccountInfo, core: Option<&T>, plugin_key: &ExternalPluginAdapterKey, @@ -174,18 +192,37 @@ pub fn fetch_external_plugin_adapter_data( } }; - let account_data = &(*account.data).borrow()[..]; - let (data_slice, data_offset, data_len) = slice_data( - registry_record.data_offset, - registry_record.data_len, - account_data, - )?; + let (data_offset, data_len) = + unwrap_data_offset_and_data_len(registry_record.data_offset, registry_record.data_len)?; + let end = data_offset + .checked_add(data_len) + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + MplCoreError::NumericalOverflow.to_string(), + ))?; + + let data_slice = &(*account.data).borrow()[data_offset..end]; let data_string = convert_external_plugin_adapter_data_to_string(&schema, data_slice); // Return the data, its offset, and length. Ok((data_string, data_offset, data_len)) } +/// Fetch the external_plugin_adapter data offset and length. This can then be used to +/// directly slice the account data for use of the external plugin adapter data elsewhere. +pub fn fetch_external_plugin_adapter_data_info( + account: &AccountInfo, + core: Option<&T>, + plugin_key: &ExternalPluginAdapterKey, +) -> Result<(usize, usize), std::io::Error> { + let registry_record = fetch_external_registry_record(account, core, plugin_key)?; + let (data_offset, data_len) = + unwrap_data_offset_and_data_len(registry_record.data_offset, registry_record.data_len)?; + + // Return the data offset and length. + Ok((data_offset, data_len)) +} + // Internal helper to fetch just the external registry record for the external plugin key. fn fetch_external_registry_record( account: &AccountInfo, @@ -355,16 +392,11 @@ pub(crate) fn registry_records_to_external_plugin_adapter_list( match plugin { ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { - let (data_slice, data_offset, data_len) = - slice_data(record.data_offset, record.data_len, account_data)?; - let data_string = convert_external_plugin_adapter_data_to_string( - &lifecycle_hook.schema, - data_slice, - ); + let (data_offset, data_len) = + unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?; acc.lifecycle_hooks.push(LifecycleHookWithData { base: lifecycle_hook, - data_string, data_offset, data_len, }) @@ -374,16 +406,11 @@ pub(crate) fn registry_records_to_external_plugin_adapter_list( } ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle), ExternalPluginAdapter::AppData(app_data) => { - let (data_slice, data_offset, data_len) = - slice_data(record.data_offset, record.data_len, account_data)?; - let data_string = convert_external_plugin_adapter_data_to_string( - &app_data.schema, - data_slice, - ); + let (data_offset, data_len) = + unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?; acc.app_data.push(AppDataWithData { base: app_data, - data_string, data_offset, data_len, }) @@ -392,16 +419,11 @@ pub(crate) fn registry_records_to_external_plugin_adapter_list( acc.linked_app_data.push(app_data) } ExternalPluginAdapter::DataSection(data_section) => { - let (data_slice, data_offset, data_len) = - slice_data(record.data_offset, record.data_len, account_data)?; - let data_string = convert_external_plugin_adapter_data_to_string( - &data_section.schema, - data_slice, - ); + let (data_offset, data_len) = + unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?; acc.data_sections.push(DataSectionWithData { base: data_section, - data_string, data_offset, data_len, }) diff --git a/clients/rust/tests/add_external_plugins.rs b/clients/rust/tests/add_external_plugins.rs index 747decbf..485388e1 100644 --- a/clients/rust/tests/add_external_plugins.rs +++ b/clients/rust/tests/add_external_plugins.rs @@ -320,6 +320,7 @@ async fn test_temporarily_cannot_add_lifecycle_hook_on_collection() { num_minted: 0, current_size: 0, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; diff --git a/clients/rust/tests/create_collection.rs b/clients/rust/tests/create_collection.rs index 84ff4ddf..7ddc728a 100644 --- a/clients/rust/tests/create_collection.rs +++ b/clients/rust/tests/create_collection.rs @@ -37,6 +37,7 @@ async fn test_create_collection() { num_minted: 0, current_size: 0, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -76,6 +77,7 @@ async fn create_collection_with_different_payer() { num_minted: 0, current_size: 0, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -132,6 +134,7 @@ async fn create_collection_with_plugins() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await; @@ -172,6 +175,7 @@ async fn create_collection_with_different_update_authority() { num_minted: 0, current_size: 0, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -233,6 +237,7 @@ async fn create_collection_with_plugins_with_different_plugin_authority() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await; diff --git a/clients/rust/tests/create_with_external_plugins.rs b/clients/rust/tests/create_with_external_plugins.rs index 9154d70d..d1494c75 100644 --- a/clients/rust/tests/create_with_external_plugins.rs +++ b/clients/rust/tests/create_with_external_plugins.rs @@ -4,16 +4,20 @@ use serde_json::json; use std::borrow::BorrowMut; use mpl_core::{ - accounts::BaseAssetV1, + accounts::{BaseAssetV1, BaseCollectionV1}, errors::MplCoreError, - fetch_external_plugin_adapter, fetch_external_plugin_adapter_data, - instructions::WriteExternalPluginAdapterDataV1Builder, + fetch_external_plugin_adapter, fetch_external_plugin_adapter_data_as_string, + fetch_external_plugin_adapter_data_info, + instructions::{ + WriteCollectionExternalPluginAdapterDataV1Builder, WriteExternalPluginAdapterDataV1Builder, + }, types::{ AppData, AppDataInitInfo, ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, ExternalPluginAdapterSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, }, + Asset, Collection, }; pub use setup::*; @@ -455,19 +459,188 @@ async fn test_create_and_fetch_app_data() { println!("Auth: {:#?}", auth); println!("Offset: {:#?}", offset); - // Fetch the actual app data. - let (data_string, data_offset, data_len) = fetch_external_plugin_adapter_data::( + // Fetch the actual app data. Validate multiple methods. + + // First, get app data data offset and length. + let (data_offset_1, data_len_1) = fetch_external_plugin_adapter_data_info::( &account_info, None, &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), ) .unwrap(); + // Second, get app data as a String. + let (data_string, data_offset_2, data_len_2) = + fetch_external_plugin_adapter_data_as_string::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + // Validate app data. assert_eq!(data_string, test_json_str); - assert_eq!(data_len, 36); + assert_eq!(data_len_1, 36); + + // Validate data matches. + assert_eq!(data_offset_1, data_offset_2); + assert_eq!(data_len_1, data_len_2); + + // Third, get app data offset and length from a full `Asset` deserialization. + let asset = Asset::from_bytes(&account.data).unwrap(); + let app_data_with_data = asset.external_plugin_adapter_list.app_data.first().unwrap(); + + // Validate data matches. + assert_eq!(data_offset_1, app_data_with_data.data_offset); + assert_eq!(data_len_1, app_data_with_data.data_len); + + println!("Data string: {:#?}", data_string); + println!("Data offset: {:#?}", data_offset_1); + println!("Data len: {:#?}", data_len_1); +} + +#[tokio::test] +async fn test_collection_create_and_fetch_app_data() { + let mut context = program_test().start_with_context().await; + + let collection = Keypair::new(); + create_collection( + &mut context, + CreateCollectionHelperArgs { + collection: &collection, + update_authority: None, + payer: None, + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::AppData( + AppDataInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: Some(ExternalPluginAdapterSchema::Json), + }, + )], + }, + ) + .await + .unwrap(); + + let update_authority = context.payer.pubkey(); + assert_collection( + &mut context, + AssertCollectionHelperArgs { + collection: collection.pubkey(), + update_authority, + name: None, + uri: None, + num_minted: 0, + current_size: 0, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Json, + })], + }, + ) + .await; + + // Test JSON. + let test_json_obj = json!({ + "message": "Hello", + "target": "world" + }); + let test_json_str = serde_json::to_string(&test_json_obj).unwrap(); + let test_json_vec = test_json_str.as_bytes().to_vec(); + + // Write data. + let ix = WriteCollectionExternalPluginAdapterDataV1Builder::new() + .collection(collection.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::AppData( + PluginAuthority::UpdateAuthority, + )) + .data(test_json_vec) + .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(); + + // Get account. + let mut account = context + .banks_client + .get_account(collection.pubkey()) + .await + .unwrap() + .unwrap(); + + let binding = collection.pubkey(); + let account_info = AccountInfo::new( + &binding, + false, + false, + &mut account.lamports, + account.data.borrow_mut(), + &account.owner, + false, + 0, + ); + let (auth, app_data, offset) = fetch_external_plugin_adapter::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + + println!("App data: {:#?}", app_data); + println!("Auth: {:#?}", auth); + println!("Offset: {:#?}", offset); + + // Fetch the actual app data. Validate multiple methods. + + // First, get app data data offset and length. + let (data_offset_1, data_len_1) = fetch_external_plugin_adapter_data_info::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + + // Second, get app data as a String. + let (data_string, data_offset_2, data_len_2) = + fetch_external_plugin_adapter_data_as_string::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + + // Validate app data. + assert_eq!(data_string, test_json_str); + assert_eq!(data_len_1, 36); + + // Validate data matches. + assert_eq!(data_offset_1, data_offset_2); + assert_eq!(data_len_1, data_len_2); + + // Third, get app data offset and length from a full `Asset` deserialization. + let collection = Collection::from_bytes(&account.data).unwrap(); + let app_data_with_data = collection + .external_plugin_adapter_list + .app_data + .first() + .unwrap(); + + // Validate data matches. + assert_eq!(data_offset_1, app_data_with_data.data_offset); + assert_eq!(data_len_1, app_data_with_data.data_len); println!("Data string: {:#?}", data_string); - println!("Data offset: {:#?}", data_offset); - println!("Data len: {:#?}", data_len); + println!("Data offset: {:#?}", data_offset_1); + println!("Data len: {:#?}", data_len_1); } diff --git a/clients/rust/tests/setup/mod.rs b/clients/rust/tests/setup/mod.rs index 0c215f1b..18dbe9e8 100644 --- a/clients/rust/tests/setup/mod.rs +++ b/clients/rust/tests/setup/mod.rs @@ -232,6 +232,7 @@ pub struct AssertCollectionHelperArgs { pub current_size: u32, // TODO use PluginList type here pub plugins: Vec, + pub external_plugin_adapters: Vec, } pub async fn assert_collection( @@ -285,7 +286,58 @@ pub async fn assert_collection( } } - // TODO validate external plugin adapters here. + assert_eq!( + input.external_plugin_adapters.len(), + collection + .external_plugin_adapter_list + .lifecycle_hooks + .len() + + collection.external_plugin_adapter_list.oracles.len() + + collection.external_plugin_adapter_list.app_data.len() + ); + for plugin in input.external_plugin_adapters { + match plugin { + ExternalPluginAdapter::LifecycleHook(hook) => { + assert!(collection + .external_plugin_adapter_list + .lifecycle_hooks + .iter() + .any(|lifecyle_hook_with_data| lifecyle_hook_with_data.base == hook)) + } + ExternalPluginAdapter::Oracle(oracle) => { + assert!(collection + .external_plugin_adapter_list + .oracles + .contains(&oracle)) + } + ExternalPluginAdapter::AppData(app_data) => { + assert!(collection + .external_plugin_adapter_list + .app_data + .iter() + .any(|app_data_with_data| app_data_with_data.base == app_data)) + } + ExternalPluginAdapter::LinkedLifecycleHook(hook) => { + assert!(collection + .external_plugin_adapter_list + .linked_lifecycle_hooks + .contains(&hook)) + } + ExternalPluginAdapter::LinkedAppData(app_data) => { + assert!(collection + .external_plugin_adapter_list + .linked_app_data + .contains(&app_data)) + } + ExternalPluginAdapter::DataSection(data) => { + assert!(collection + .external_plugin_adapter_list + .data_sections + .iter() + .any(|data_sections_with_data| data_sections_with_data.base == data)) + } + } + } } pub async fn airdrop( From 160d3101df5c41f5f3647a868bc25972f7c39d6b Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:30:04 -0700 Subject: [PATCH 3/4] Update comment --- clients/rust/src/hooked/plugin.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 3bc99bce..341e9d54 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -171,7 +171,7 @@ fn unwrap_data_offset_and_data_len( } /// Fetch the external_plugin_adapter data from the registry and convert to string. -/// May not be suitable for large amounts of data. +/// May not be suitable for larger amounts of data. pub fn fetch_external_plugin_adapter_data_as_string( account: &AccountInfo, core: Option<&T>, @@ -208,8 +208,9 @@ pub fn fetch_external_plugin_adapter_data_as_string Ok((data_string, data_offset, data_len)) } -/// Fetch the external_plugin_adapter data offset and length. This can then be used to +/// Fetch the external plugin adapter data offset and length. These can be used to /// directly slice the account data for use of the external plugin adapter data elsewhere. +/// This function is more suitable for larger amounts of external plugin adapter data. pub fn fetch_external_plugin_adapter_data_info( account: &AccountInfo, core: Option<&T>, From 9a30d6acf69083602aefc641ed2df1ed577cd9f4 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:26:14 -0700 Subject: [PATCH 4/4] Remove fetch_external_plugin_adapter_data_as_string and test wrapped --- clients/rust/src/hooked/plugin.rs | 42 +----- .../tests/create_with_external_plugins.rs | 130 +++++++++++------- 2 files changed, 79 insertions(+), 93 deletions(-) diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 341e9d54..1a8cd933 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -7,7 +7,6 @@ use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, - convert_external_plugin_adapter_data_to_string, errors::MplCoreError, types::{ ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalPluginAdapterType, LinkedDataKey, @@ -170,47 +169,8 @@ fn unwrap_data_offset_and_data_len( Ok((data_offset as usize, data_len as usize)) } -/// Fetch the external_plugin_adapter data from the registry and convert to string. -/// May not be suitable for larger amounts of data. -pub fn fetch_external_plugin_adapter_data_as_string( - account: &AccountInfo, - core: Option<&T>, - plugin_key: &ExternalPluginAdapterKey, -) -> Result<(String, usize, usize), std::io::Error> { - let (registry_record, external_plugin) = - fetch_wrapped_external_plugin_adapter(account, core, plugin_key)?; - - let schema = match external_plugin { - ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => lifecycle_hook.schema, - ExternalPluginAdapter::AppData(app_data) => app_data.schema, - ExternalPluginAdapter::DataSection(data_section) => data_section.schema, - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::UnsupportedOperation.to_string(), - )); - } - }; - - let (data_offset, data_len) = - unwrap_data_offset_and_data_len(registry_record.data_offset, registry_record.data_len)?; - let end = data_offset - .checked_add(data_len) - .ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - MplCoreError::NumericalOverflow.to_string(), - ))?; - - let data_slice = &(*account.data).borrow()[data_offset..end]; - let data_string = convert_external_plugin_adapter_data_to_string(&schema, data_slice); - - // Return the data, its offset, and length. - Ok((data_string, data_offset, data_len)) -} - /// Fetch the external plugin adapter data offset and length. These can be used to -/// directly slice the account data for use of the external plugin adapter data elsewhere. -/// This function is more suitable for larger amounts of external plugin adapter data. +/// directly slice the account data for use of the external plugin adapter data. pub fn fetch_external_plugin_adapter_data_info( account: &AccountInfo, core: Option<&T>, diff --git a/clients/rust/tests/create_with_external_plugins.rs b/clients/rust/tests/create_with_external_plugins.rs index d1494c75..c6ea3f13 100644 --- a/clients/rust/tests/create_with_external_plugins.rs +++ b/clients/rust/tests/create_with_external_plugins.rs @@ -5,9 +5,10 @@ use std::borrow::BorrowMut; use mpl_core::{ accounts::{BaseAssetV1, BaseCollectionV1}, + convert_external_plugin_adapter_data_to_string, errors::MplCoreError, - fetch_external_plugin_adapter, fetch_external_plugin_adapter_data_as_string, - fetch_external_plugin_adapter_data_info, + fetch_external_plugin_adapter, fetch_external_plugin_adapter_data_info, + fetch_wrapped_external_plugin_adapter, instructions::{ WriteCollectionExternalPluginAdapterDataV1Builder, WriteExternalPluginAdapterDataV1Builder, }, @@ -448,6 +449,22 @@ async fn test_create_and_fetch_app_data() { false, 0, ); + + // Fetch external plugin adapter two ways. + // First, get the external plugin adapter in its enum. + let (registry_record, external_plugin) = fetch_wrapped_external_plugin_adapter::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + + let inner_app_data = match external_plugin { + ExternalPluginAdapter::AppData(app_data) => app_data, + _ => panic!("Unexpected external plugin adapter"), + }; + + // Second, get the inner `AppData` object directly. let (auth, app_data, offset) = fetch_external_plugin_adapter::( &account_info, None, @@ -455,48 +472,44 @@ async fn test_create_and_fetch_app_data() { ) .unwrap(); + // Validate the data matches between the two fetches. + assert_eq!(registry_record.authority, auth); + assert_eq!(inner_app_data, app_data); + println!("App data: {:#?}", app_data); println!("Auth: {:#?}", auth); println!("Offset: {:#?}", offset); // Fetch the actual app data. Validate multiple methods. - // First, get app data data offset and length. - let (data_offset_1, data_len_1) = fetch_external_plugin_adapter_data_info::( + // First, get app data data offset and length directly. + let (data_offset, data_len) = fetch_external_plugin_adapter_data_info::( &account_info, None, &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), ) .unwrap(); - // Second, get app data as a String. - let (data_string, data_offset_2, data_len_2) = - fetch_external_plugin_adapter_data_as_string::( - &account_info, - None, - &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), - ) - .unwrap(); - - // Validate app data. - assert_eq!(data_string, test_json_str); - assert_eq!(data_len_1, 36); - - // Validate data matches. - assert_eq!(data_offset_1, data_offset_2); - assert_eq!(data_len_1, data_len_2); - - // Third, get app data offset and length from a full `Asset` deserialization. + // Second, get app data offset and length from a full `Asset` deserialization. let asset = Asset::from_bytes(&account.data).unwrap(); let app_data_with_data = asset.external_plugin_adapter_list.app_data.first().unwrap(); - // Validate data matches. - assert_eq!(data_offset_1, app_data_with_data.data_offset); - assert_eq!(data_len_1, app_data_with_data.data_len); + // Validate data matches between two methods. + assert_eq!(data_offset, app_data_with_data.data_offset); + assert_eq!(data_len, app_data_with_data.data_len); + + // Convert data to string. + let data_end = data_offset.checked_add(data_len).unwrap(); + let data_slice = &account.data[data_offset..data_end]; + let data_string = convert_external_plugin_adapter_data_to_string(&app_data.schema, data_slice); + + // Validate app data. + assert_eq!(data_string, test_json_str); + assert_eq!(data_len, 36); println!("Data string: {:#?}", data_string); - println!("Data offset: {:#?}", data_offset_1); - println!("Data len: {:#?}", data_len_1); + println!("Data offset: {:#?}", data_offset); + println!("Data len: {:#?}", data_len); } #[tokio::test] @@ -590,6 +603,23 @@ async fn test_collection_create_and_fetch_app_data() { false, 0, ); + + // Fetch external plugin adapter two ways. + // First, get the external plugin adapter in its enum. + let (registry_record, external_plugin) = + fetch_wrapped_external_plugin_adapter::( + &account_info, + None, + &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), + ) + .unwrap(); + + let inner_app_data = match external_plugin { + ExternalPluginAdapter::AppData(app_data) => app_data, + _ => panic!("Unexpected external plugin adapter"), + }; + + // Second, get the inner `AppData` object directly. let (auth, app_data, offset) = fetch_external_plugin_adapter::( &account_info, None, @@ -597,38 +627,25 @@ async fn test_collection_create_and_fetch_app_data() { ) .unwrap(); + // Validate the data matches between the two fetches. + assert_eq!(registry_record.authority, auth); + assert_eq!(inner_app_data, app_data); + println!("App data: {:#?}", app_data); println!("Auth: {:#?}", auth); println!("Offset: {:#?}", offset); // Fetch the actual app data. Validate multiple methods. - // First, get app data data offset and length. - let (data_offset_1, data_len_1) = fetch_external_plugin_adapter_data_info::( + // First, get app data data offset and length directly. + let (data_offset, data_len) = fetch_external_plugin_adapter_data_info::( &account_info, None, &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), ) .unwrap(); - // Second, get app data as a String. - let (data_string, data_offset_2, data_len_2) = - fetch_external_plugin_adapter_data_as_string::( - &account_info, - None, - &ExternalPluginAdapterKey::AppData(PluginAuthority::UpdateAuthority), - ) - .unwrap(); - - // Validate app data. - assert_eq!(data_string, test_json_str); - assert_eq!(data_len_1, 36); - - // Validate data matches. - assert_eq!(data_offset_1, data_offset_2); - assert_eq!(data_len_1, data_len_2); - - // Third, get app data offset and length from a full `Asset` deserialization. + // Second, get app data offset and length from a full `Collection` deserialization. let collection = Collection::from_bytes(&account.data).unwrap(); let app_data_with_data = collection .external_plugin_adapter_list @@ -636,11 +653,20 @@ async fn test_collection_create_and_fetch_app_data() { .first() .unwrap(); - // Validate data matches. - assert_eq!(data_offset_1, app_data_with_data.data_offset); - assert_eq!(data_len_1, app_data_with_data.data_len); + // Validate data matches between two methods. + assert_eq!(data_offset, app_data_with_data.data_offset); + assert_eq!(data_len, app_data_with_data.data_len); + + // Convert data to string. + let data_end = data_offset.checked_add(data_len).unwrap(); + let data_slice = &account.data[data_offset..data_end]; + let data_string = convert_external_plugin_adapter_data_to_string(&app_data.schema, data_slice); + + // Validate app data. + assert_eq!(data_string, test_json_str); + assert_eq!(data_len, 36); println!("Data string: {:#?}", data_string); - println!("Data offset: {:#?}", data_offset_1); - println!("Data len: {:#?}", data_len_1); + println!("Data offset: {:#?}", data_offset); + println!("Data len: {:#?}", data_len); }