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
24pub 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 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 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 Ok((
81 registry_record.authority.clone(),
82 inner,
83 registry_record.offset as usize,
84 ))
85}
86
87pub 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
95pub 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
103pub 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
128pub 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 Ok((
147 registry_record.authority.clone(),
148 inner,
149 registry_record.offset as usize,
150 ))
151}
152
153pub 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 let plugin = ExternalPluginAdapter::deserialize(
163 &mut &(*account.data).borrow()[registry_record.offset as usize..],
164 )?;
165
166 Ok((registry_record, plugin))
168}
169
170fn 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
188pub 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 Ok((data_offset, data_len))
201}
202
203fn 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 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
240pub 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
257pub(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
365pub(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}