1pub mod plugin;
2pub use plugin::*;
3
4pub mod advanced_types;
5pub use advanced_types::*;
6
7pub mod asset;
8
9pub mod collection;
10
11#[cfg(feature = "anchor")]
12use anchor_lang::prelude::{
13 AnchorDeserialize as CrateDeserialize, AnchorSerialize as CrateSerialize,
14};
15use base64::prelude::*;
16#[cfg(not(feature = "anchor"))]
17use borsh::{BorshDeserialize as CrateDeserialize, BorshSerialize as CrateSerialize};
18use modular_bitfield::{bitfield, specifiers::B29};
19use num_traits::FromPrimitive;
20use std::{cmp::Ordering, mem::size_of};
21
22use crate::{
23 accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1, PluginRegistryV1},
24 errors::MplCoreError,
25 types::{
26 ExternalCheckResult, ExternalPluginAdapterKey, ExternalPluginAdapterSchema,
27 ExternalPluginAdapterType, Key, Plugin, PluginType, RegistryRecord, UpdateAuthority,
28 },
29};
30use solana_program::account_info::AccountInfo;
31
32impl From<&Plugin> for PluginType {
33 fn from(plugin: &Plugin) -> Self {
34 match plugin {
35 Plugin::AddBlocker(_) => PluginType::AddBlocker,
36 Plugin::ImmutableMetadata(_) => PluginType::ImmutableMetadata,
37 Plugin::Royalties(_) => PluginType::Royalties,
38 Plugin::FreezeDelegate(_) => PluginType::FreezeDelegate,
39 Plugin::BurnDelegate(_) => PluginType::BurnDelegate,
40 Plugin::TransferDelegate(_) => PluginType::TransferDelegate,
41 Plugin::UpdateDelegate(_) => PluginType::UpdateDelegate,
42 Plugin::PermanentFreezeDelegate(_) => PluginType::PermanentFreezeDelegate,
43 Plugin::Attributes(_) => PluginType::Attributes,
44 Plugin::PermanentTransferDelegate(_) => PluginType::PermanentTransferDelegate,
45 Plugin::PermanentBurnDelegate(_) => PluginType::PermanentBurnDelegate,
46 Plugin::Edition(_) => PluginType::Edition,
47 Plugin::MasterEdition(_) => PluginType::MasterEdition,
48 Plugin::VerifiedCreators(_) => PluginType::VerifiedCreators,
49 Plugin::Autograph(_) => PluginType::Autograph,
50 Plugin::BubblegumV2(_) => PluginType::BubblegumV2,
51 Plugin::FreezeExecute(_) => PluginType::FreezeExecute,
52 }
53 }
54}
55
56impl BaseAssetV1 {
57 const BASE_LEN: usize = 1 + 32 + 1 + 4 + 4 + 1; }
65
66impl BaseCollectionV1 {
67 const BASE_LEN: usize = 1 + 32 + 4 + 4 + 4 + 4; }
75
76#[cfg(feature = "anchor")]
79mod anchor_impl {
80 use super::*;
81 use anchor_lang::{
82 prelude::{Owner, Pubkey},
83 AccountDeserialize, AccountSerialize, Discriminator,
84 };
85
86 impl AccountDeserialize for BaseAssetV1 {
87 fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
88 let base_asset = Self::from_bytes(buf)?;
89 Ok(base_asset)
90 }
91 }
92
93 impl AccountSerialize for BaseAssetV1 {}
96
97 impl Discriminator for BaseAssetV1 {
99 const DISCRIMINATOR: &[u8] = &[0u8; 8];
100 }
101
102 impl Owner for BaseAssetV1 {
103 fn owner() -> Pubkey {
104 crate::ID
105 }
106 }
107
108 impl AccountDeserialize for BaseCollectionV1 {
109 fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
110 let base_asset = Self::from_bytes(buf)?;
111 Ok(base_asset)
112 }
113 }
114
115 impl AccountSerialize for BaseCollectionV1 {}
118
119 impl Discriminator for BaseCollectionV1 {
121 const DISCRIMINATOR: &[u8] = &[0u8; 8];
122 }
123
124 impl Owner for BaseCollectionV1 {
125 fn owner() -> Pubkey {
126 crate::ID
127 }
128 }
129}
130
131impl DataBlob for BaseAssetV1 {
132 fn len(&self) -> usize {
133 let mut size = BaseAssetV1::BASE_LEN + self.name.len() + self.uri.len();
134
135 if let UpdateAuthority::Address(_) | UpdateAuthority::Collection(_) = self.update_authority
136 {
137 size += 32;
138 }
139
140 if self.seq.is_some() {
141 size += size_of::<u64>();
142 }
143 size
144 }
145}
146
147impl SolanaAccount for BaseAssetV1 {
148 fn key() -> Key {
149 Key::AssetV1
150 }
151}
152
153impl DataBlob for BaseCollectionV1 {
154 fn len(&self) -> usize {
155 Self::BASE_LEN + self.name.len() + self.uri.len()
156 }
157}
158
159impl SolanaAccount for BaseCollectionV1 {
160 fn key() -> Key {
161 Key::CollectionV1
162 }
163}
164
165impl SolanaAccount for PluginRegistryV1 {
166 fn key() -> Key {
167 Key::PluginRegistryV1
168 }
169}
170
171impl SolanaAccount for PluginHeaderV1 {
172 fn key() -> Key {
173 Key::PluginHeaderV1
174 }
175}
176
177impl Key {
178 pub fn from_slice(data: &[u8], offset: usize) -> Result<Self, std::io::Error> {
180 let key_byte = *data.get(offset).ok_or_else(|| {
181 std::io::Error::new(
182 std::io::ErrorKind::Other,
183 MplCoreError::DeserializationError.to_string(),
184 )
185 })?;
186
187 Self::from_u8(key_byte).ok_or_else(|| {
188 std::io::Error::new(
189 std::io::ErrorKind::Other,
190 MplCoreError::DeserializationError.to_string(),
191 )
192 })
193 }
194}
195
196pub fn load_key(account: &AccountInfo, offset: usize) -> Result<Key, std::io::Error> {
198 let data = account.data.borrow();
199 Key::from_slice(&data, offset)
200}
201
202#[allow(clippy::len_without_is_empty)]
204pub trait DataBlob: CrateSerialize + CrateDeserialize {
205 fn len(&self) -> usize;
207}
208
209pub trait SolanaAccount: CrateSerialize + CrateDeserialize {
211 fn key() -> Key;
213
214 fn load(account: &AccountInfo, offset: usize) -> Result<Self, std::io::Error> {
216 let key = load_key(account, offset)?;
217
218 if key != Self::key() {
219 return Err(std::io::Error::new(
220 std::io::ErrorKind::Other,
221 MplCoreError::DeserializationError.to_string(),
222 ));
223 }
224
225 let mut bytes: &[u8] = &(*account.data).borrow()[offset..];
226 Self::deserialize(&mut bytes)
227 }
228
229 fn save(&self, account: &AccountInfo, offset: usize) -> Result<(), std::io::Error> {
231 borsh::to_writer(&mut account.data.borrow_mut()[offset..], self)
232 }
233}
234
235impl RegistryRecord {
236 pub fn compare_offsets(a: &RegistryRecord, b: &RegistryRecord) -> Ordering {
238 a.offset.cmp(&b.offset)
239 }
240}
241
242#[bitfield(bits = 32)]
244#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)]
245pub struct ExternalCheckResultBits {
246 pub can_listen: bool,
247 pub can_approve: bool,
248 pub can_reject: bool,
249 pub empty_bits: B29,
250}
251
252impl From<ExternalCheckResult> for ExternalCheckResultBits {
253 fn from(check_result: ExternalCheckResult) -> Self {
254 ExternalCheckResultBits::from_bytes(check_result.flags.to_le_bytes())
255 }
256}
257
258impl From<ExternalCheckResultBits> for ExternalCheckResult {
259 fn from(bits: ExternalCheckResultBits) -> Self {
260 ExternalCheckResult {
261 flags: u32::from_le_bytes(bits.into_bytes()),
262 }
263 }
264}
265
266impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType {
267 fn from(key: &ExternalPluginAdapterKey) -> Self {
268 match key {
269 ExternalPluginAdapterKey::LifecycleHook(_) => ExternalPluginAdapterType::LifecycleHook,
270 ExternalPluginAdapterKey::LinkedLifecycleHook(_) => {
271 ExternalPluginAdapterType::LinkedLifecycleHook
272 }
273 ExternalPluginAdapterKey::Oracle(_) => ExternalPluginAdapterType::Oracle,
274 ExternalPluginAdapterKey::AppData(_) => ExternalPluginAdapterType::AppData,
275 ExternalPluginAdapterKey::LinkedAppData(_) => ExternalPluginAdapterType::LinkedAppData,
276 ExternalPluginAdapterKey::DataSection(_) => ExternalPluginAdapterType::DataSection,
277 }
278 }
279}
280
281pub fn convert_external_plugin_adapter_data_to_string(
284 schema: &ExternalPluginAdapterSchema,
285 data_slice: &[u8],
286) -> String {
287 match schema {
288 ExternalPluginAdapterSchema::Binary => {
289 BASE64_STANDARD.encode(data_slice)
291 }
292 ExternalPluginAdapterSchema::Json => {
293 String::from_utf8_lossy(data_slice).to_string()
295 }
296 ExternalPluginAdapterSchema::MsgPack => {
297 match rmp_serde::decode::from_slice::<serde_json::Value>(data_slice) {
299 Ok(json_val) => serde_json::to_string(&json_val)
300 .unwrap_or_else(|_| BASE64_STANDARD.encode(data_slice)),
301 Err(_) => {
302 BASE64_STANDARD.encode(data_slice)
304 }
305 }
306 }
307 }
308}