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