Skip to main content

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, GroupsPlugin, ImmutableMetadataPlugin, LifecycleHookWithData,
19    MasterEditionPlugin, PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin,
20    PermanentFreezeExecutePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe,
21    PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin,
22    UpdateDelegatePlugin, 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::Groups(groups) => acc.groups = Some(GroupsPlugin { base, groups }),
353                    Plugin::FreezeExecute(freeze_execute) => {
354                        acc.freeze_execute = Some(FreezeExecutePlugin {
355                            base,
356                            freeze_execute,
357                        })
358                    }
359                    Plugin::PermanentFreezeExecute(permanent_freeze_execute) => {
360                        acc.permanent_freeze_execute = Some(PermanentFreezeExecutePlugin {
361                            base,
362                            permanent_freeze_execute,
363                        })
364                    }
365                }
366            }
367            Ok(acc)
368        });
369
370    result
371}
372
373// Convert a slice of `AdapterRegistryRecordSafe` into the `ExternalPluginAdaptersList` type, dropping any unknown
374// plugins (i.e. `ExternalPluginAdapterType`s that are too new for this client to know about).
375pub(crate) fn registry_records_to_external_plugin_adapter_list(
376    registry_records: &[ExternalRegistryRecordSafe],
377    account_data: &[u8],
378) -> Result<ExternalPluginAdaptersList, std::io::Error> {
379    let result = registry_records.iter().try_fold(
380        ExternalPluginAdaptersList::default(),
381        |mut acc, record| {
382            if ExternalPluginAdapterType::from_u8(record.plugin_type).is_some() {
383                let plugin = ExternalPluginAdapter::deserialize(
384                    &mut &account_data[record.offset as usize..],
385                )?;
386
387                match plugin {
388                    ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => {
389                        let (data_offset, data_len) =
390                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
391
392                        acc.lifecycle_hooks.push(LifecycleHookWithData {
393                            base: lifecycle_hook,
394                            data_offset,
395                            data_len,
396                        })
397                    }
398                    ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => {
399                        acc.linked_lifecycle_hooks.push(lifecycle_hook)
400                    }
401                    ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle),
402                    ExternalPluginAdapter::AppData(app_data) => {
403                        let (data_offset, data_len) =
404                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
405
406                        acc.app_data.push(AppDataWithData {
407                            base: app_data,
408                            data_offset,
409                            data_len,
410                        })
411                    }
412                    ExternalPluginAdapter::LinkedAppData(app_data) => {
413                        acc.linked_app_data.push(app_data)
414                    }
415                    ExternalPluginAdapter::DataSection(data_section) => {
416                        let (data_offset, data_len) =
417                            unwrap_data_offset_and_data_len(record.data_offset, record.data_len)?;
418
419                        acc.data_sections.push(DataSectionWithData {
420                            base: data_section,
421                            data_offset,
422                            data_len,
423                        })
424                    }
425                    ExternalPluginAdapter::AgentIdentity(agent_identity) => {
426                        acc.agent_identities.push(agent_identity)
427                    }
428                }
429            }
430            Ok(acc)
431        },
432    );
433
434    result
435}
436
437pub(crate) fn find_external_plugin_adapter<'b>(
438    plugin_registry: &'b PluginRegistryV1Safe,
439    plugin_key: &ExternalPluginAdapterKey,
440    account: &AccountInfo<'_>,
441) -> Result<(Option<usize>, Option<&'b ExternalRegistryRecordSafe>), std::io::Error> {
442    let mut result = (None, None);
443    for (i, record) in plugin_registry.external_registry.iter().enumerate() {
444        if record.plugin_type == ExternalPluginAdapterType::from(plugin_key) as u8
445            && (match plugin_key {
446                ExternalPluginAdapterKey::LifecycleHook(address)
447                | ExternalPluginAdapterKey::Oracle(address)
448                | ExternalPluginAdapterKey::LinkedLifecycleHook(address) => {
449                    let pubkey_offset = record.offset.checked_add(1).ok_or(std::io::Error::new(
450                        std::io::ErrorKind::Other,
451                        MplCoreError::NumericalOverflow,
452                    ))?;
453                    address
454                        == &match Pubkey::deserialize(
455                            &mut &account.data.borrow()[pubkey_offset as usize..],
456                        ) {
457                            Ok(address) => address,
458                            Err(_) => {
459                                return Err(std::io::Error::new(
460                                    std::io::ErrorKind::Other,
461                                    Box::<dyn std::error::Error + Send + Sync>::from(
462                                        MplCoreError::DeserializationError,
463                                    ),
464                                ))
465                            }
466                        }
467                }
468                ExternalPluginAdapterKey::AppData(authority) => {
469                    let authority_offset =
470                        record.offset.checked_add(1).ok_or(std::io::Error::new(
471                            std::io::ErrorKind::Other,
472                            MplCoreError::NumericalOverflow,
473                        ))?;
474                    authority
475                        == &match PluginAuthority::deserialize(
476                            &mut &account.data.borrow()[authority_offset as usize..],
477                        ) {
478                            Ok(authority) => authority,
479                            Err(_) => {
480                                return Err(std::io::Error::new(
481                                    std::io::ErrorKind::Other,
482                                    Box::<dyn std::error::Error + Send + Sync>::from(
483                                        MplCoreError::DeserializationError,
484                                    ),
485                                ))
486                            }
487                        }
488                }
489                ExternalPluginAdapterKey::LinkedAppData(authority) => {
490                    let authority_offset =
491                        record.offset.checked_add(1).ok_or(std::io::Error::new(
492                            std::io::ErrorKind::Other,
493                            MplCoreError::NumericalOverflow,
494                        ))?;
495                    authority
496                        == &match PluginAuthority::deserialize(
497                            &mut &account.data.borrow()[authority_offset as usize..],
498                        ) {
499                            Ok(authority) => authority,
500                            Err(_) => {
501                                return Err(std::io::Error::new(
502                                    std::io::ErrorKind::Other,
503                                    Box::<dyn std::error::Error + Send + Sync>::from(
504                                        MplCoreError::DeserializationError,
505                                    ),
506                                ))
507                            }
508                        }
509                }
510                ExternalPluginAdapterKey::DataSection(linked_data_key) => {
511                    let linked_data_key_offset =
512                        record.offset.checked_add(1).ok_or(std::io::Error::new(
513                            std::io::ErrorKind::Other,
514                            MplCoreError::NumericalOverflow,
515                        ))?;
516                    linked_data_key
517                        == &match LinkedDataKey::deserialize(
518                            &mut &account.data.borrow()[linked_data_key_offset as usize..],
519                        ) {
520                            Ok(linked_data_key) => linked_data_key,
521                            Err(_) => {
522                                return Err(std::io::Error::new(
523                                    std::io::ErrorKind::Other,
524                                    Box::<dyn std::error::Error + Send + Sync>::from(
525                                        MplCoreError::DeserializationError,
526                                    ),
527                                ))
528                            }
529                        }
530                }
531                // AgentIdentity is a unit variant key (only one per asset).
532                ExternalPluginAdapterKey::AgentIdentity => true,
533            })
534        {
535            result = (Some(i), Some(record));
536            break;
537        }
538    }
539
540    Ok(result)
541}