mpl_core/hooked/
plugin.rs

1#[cfg(feature = "anchor")]
2use anchor_lang::prelude::AnchorDeserialize as CrateDeserialize;
3#[cfg(not(feature = "anchor"))]
4use borsh::BorshDeserialize as CrateDeserialize;
5use num_traits::FromPrimitive;
6use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
7
8use crate::{
9    accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1},
10    errors::MplCoreError,
11    types::{
12        ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalPluginAdapterType, LinkedDataKey,
13        Plugin, PluginAuthority, PluginType, RegistryRecord,
14    },
15    AddBlockerPlugin, AppDataWithData, AttributesPlugin, AutographPlugin, BaseAuthority,
16    BasePlugin, BubblegumV2Plugin, BurnDelegatePlugin, DataBlob, DataSectionWithData,
17    EditionPlugin, ExternalPluginAdaptersList, ExternalRegistryRecordSafe, FreezeDelegatePlugin,
18    FreezeExecutePlugin, ImmutableMetadataPlugin, LifecycleHookWithData, MasterEditionPlugin,
19    PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, PermanentFreezeExecutePlugin,
20    PermanentTransferDelegatePlugin, PluginRegistryV1Safe, PluginsList, RegistryRecordSafe,
21    RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, UpdateDelegatePlugin,
22    VerifiedCreatorsPlugin,
23};
24
25/// Fetch the plugin from the registry.
26pub fn fetch_plugin<T: DataBlob + SolanaAccount, U: CrateDeserialize>(
27    account: &AccountInfo,
28    plugin_type: PluginType,
29) -> Result<(PluginAuthority, U, usize), std::io::Error> {
30    let asset = T::load(account, 0)?;
31
32    if asset.len() == account.data_len() {
33        return Err(std::io::Error::new(
34            std::io::ErrorKind::Other,
35            MplCoreError::PluginNotFound.to_string(),
36        ));
37    }
38
39    let header = PluginHeaderV1::from_bytes(&(*account.data).borrow()[asset.len()..])?;
40    let plugin_registry = PluginRegistryV1Safe::from_bytes(
41        &(*account.data).borrow()[header.plugin_registry_offset as usize..],
42    )?;
43
44    // Find the plugin in the registry.
45    let registry_record = plugin_registry
46        .registry
47        .iter()
48        .find(|record| {
49            if let Some(plugin) = PluginType::from_u8(record.plugin_type) {
50                plugin == plugin_type
51            } else {
52                false
53            }
54        })
55        .ok_or(std::io::Error::new(
56            std::io::ErrorKind::Other,
57            MplCoreError::PluginNotFound.to_string(),
58        ))?;
59
60    // Deserialize the plugin.
61    let plugin =
62        Plugin::deserialize(&mut &(*account.data).borrow()[(registry_record.offset as usize)..])?;
63
64    if PluginType::from(&plugin) != plugin_type {
65        return Err(std::io::Error::new(
66            std::io::ErrorKind::Other,
67            MplCoreError::PluginNotFound.to_string(),
68        ));
69    }
70
71    let inner = U::deserialize(
72        &mut &(*account.data).borrow()[registry_record.offset.checked_add(1).ok_or(
73            std::io::Error::new(
74                std::io::ErrorKind::Other,
75                MplCoreError::NumericalOverflow.to_string(),
76            ),
77        )? as usize..],
78    )?;
79
80    // Return the plugin and its authority.
81    Ok((
82        registry_record.authority.clone(),
83        inner,
84        registry_record.offset as usize,
85    ))
86}
87
88/// Fetch the plugin on an asset.
89pub fn fetch_asset_plugin<U: CrateDeserialize>(
90    account: &AccountInfo,
91    plugin_type: PluginType,
92) -> Result<(PluginAuthority, U, usize), std::io::Error> {
93    fetch_plugin::<BaseAssetV1, U>(account, plugin_type)
94}
95
96/// Fetch the plugin on a collection.
97pub fn fetch_collection_plugin<U: CrateDeserialize>(
98    account: &AccountInfo,
99    plugin_type: PluginType,
100) -> Result<(PluginAuthority, U, usize), std::io::Error> {
101    fetch_plugin::<BaseCollectionV1, U>(account, plugin_type)
102}
103
104/// Fetch the plugin registry, dropping any unknown plugins (i.e. `PluginType`s that are too new
105///  for this client to know about).
106pub fn fetch_plugins(account_data: &[u8]) -> Result<Vec<RegistryRecord>, std::io::Error> {
107    let asset = BaseAssetV1::from_bytes(account_data)?;
108
109    let header = PluginHeaderV1::from_bytes(&account_data[asset.len()..])?;
110    let plugin_registry = PluginRegistryV1Safe::from_bytes(
111        &account_data[(header.plugin_registry_offset as usize)..],
112    )?;
113
114    let filtered_plugin_registry = plugin_registry
115        .registry
116        .iter()
117        .filter_map(|record| {
118            PluginType::from_u8(record.plugin_type).map(|plugin_type| RegistryRecord {
119                plugin_type,
120                authority: record.authority.clone(),
121                offset: record.offset,
122            })
123        })
124        .collect();
125
126    Ok(filtered_plugin_registry)
127}
128
129/// Fetch the external plugin adapter from the registry.
130pub fn fetch_external_plugin_adapter<T: DataBlob + SolanaAccount, U: CrateDeserialize>(
131    account: &AccountInfo,
132    core: Option<&T>,
133    plugin_key: &ExternalPluginAdapterKey,
134) -> Result<(PluginAuthority, U, usize), std::io::Error> {
135    let registry_record = fetch_external_registry_record(account, core, plugin_key)?;
136
137    let inner = U::deserialize(
138        &mut &(*account.data).borrow()[registry_record.offset.checked_add(1).ok_or(
139            std::io::Error::new(
140                std::io::ErrorKind::Other,
141                MplCoreError::NumericalOverflow.to_string(),
142            ),
143        )? as usize..],
144    )?;
145
146    // Return the plugin and its authority.
147    Ok((
148        registry_record.authority.clone(),
149        inner,
150        registry_record.offset as usize,
151    ))
152}
153
154/// Fetch the external plugin adapter from the registry.
155pub fn fetch_wrapped_external_plugin_adapter<T: DataBlob + SolanaAccount>(
156    account: &AccountInfo,
157    core: Option<&T>,
158    plugin_key: &ExternalPluginAdapterKey,
159) -> Result<(ExternalRegistryRecordSafe, ExternalPluginAdapter), std::io::Error> {
160    let registry_record = fetch_external_registry_record(account, core, plugin_key)?;
161
162    // Deserialize the plugin.
163    let plugin = ExternalPluginAdapter::deserialize(
164        &mut &(*account.data).borrow()[registry_record.offset as usize..],
165    )?;
166
167    // Return the plugin and its authority.
168    Ok((registry_record, plugin))
169}
170
171// Helper to unwrap optional data offset and data length.
172fn unwrap_data_offset_and_data_len(
173    data_offset: Option<u64>,
174    data_len: Option<u64>,
175) -> Result<(usize, usize), std::io::Error> {
176    let data_offset = data_offset.ok_or(std::io::Error::new(
177        std::io::ErrorKind::Other,
178        MplCoreError::InvalidPlugin.to_string(),
179    ))?;
180
181    let data_len = data_len.ok_or(std::io::Error::new(
182        std::io::ErrorKind::Other,
183        MplCoreError::InvalidPlugin.to_string(),
184    ))?;
185
186    Ok((data_offset as usize, data_len as usize))
187}
188
189/// Fetch the external plugin adapter data offset and length.  These can be used to
190/// directly slice the account data for use of the external plugin adapter data.
191pub fn fetch_external_plugin_adapter_data_info<T: DataBlob + SolanaAccount>(
192    account: &AccountInfo,
193    core: Option<&T>,
194    plugin_key: &ExternalPluginAdapterKey,
195) -> Result<(usize, usize), std::io::Error> {
196    let registry_record = fetch_external_registry_record(account, core, plugin_key)?;
197    let (data_offset, data_len) =
198        unwrap_data_offset_and_data_len(registry_record.data_offset, registry_record.data_len)?;
199
200    // Return the data offset and length.
201    Ok((data_offset, data_len))
202}
203
204// Internal helper to fetch just the external registry record for the external plugin key.
205fn fetch_external_registry_record<T: DataBlob + SolanaAccount>(
206    account: &AccountInfo,
207    core: Option<&T>,
208    plugin_key: &ExternalPluginAdapterKey,
209) -> Result<ExternalRegistryRecordSafe, std::io::Error> {
210    let size = match core {
211        Some(core) => core.len(),
212        None => {
213            let asset = T::load(account, 0)?;
214
215            if asset.len() == account.data_len() {
216                return Err(std::io::Error::new(
217                    std::io::ErrorKind::Other,
218                    MplCoreError::ExternalPluginAdapterNotFound.to_string(),
219                ));
220            }
221
222            asset.len()
223        }
224    };
225
226    let header = PluginHeaderV1::from_bytes(&(*account.data).borrow()[size..])?;
227    let plugin_registry = PluginRegistryV1Safe::from_bytes(
228        &(*account.data).borrow()[header.plugin_registry_offset as usize..],
229    )?;
230
231    // Find and return the registry record.
232    let result = find_external_plugin_adapter(&plugin_registry, plugin_key, account)?;
233    result.1.cloned().ok_or_else(|| {
234        std::io::Error::new(
235            std::io::ErrorKind::Other,
236            MplCoreError::ExternalPluginAdapterNotFound.to_string(),
237        )
238    })
239}
240
241/// List all plugins in an account, dropping any unknown plugins (i.e. `PluginType`s that are too
242/// new for this client to know about). Note this also does not support external plugin adapters for now,
243/// and will be updated when those are defined.
244pub fn list_plugins(account_data: &[u8]) -> Result<Vec<PluginType>, std::io::Error> {
245    let asset = BaseAssetV1::from_bytes(account_data)?;
246    let header = PluginHeaderV1::from_bytes(&account_data[asset.len()..])?;
247    let plugin_registry = PluginRegistryV1Safe::from_bytes(
248        &account_data[(header.plugin_registry_offset as usize)..],
249    )?;
250
251    Ok(plugin_registry
252        .registry
253        .iter()
254        .filter_map(|registry_record| PluginType::from_u8(registry_record.plugin_type))
255        .collect())
256}
257
258// Convert a slice of `RegistryRecordSafe` into the `PluginsList` type, dropping any unknown
259// plugins (i.e. `PluginType`s that are too new for this client to know about).
260pub(crate) fn registry_records_to_plugin_list(
261    registry_records: &[RegistryRecordSafe],
262    account_data: &[u8],
263) -> Result<PluginsList, std::io::Error> {
264    let result = registry_records
265        .iter()
266        .try_fold(PluginsList::default(), |mut acc, record| {
267            if PluginType::from_u8(record.plugin_type).is_some() {
268                let authority: BaseAuthority = record.authority.clone().into();
269                let base = BasePlugin {
270                    authority,
271                    offset: Some(record.offset),
272                };
273                let plugin = Plugin::deserialize(&mut &account_data[record.offset as usize..])?;
274
275                match plugin {
276                    Plugin::Royalties(royalties) => {
277                        acc.royalties = Some(RoyaltiesPlugin { base, royalties });
278                    }
279                    Plugin::FreezeDelegate(freeze_delegate) => {
280                        acc.freeze_delegate = Some(FreezeDelegatePlugin {
281                            base,
282                            freeze_delegate,
283                        });
284                    }
285                    Plugin::BurnDelegate(burn_delegate) => {
286                        acc.burn_delegate = Some(BurnDelegatePlugin {
287                            base,
288                            burn_delegate,
289                        });
290                    }
291                    Plugin::TransferDelegate(transfer_delegate) => {
292                        acc.transfer_delegate = Some(TransferDelegatePlugin {
293                            base,
294                            transfer_delegate,
295                        });
296                    }
297                    Plugin::UpdateDelegate(update_delegate) => {
298                        acc.update_delegate = Some(UpdateDelegatePlugin {
299                            base,
300                            update_delegate,
301                        });
302                    }
303                    Plugin::PermanentFreezeDelegate(permanent_freeze_delegate) => {
304                        acc.permanent_freeze_delegate = Some(PermanentFreezeDelegatePlugin {
305                            base,
306                            permanent_freeze_delegate,
307                        });
308                    }
309                    Plugin::Attributes(attributes) => {
310                        acc.attributes = Some(AttributesPlugin { base, attributes });
311                    }
312                    Plugin::PermanentTransferDelegate(permanent_transfer_delegate) => {
313                        acc.permanent_transfer_delegate = Some(PermanentTransferDelegatePlugin {
314                            base,
315                            permanent_transfer_delegate,
316                        })
317                    }
318                    Plugin::PermanentBurnDelegate(permanent_burn_delegate) => {
319                        acc.permanent_burn_delegate = Some(PermanentBurnDelegatePlugin {
320                            base,
321                            permanent_burn_delegate,
322                        })
323                    }
324                    Plugin::Edition(edition) => acc.edition = Some(EditionPlugin { base, edition }),
325                    Plugin::MasterEdition(master_edition) => {
326                        acc.master_edition = Some(MasterEditionPlugin {
327                            base,
328                            master_edition,
329                        })
330                    }
331                    Plugin::AddBlocker(add_blocker) => {
332                        acc.add_blocker = Some(AddBlockerPlugin { base, add_blocker })
333                    }
334                    Plugin::ImmutableMetadata(immutable_metadata) => {
335                        acc.immutable_metadata = Some(ImmutableMetadataPlugin {
336                            base,
337                            immutable_metadata,
338                        })
339                    }
340                    Plugin::VerifiedCreators(verified_creators) => {
341                        acc.verified_creators = Some(VerifiedCreatorsPlugin {
342                            base,
343                            verified_creators,
344                        })
345                    }
346                    Plugin::Autograph(autograph) => {
347                        acc.autograph = Some(AutographPlugin { base, autograph })
348                    }
349                    Plugin::BubblegumV2(bubblegum_v2) => {
350                        acc.bubblegum_v2 = Some(BubblegumV2Plugin { base, bubblegum_v2 })
351                    }
352                    Plugin::FreezeExecute(freeze_execute) => {
353                        acc.freeze_execute = Some(FreezeExecutePlugin {
354                            base,
355                            freeze_execute,
356                        })
357                    }
358                    Plugin::PermanentFreezeExecute(permanent_freeze_execute) => {
359                        acc.permanent_freeze_execute = Some(PermanentFreezeExecutePlugin {
360                            base,
361                            permanent_freeze_execute,
362                        })
363                    }
364                }
365            }
366            Ok(acc)
367        });
368
369    result
370}
371
372// Convert a slice of `AdapterRegistryRecordSafe` into the `ExternalPluginAdaptersList` type, dropping any unknown
373// plugins (i.e. `ExternalPluginAdapterType`s that are too new for this client to know about).
374pub(crate) fn registry_records_to_external_plugin_adapter_list(
375    registry_records: &[ExternalRegistryRecordSafe],
376    account_data: &[u8],
377) -> Result<ExternalPluginAdaptersList, std::io::Error> {
378    let result = registry_records.iter().try_fold(
379        ExternalPluginAdaptersList::default(),
380        |mut acc, record| {
381            if ExternalPluginAdapterType::from_u8(record.plugin_type).is_some() {
382                let plugin = ExternalPluginAdapter::deserialize(
383                    &mut &account_data[record.offset as usize..],
384                )?;
385
386                match plugin {
387                    ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => {
388                        let (data_offset, data_len) =
389                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
390
391                        acc.lifecycle_hooks.push(LifecycleHookWithData {
392                            base: lifecycle_hook,
393                            data_offset,
394                            data_len,
395                        })
396                    }
397                    ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => {
398                        acc.linked_lifecycle_hooks.push(lifecycle_hook)
399                    }
400                    ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle),
401                    ExternalPluginAdapter::AppData(app_data) => {
402                        let (data_offset, data_len) =
403                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
404
405                        acc.app_data.push(AppDataWithData {
406                            base: app_data,
407                            data_offset,
408                            data_len,
409                        })
410                    }
411                    ExternalPluginAdapter::LinkedAppData(app_data) => {
412                        acc.linked_app_data.push(app_data)
413                    }
414                    ExternalPluginAdapter::DataSection(data_section) => {
415                        let (data_offset, data_len) =
416                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
417
418                        acc.data_sections.push(DataSectionWithData {
419                            base: data_section,
420                            data_offset,
421                            data_len,
422                        })
423                    }
424                }
425            }
426            Ok(acc)
427        },
428    );
429
430    result
431}
432
433pub(crate) fn find_external_plugin_adapter<'b>(
434    plugin_registry: &'b PluginRegistryV1Safe,
435    plugin_key: &ExternalPluginAdapterKey,
436    account: &AccountInfo<'_>,
437) -> Result<(Option<usize>, Option<&'b ExternalRegistryRecordSafe>), std::io::Error> {
438    let mut result = (None, None);
439    for (i, record) in plugin_registry.external_registry.iter().enumerate() {
440        if record.plugin_type == ExternalPluginAdapterType::from(plugin_key) as u8
441            && (match plugin_key {
442                ExternalPluginAdapterKey::LifecycleHook(address)
443                | ExternalPluginAdapterKey::Oracle(address)
444                | ExternalPluginAdapterKey::LinkedLifecycleHook(address) => {
445                    let pubkey_offset = record.offset.checked_add(1).ok_or(std::io::Error::new(
446                        std::io::ErrorKind::Other,
447                        MplCoreError::NumericalOverflow,
448                    ))?;
449                    address
450                        == &match Pubkey::deserialize(
451                            &mut &account.data.borrow()[pubkey_offset as usize..],
452                        ) {
453                            Ok(address) => address,
454                            Err(_) => {
455                                return Err(std::io::Error::new(
456                                    std::io::ErrorKind::Other,
457                                    Box::<dyn std::error::Error + Send + Sync>::from(
458                                        MplCoreError::DeserializationError,
459                                    ),
460                                ))
461                            }
462                        }
463                }
464                ExternalPluginAdapterKey::AppData(authority) => {
465                    let authority_offset =
466                        record.offset.checked_add(1).ok_or(std::io::Error::new(
467                            std::io::ErrorKind::Other,
468                            MplCoreError::NumericalOverflow,
469                        ))?;
470                    authority
471                        == &match PluginAuthority::deserialize(
472                            &mut &account.data.borrow()[authority_offset as usize..],
473                        ) {
474                            Ok(authority) => authority,
475                            Err(_) => {
476                                return Err(std::io::Error::new(
477                                    std::io::ErrorKind::Other,
478                                    Box::<dyn std::error::Error + Send + Sync>::from(
479                                        MplCoreError::DeserializationError,
480                                    ),
481                                ))
482                            }
483                        }
484                }
485                ExternalPluginAdapterKey::LinkedAppData(authority) => {
486                    let authority_offset =
487                        record.offset.checked_add(1).ok_or(std::io::Error::new(
488                            std::io::ErrorKind::Other,
489                            MplCoreError::NumericalOverflow,
490                        ))?;
491                    authority
492                        == &match PluginAuthority::deserialize(
493                            &mut &account.data.borrow()[authority_offset as usize..],
494                        ) {
495                            Ok(authority) => authority,
496                            Err(_) => {
497                                return Err(std::io::Error::new(
498                                    std::io::ErrorKind::Other,
499                                    Box::<dyn std::error::Error + Send + Sync>::from(
500                                        MplCoreError::DeserializationError,
501                                    ),
502                                ))
503                            }
504                        }
505                }
506                ExternalPluginAdapterKey::DataSection(linked_data_key) => {
507                    let linked_data_key_offset =
508                        record.offset.checked_add(1).ok_or(std::io::Error::new(
509                            std::io::ErrorKind::Other,
510                            MplCoreError::NumericalOverflow,
511                        ))?;
512                    linked_data_key
513                        == &match LinkedDataKey::deserialize(
514                            &mut &account.data.borrow()[linked_data_key_offset as usize..],
515                        ) {
516                            Ok(linked_data_key) => linked_data_key,
517                            Err(_) => {
518                                return Err(std::io::Error::new(
519                                    std::io::ErrorKind::Other,
520                                    Box::<dyn std::error::Error + Send + Sync>::from(
521                                        MplCoreError::DeserializationError,
522                                    ),
523                                ))
524                            }
525                        }
526                }
527            })
528        {
529            result = (Some(i), Some(record));
530            break;
531        }
532    }
533
534    Ok(result)
535}