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 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#[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#[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#[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
115struct RegistryRecordSafe {
117 pub plugin_type: u8,
118 pub authority: PluginAuthority,
119 pub offset: u64,
120}
121
122impl RegistryRecordSafe {
123 pub fn compare_offsets(a: &RegistryRecordSafe, b: &RegistryRecordSafe) -> Ordering {
125 a.offset.cmp(&b.offset)
126 }
127}
128
129struct 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#[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 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 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 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 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}