Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust client improvements #155

Merged
merged 4 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clients/rust/src/hooked/advanced_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ pub struct Asset {
pub struct Collection {
pub base: BaseCollectionV1,
pub plugin_list: PluginsList,
pub external_plugin_adapter_list: ExternalPluginAdaptersList,
pub plugin_header: Option<PluginHeaderV1>,
}

Expand Down
72 changes: 39 additions & 33 deletions clients/rust/src/hooked/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,63 @@ use borsh::BorshSerialize;
use crate::{
accounts::{BaseAssetV1, PluginHeaderV1},
registry_records_to_external_plugin_adapter_list, registry_records_to_plugin_list, Asset,
PluginRegistryV1Safe,
ExternalPluginAdaptersList, PluginRegistryV1Safe, PluginsList,
};

impl Asset {
pub fn deserialize(data: &[u8]) -> Result<Self, std::io::Error> {
pub fn deserialize(data: &[u8]) -> Result<Box<Self>, std::io::Error> {
let base = BaseAssetV1::from_bytes(data)?;
let base_data = base.try_to_vec()?;
let (plugin_header, plugin_list, external_plugin_adapter_list) = if base_data.len()
!= data.len()
{
let plugin_header = PluginHeaderV1::from_bytes(&data[base_data.len()..])?;
let plugin_registry = PluginRegistryV1Safe::from_bytes(
&data[plugin_header.plugin_registry_offset as usize..],
)?;

let plugin_list = registry_records_to_plugin_list(&plugin_registry.registry, data)?;
let external_plugin_adapter_list = registry_records_to_external_plugin_adapter_list(
&plugin_registry.external_registry,
data,
)?;

(
Some(plugin_header),
Some(plugin_list),
Some(external_plugin_adapter_list),
)
} else {
(None, None, None)
};

Ok(Self {

if base_data.len() != data.len() {
return Self::deserialize_with_plugins(data, base, base_data);
}

Ok(Box::new(Self {
base,
plugin_list: PluginsList::default(),
external_plugin_adapter_list: ExternalPluginAdaptersList::default(),
plugin_header: None,
}))
}

fn deserialize_with_plugins(
data: &[u8],
base: BaseAssetV1,
base_data: Vec<u8>,
) -> Result<Box<Self>, std::io::Error> {
let plugin_header = PluginHeaderV1::from_bytes(&data[base_data.len()..])?;
let plugin_registry = PluginRegistryV1Safe::from_bytes(
&data[plugin_header.plugin_registry_offset as usize..],
)?;

let plugin_list = registry_records_to_plugin_list(&plugin_registry.registry, data)?;
let external_plugin_adapter_list = registry_records_to_external_plugin_adapter_list(
&plugin_registry.external_registry,
data,
)?;

Ok(Box::new(Self {
base,
plugin_list: plugin_list.unwrap_or_default(),
external_plugin_adapter_list: external_plugin_adapter_list.unwrap_or_default(),
plugin_header,
})
plugin_list,
external_plugin_adapter_list,
plugin_header: Some(plugin_header),
}))
}

#[inline(always)]
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> {
pub fn from_bytes(data: &[u8]) -> Result<Box<Self>, std::io::Error> {
Self::deserialize(data)
}
}

impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Asset {
impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Box<Asset> {
type Error = std::io::Error;

fn try_from(
account_info: &solana_program::account_info::AccountInfo<'a>,
) -> Result<Self, Self::Error> {
let data: &[u8] = &(*account_info.data).borrow();
Self::deserialize(data)
Asset::deserialize(data)
}
}
56 changes: 37 additions & 19 deletions clients/rust/src/hooked/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,64 @@ use borsh::BorshSerialize;

use crate::{
accounts::{BaseCollectionV1, PluginHeaderV1},
registry_records_to_plugin_list, Collection, PluginRegistryV1Safe,
registry_records_to_external_plugin_adapter_list, registry_records_to_plugin_list, Collection,
ExternalPluginAdaptersList, PluginRegistryV1Safe, PluginsList,
};

impl Collection {
pub fn deserialize(data: &[u8]) -> Result<Self, std::io::Error> {
pub fn deserialize(data: &[u8]) -> Result<Box<Self>, std::io::Error> {
let base = BaseCollectionV1::from_bytes(data)?;
let base_data = base.try_to_vec()?;
let (plugin_header, plugin_list) = if base_data.len() != data.len() {
let plugin_header = PluginHeaderV1::from_bytes(&data[base_data.len()..])?;
let plugin_registry = PluginRegistryV1Safe::from_bytes(
&data[plugin_header.plugin_registry_offset as usize..],
)?;

let plugin_list = registry_records_to_plugin_list(&plugin_registry.registry, data)?;
if base_data.len() != data.len() {
return Self::deserialize_with_plugins(data, base, base_data);
}

(Some(plugin_header), Some(plugin_list))
} else {
(None, None)
};
Ok(Box::new(Self {
base,
plugin_list: PluginsList::default(),
external_plugin_adapter_list: ExternalPluginAdaptersList::default(),
plugin_header: None,
}))
}

fn deserialize_with_plugins(
data: &[u8],
base: BaseCollectionV1,
base_data: Vec<u8>,
) -> Result<Box<Self>, std::io::Error> {
let plugin_header = PluginHeaderV1::from_bytes(&data[base_data.len()..])?;
let plugin_registry = PluginRegistryV1Safe::from_bytes(
&data[plugin_header.plugin_registry_offset as usize..],
)?;

Ok(Self {
let plugin_list = registry_records_to_plugin_list(&plugin_registry.registry, data)?;
let external_plugin_adapter_list = registry_records_to_external_plugin_adapter_list(
&plugin_registry.external_registry,
data,
)?;

Ok(Box::new(Self {
base,
plugin_list: plugin_list.unwrap_or_default(),
plugin_header,
})
plugin_list,
external_plugin_adapter_list,
plugin_header: Some(plugin_header),
}))
}

#[inline(always)]
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> {
pub fn from_bytes(data: &[u8]) -> Result<Box<Self>, std::io::Error> {
Self::deserialize(data)
}
}

impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Collection {
impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Box<Collection> {
type Error = std::io::Error;

fn try_from(
account_info: &solana_program::account_info::AccountInfo<'a>,
) -> Result<Self, Self::Error> {
let data: &[u8] = &(*account_info.data).borrow();
Self::deserialize(data)
Collection::deserialize(data)
}
}
89 changes: 87 additions & 2 deletions clients/rust/src/hooked/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use collection::*;
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};
Expand All @@ -24,8 +25,8 @@ use crate::{
accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1, PluginRegistryV1},
errors::MplCoreError,
types::{
ExternalCheckResult, ExternalPluginAdapterKey, ExternalPluginAdapterType, Key, Plugin,
PluginType, RegistryRecord,
ExternalCheckResult, ExternalPluginAdapterKey, ExternalPluginAdapterSchema,
ExternalPluginAdapterType, Key, Plugin, PluginType, RegistryRecord,
},
};
use solana_program::account_info::AccountInfo;
Expand Down Expand Up @@ -62,6 +63,61 @@ impl BaseCollectionV1 {
pub const BASE_LENGTH: usize = 1 + 32 + 4 + 4 + 4 + 4;
}

/// Anchor implementations that enable using `Account<BaseAssetV1>` and `Account<BaseCollectionV1>`
/// in Anchor programs.
#[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)
}
}

// Not used as an Anchor program using Account<BaseAssetV1> would not have permission to
// reserialize the account as it's owned by mpl-core.
impl AccountSerialize for BaseAssetV1 {}

// Not used but needed for Anchor.
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)
}
}

// Not used as an Anchor program using Account<BaseCollectionV1> would not have permission to
// reserialize the account as it's owned by mpl-core.
impl AccountSerialize for BaseCollectionV1 {}

// Not used but needed for Anchor.
impl Discriminator for BaseCollectionV1 {
const DISCRIMINATOR: [u8; 8] = [0; 8];
}

impl Owner for BaseCollectionV1 {
fn owner() -> Pubkey {
crate::ID
}
}
}

impl DataBlob for BaseAssetV1 {
fn get_initial_size() -> usize {
BaseAssetV1::BASE_LENGTH
Expand Down Expand Up @@ -199,3 +255,32 @@ impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType {
}
}
}

/// Use `ExternalPluginAdapterSchema` to convert data to string. If schema is binary or there is
/// an error, then use Base64 encoding.
pub fn convert_external_plugin_adapter_data_to_string(
schema: &ExternalPluginAdapterSchema,
data_slice: &[u8],
) -> String {
match schema {
ExternalPluginAdapterSchema::Binary => {
// Encode the binary data as a base64 string.
BASE64_STANDARD.encode(data_slice)
}
ExternalPluginAdapterSchema::Json => {
// Convert the byte slice to a UTF-8 string, replacing invalid characterse.
String::from_utf8_lossy(data_slice).to_string()
}
ExternalPluginAdapterSchema::MsgPack => {
// Attempt to decode `MsgPack` to serde_json::Value and serialize to JSON string.
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(_) => {
// Failed to decode `MsgPack`, fallback to base64.
BASE64_STANDARD.encode(data_slice)
}
}
}
}
}
Loading
Loading