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
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::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
373pub(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 ExternalPluginAdapterKey::AgentIdentity => true,
533 })
534 {
535 result = (Some(i), Some(record));
536 break;
537 }
538 }
539
540 Ok(result)
541}