pub mod plugin;
pub use plugin::*;
pub mod advanced_types;
pub use advanced_types::*;
pub mod asset;
pub use asset::*;
pub mod collection;
pub use collection::*;
#[cfg(feature = "anchor")]
use anchor_lang::prelude::{
AnchorDeserialize as CrateDeserialize, AnchorSerialize as CrateSerialize,
};
use base64::prelude::*;
#[cfg(not(feature = "anchor"))]
use borsh::{BorshDeserialize as CrateDeserialize, BorshSerialize as CrateSerialize};
use modular_bitfield::{bitfield, specifiers::B29};
use num_traits::FromPrimitive;
use std::{cmp::Ordering, mem::size_of};
use crate::{
accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1, PluginRegistryV1},
errors::MplCoreError,
types::{
ExternalCheckResult, ExternalPluginAdapterKey, ExternalPluginAdapterSchema,
ExternalPluginAdapterType, Key, Plugin, PluginType, RegistryRecord, UpdateAuthority,
},
};
use solana_program::account_info::AccountInfo;
impl From<&Plugin> for PluginType {
fn from(plugin: &Plugin) -> Self {
match plugin {
Plugin::Royalties(_) => PluginType::Royalties,
Plugin::FreezeDelegate(_) => PluginType::FreezeDelegate,
Plugin::BurnDelegate(_) => PluginType::BurnDelegate,
Plugin::TransferDelegate(_) => PluginType::TransferDelegate,
Plugin::UpdateDelegate(_) => PluginType::UpdateDelegate,
Plugin::PermanentFreezeDelegate(_) => PluginType::PermanentFreezeDelegate,
Plugin::Attributes(_) => PluginType::Attributes,
Plugin::PermanentTransferDelegate(_) => PluginType::PermanentTransferDelegate,
Plugin::PermanentBurnDelegate(_) => PluginType::PermanentBurnDelegate,
Plugin::Edition(_) => PluginType::Edition,
Plugin::MasterEdition(_) => PluginType::MasterEdition,
Plugin::AddBlocker(_) => PluginType::AddBlocker,
Plugin::ImmutableMetadata(_) => PluginType::ImmutableMetadata,
Plugin::VerifiedCreators(_) => PluginType::VerifiedCreators,
Plugin::Autograph(_) => PluginType::Autograph,
}
}
}
impl BaseAssetV1 {
const BASE_LEN: usize = 1 + 32 + 1 + 4 + 4 + 1; }
impl BaseCollectionV1 {
const BASE_LEN: usize = 1 + 32 + 4 + 4 + 4 + 4; }
#[cfg(feature = "anchor")]
mod anchor_impl {
use super::*;
use anchor_lang::{
prelude::{Owner, Pubkey},
AccountDeserialize, AccountSerialize, Discriminator,
};
impl AccountDeserialize for BaseAssetV1 {
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
let base_asset = Self::from_bytes(buf)?;
Ok(base_asset)
}
}
impl AccountSerialize for BaseAssetV1 {}
impl Discriminator for BaseAssetV1 {
const DISCRIMINATOR: [u8; 8] = [0; 8];
}
impl Owner for BaseAssetV1 {
fn owner() -> Pubkey {
crate::ID
}
}
impl AccountDeserialize for BaseCollectionV1 {
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
let base_asset = Self::from_bytes(buf)?;
Ok(base_asset)
}
}
impl AccountSerialize for BaseCollectionV1 {}
impl Discriminator for BaseCollectionV1 {
const DISCRIMINATOR: [u8; 8] = [0; 8];
}
impl Owner for BaseCollectionV1 {
fn owner() -> Pubkey {
crate::ID
}
}
}
impl DataBlob for BaseAssetV1 {
fn len(&self) -> usize {
let mut size = BaseAssetV1::BASE_LEN + self.name.len() + self.uri.len();
if let UpdateAuthority::Address(_) | UpdateAuthority::Collection(_) = self.update_authority
{
size += 32;
}
if self.seq.is_some() {
size += size_of::<u64>();
}
size
}
}
impl SolanaAccount for BaseAssetV1 {
fn key() -> Key {
Key::AssetV1
}
}
impl DataBlob for BaseCollectionV1 {
fn len(&self) -> usize {
Self::BASE_LEN + self.name.len() + self.uri.len()
}
}
impl SolanaAccount for BaseCollectionV1 {
fn key() -> Key {
Key::CollectionV1
}
}
impl SolanaAccount for PluginRegistryV1 {
fn key() -> Key {
Key::PluginRegistryV1
}
}
impl SolanaAccount for PluginHeaderV1 {
fn key() -> Key {
Key::PluginHeaderV1
}
}
pub fn load_key(account: &AccountInfo, offset: usize) -> Result<Key, std::io::Error> {
let key = Key::from_u8((*account.data).borrow()[offset]).ok_or(std::io::Error::new(
std::io::ErrorKind::Other,
MplCoreError::DeserializationError.to_string(),
))?;
Ok(key)
}
#[allow(clippy::len_without_is_empty)]
pub trait DataBlob: CrateSerialize + CrateDeserialize {
fn len(&self) -> usize;
}
pub trait SolanaAccount: CrateSerialize + CrateDeserialize {
fn key() -> Key;
fn load(account: &AccountInfo, offset: usize) -> Result<Self, std::io::Error> {
let key = load_key(account, offset)?;
if key != Self::key() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
MplCoreError::DeserializationError.to_string(),
));
}
let mut bytes: &[u8] = &(*account.data).borrow()[offset..];
Self::deserialize(&mut bytes)
}
fn save(&self, account: &AccountInfo, offset: usize) -> Result<(), std::io::Error> {
borsh::to_writer(&mut account.data.borrow_mut()[offset..], self)
}
}
impl RegistryRecord {
pub fn compare_offsets(a: &RegistryRecord, b: &RegistryRecord) -> Ordering {
a.offset.cmp(&b.offset)
}
}
#[bitfield(bits = 32)]
#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)]
pub struct ExternalCheckResultBits {
pub can_listen: bool,
pub can_approve: bool,
pub can_reject: bool,
pub empty_bits: B29,
}
impl From<ExternalCheckResult> for ExternalCheckResultBits {
fn from(check_result: ExternalCheckResult) -> Self {
ExternalCheckResultBits::from_bytes(check_result.flags.to_le_bytes())
}
}
impl From<ExternalCheckResultBits> for ExternalCheckResult {
fn from(bits: ExternalCheckResultBits) -> Self {
ExternalCheckResult {
flags: u32::from_le_bytes(bits.into_bytes()),
}
}
}
impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType {
fn from(key: &ExternalPluginAdapterKey) -> Self {
match key {
ExternalPluginAdapterKey::LifecycleHook(_) => ExternalPluginAdapterType::LifecycleHook,
ExternalPluginAdapterKey::LinkedLifecycleHook(_) => {
ExternalPluginAdapterType::LinkedLifecycleHook
}
ExternalPluginAdapterKey::Oracle(_) => ExternalPluginAdapterType::Oracle,
ExternalPluginAdapterKey::AppData(_) => ExternalPluginAdapterType::AppData,
ExternalPluginAdapterKey::LinkedAppData(_) => ExternalPluginAdapterType::LinkedAppData,
ExternalPluginAdapterKey::DataSection(_) => ExternalPluginAdapterType::DataSection,
}
}
}
pub fn convert_external_plugin_adapter_data_to_string(
schema: &ExternalPluginAdapterSchema,
data_slice: &[u8],
) -> String {
match schema {
ExternalPluginAdapterSchema::Binary => {
BASE64_STANDARD.encode(data_slice)
}
ExternalPluginAdapterSchema::Json => {
String::from_utf8_lossy(data_slice).to_string()
}
ExternalPluginAdapterSchema::MsgPack => {
match rmp_serde::decode::from_slice::<serde_json::Value>(data_slice) {
Ok(json_val) => serde_json::to_string(&json_val)
.unwrap_or_else(|_| BASE64_STANDARD.encode(data_slice)),
Err(_) => {
BASE64_STANDARD.encode(data_slice)
}
}
}
}
}