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