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
25pub 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 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 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 Ok((
82 registry_record.authority.clone(),
83 inner,
84 registry_record.offset as usize,
85 ))
86}
87
88pub 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
96pub 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
104pub 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
129pub 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 Ok((
148 registry_record.authority.clone(),
149 inner,
150 registry_record.offset as usize,
151 ))
152}
153
154pub 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 let plugin = ExternalPluginAdapter::deserialize(
164 &mut &(*account.data).borrow()[registry_record.offset as usize..],
165 )?;
166
167 Ok((registry_record, plugin))
169}
170
171fn 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
189pub 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 Ok((data_offset, data_len))
202}
203
204fn 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 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
241pub 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
258pub(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
372pub(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}