core_preview/
indexable_asset.rs

1use base64::prelude::*;
2use borsh::BorshDeserialize;
3use solana_program::pubkey::Pubkey;
4use std::{
5    cmp::Ordering,
6    collections::HashMap,
7    fmt::{Display, Formatter},
8    io::ErrorKind,
9};
10
11use crate::{
12    accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1},
13    types::{Key, Plugin, PluginAuthority, PluginType, UpdateAuthority},
14    DataBlob,
15};
16
17impl PluginType {
18    // Needed to determine if a plugin is a known or unknown type.
19    // TODO: Derive this using Kinobi.
20    pub fn from_u8(n: u8) -> Option<PluginType> {
21        match n {
22            0 => Some(PluginType::Royalties),
23            1 => Some(PluginType::FreezeDelegate),
24            2 => Some(PluginType::BurnDelegate),
25            3 => Some(PluginType::TransferDelegate),
26            4 => Some(PluginType::UpdateDelegate),
27            5 => Some(PluginType::PermanentFreezeDelegate),
28            6 => Some(PluginType::Attributes),
29            7 => Some(PluginType::PermanentTransferDelegate),
30            8 => Some(PluginType::PermanentBurnDelegate),
31            _ => None,
32        }
33    }
34}
35
36impl Display for PluginType {
37    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
38        let message = match self {
39            PluginType::Royalties => "royalties".to_string(),
40            PluginType::FreezeDelegate => "freeze_delegate".to_string(),
41            PluginType::BurnDelegate => "burn_delegate".to_string(),
42            PluginType::TransferDelegate => "transfer_delegate".to_string(),
43            PluginType::UpdateDelegate => "update_delegate".to_string(),
44            PluginType::PermanentFreezeDelegate => "permanent_freeze_delegate".to_string(),
45            PluginType::Attributes => "attributes".to_string(),
46            PluginType::PermanentTransferDelegate => "permanent_transfer_delegate".to_string(),
47            PluginType::PermanentBurnDelegate => "permanent_burn_delegate".to_string(),
48        };
49
50        write!(f, "{message}")
51    }
52}
53
54/// Schema used for indexing known plugin types.
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct IndexablePluginSchemaV1 {
58    pub index: u64,
59    pub offset: u64,
60    pub authority: PluginAuthority,
61    pub data: Plugin,
62}
63
64/// Schema used for indexing unknown plugin types, storing the plugin as raw data.
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct IndexableUnknownPluginSchemaV1 {
68    pub index: u64,
69    pub offset: u64,
70    pub authority: PluginAuthority,
71    pub r#type: u8,
72    pub data: String,
73}
74
75// Type used to store a plugin that was processed and is either a known or unknown plugin type.
76// Used when building an `IndexableAsset`.
77#[derive(Clone, Debug, Eq, PartialEq)]
78enum ProcessedPlugin {
79    Known((PluginType, IndexablePluginSchemaV1)),
80    Unknown(IndexableUnknownPluginSchemaV1),
81}
82
83impl ProcessedPlugin {
84    fn from_data(
85        index: u64,
86        offset: u64,
87        plugin_type: u8,
88        plugin_slice: &mut &[u8],
89        authority: PluginAuthority,
90    ) -> Result<Self, std::io::Error> {
91        let processed_plugin = if let Some(plugin_type) = PluginType::from_u8(plugin_type) {
92            let plugin = Plugin::deserialize(plugin_slice)?;
93            let indexable_plugin_schema = IndexablePluginSchemaV1 {
94                index,
95                offset,
96                authority,
97                data: plugin,
98            };
99            ProcessedPlugin::Known((plugin_type, indexable_plugin_schema))
100        } else {
101            let encoded: String = BASE64_STANDARD.encode(plugin_slice);
102            ProcessedPlugin::Unknown(IndexableUnknownPluginSchemaV1 {
103                index,
104                offset,
105                authority,
106                r#type: plugin_type,
107                data: encoded,
108            })
109        };
110
111        Ok(processed_plugin)
112    }
113}
114
115// Registry record that can be used when some plugins are not known.
116struct RegistryRecordSafe {
117    pub plugin_type: u8,
118    pub authority: PluginAuthority,
119    pub offset: u64,
120}
121
122impl RegistryRecordSafe {
123    /// Associated function for sorting `RegistryRecordIndexable` by offset.
124    pub fn compare_offsets(a: &RegistryRecordSafe, b: &RegistryRecordSafe) -> Ordering {
125        a.offset.cmp(&b.offset)
126    }
127}
128
129// Plugin registry that can safely be deserialized even if some plugins are not known.
130struct PluginRegistryV1Safe {
131    pub _key: Key,
132    pub registry: Vec<RegistryRecordSafe>,
133}
134
135impl PluginRegistryV1Safe {
136    #[inline(always)]
137    pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> {
138        let mut data: &[u8] = data;
139        let key = Key::deserialize(&mut data)?;
140        if key != Key::PluginRegistryV1 {
141            return Err(ErrorKind::InvalidInput.into());
142        }
143
144        let registry_size = u32::deserialize(&mut data)?;
145
146        let mut registry = vec![];
147        for _ in 0..registry_size {
148            let plugin_type = u8::deserialize(&mut data)?;
149            let authority = PluginAuthority::deserialize(&mut data)?;
150            let offset = u64::deserialize(&mut data)?;
151
152            registry.push(RegistryRecordSafe {
153                plugin_type,
154                authority,
155                offset,
156            });
157        }
158
159        Ok(Self {
160            _key: key,
161            registry,
162        })
163    }
164}
165
166/// A type used to store both Core Assets and Core Collections for indexing.
167#[derive(Clone, Debug, Eq, PartialEq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct IndexableAsset {
170    pub owner: Option<Pubkey>,
171    pub update_authority: UpdateAuthority,
172    pub name: String,
173    pub uri: String,
174    pub seq: u64,
175    pub num_minted: Option<u32>,
176    pub current_size: Option<u32>,
177    pub plugins: HashMap<String, IndexablePluginSchemaV1>,
178    pub unknown_plugins: Vec<IndexableUnknownPluginSchemaV1>,
179}
180
181impl IndexableAsset {
182    /// Create a new `IndexableAsset` from a `BaseAssetV1``.  Note this uses a passed-in `seq` rather than
183    /// the one contained in `asset` to avoid errors.
184    pub fn from_asset(asset: BaseAssetV1, seq: u64) -> Self {
185        Self {
186            owner: Some(asset.owner),
187            update_authority: asset.update_authority,
188            name: asset.name,
189            uri: asset.uri,
190            seq,
191            num_minted: None,
192            current_size: None,
193            plugins: HashMap::new(),
194            unknown_plugins: Vec::new(),
195        }
196    }
197
198    /// Create a new `IndexableAsset` from a `BaseCollectionV1`.
199    pub fn from_collection(collection: BaseCollectionV1) -> Self {
200        Self {
201            owner: None,
202            update_authority: UpdateAuthority::Address(collection.update_authority),
203            name: collection.name,
204            uri: collection.uri,
205            seq: 0,
206            num_minted: Some(collection.num_minted),
207            current_size: Some(collection.current_size),
208            plugins: HashMap::new(),
209            unknown_plugins: Vec::new(),
210        }
211    }
212
213    // Add a processed plugin to the correct `IndexableAsset` struct member.
214    fn add_processed_plugin(&mut self, plugin: ProcessedPlugin) {
215        match plugin {
216            ProcessedPlugin::Known((plugin_type, indexable_plugin_schema)) => {
217                self.plugins
218                    .insert(plugin_type.to_string(), indexable_plugin_schema);
219            }
220            ProcessedPlugin::Unknown(indexable_unknown_plugin_schema) => {
221                self.unknown_plugins.push(indexable_unknown_plugin_schema)
222            }
223        }
224    }
225
226    /// Fetch the base `Asset`` or `Collection`` and all the plugins and store in an `IndexableAsset`.
227    pub fn fetch(key: Key, account: &[u8]) -> Result<Self, std::io::Error> {
228        let (mut indexable_asset, base_size) = match key {
229            Key::AssetV1 => {
230                let asset = BaseAssetV1::from_bytes(account)?;
231                let base_size = asset.get_size();
232                let indexable_asset = Self::from_asset(asset, 0);
233                (indexable_asset, base_size)
234            }
235            Key::CollectionV1 => {
236                let collection = BaseCollectionV1::from_bytes(account)?;
237                let base_size = collection.get_size();
238                let indexable_asset = Self::from_collection(collection);
239                (indexable_asset, base_size)
240            }
241            _ => return Err(ErrorKind::InvalidInput.into()),
242        };
243
244        if base_size != account.len() {
245            let header = PluginHeaderV1::from_bytes(&account[base_size..])?;
246            let plugin_registry = PluginRegistryV1Safe::from_bytes(
247                &account[(header.plugin_registry_offset as usize)..],
248            )?;
249
250            let mut registry_records = plugin_registry.registry;
251            registry_records.sort_by(RegistryRecordSafe::compare_offsets);
252
253            for (i, records) in registry_records.windows(2).enumerate() {
254                let mut plugin_slice =
255                    &account[records[0].offset as usize..records[1].offset as usize];
256                let processed_plugin = ProcessedPlugin::from_data(
257                    i as u64,
258                    records[0].offset,
259                    records[0].plugin_type,
260                    &mut plugin_slice,
261                    records[0].authority.clone(),
262                )?;
263
264                indexable_asset.add_processed_plugin(processed_plugin);
265            }
266
267            if let Some(record) = registry_records.last() {
268                let mut plugin_slice =
269                    &account[record.offset as usize..header.plugin_registry_offset as usize];
270
271                let processed_plugin = ProcessedPlugin::from_data(
272                    registry_records.len() as u64 - 1,
273                    record.offset,
274                    record.plugin_type,
275                    &mut plugin_slice,
276                    record.authority.clone(),
277                )?;
278
279                indexable_asset.add_processed_plugin(processed_plugin);
280            }
281        }
282        Ok(indexable_asset)
283    }
284}