Skip to content

Commit

Permalink
Add helpers for compress/decompress, fix spl-noop wrap and compressed…
Browse files Browse the repository at this point in the history
… transfer
  • Loading branch information
danenbm committed Mar 8, 2024
1 parent bf046e0 commit e0c7f7a
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 154 deletions.
4 changes: 4 additions & 0 deletions programs/mpl-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ pub enum MplCoreError {
/// 20 - Missing new owner
#[error("Missing new owner")]
MissingNewOwner,

/// 20 - Missing system program
#[error("Missing system program")]
MissingSystemProgram,
}

impl PrintProgramError for MplCoreError {
Expand Down
3 changes: 2 additions & 1 deletion programs/mpl-core/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ pub(crate) enum MplAssetInstruction {
#[account(2, signer, name="authority", desc = "The owner or delegate of the asset")]
#[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")]
#[account(4, name="new_owner", desc = "The new owner to which to transfer the asset")]
#[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")]
#[account(5, optional, name="system_program", desc = "The system program")]
#[account(6, optional, name="log_wrapper", desc = "The SPL Noop Program")]
Transfer(TransferArgs),

/// Update an mpl-core.
Expand Down
55 changes: 8 additions & 47 deletions programs/mpl-core/src/processor/compress.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
use borsh::{BorshDeserialize, BorshSerialize};
use mpl_utils::assert_signer;
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy,
};
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

use crate::{
error::MplCoreError,
instruction::accounts::CompressAccounts,
plugins::{Plugin, PluginType, RegistryRecord},
state::{
Asset, Collection, Compressible, HashablePluginSchema, HashedAsset, HashedAssetSchema, Key,
},
utils::{fetch_core_data, load_key, resize_or_reallocate_account, validate_asset_permissions},
plugins::{Plugin, PluginType},
state::{Asset, Collection, Key, Wrappable},
utils::{compress_into_account_space, fetch_core_data, load_key, validate_asset_permissions},
};

#[repr(C)]
Expand Down Expand Up @@ -48,50 +44,15 @@ pub(crate) fn compress<'a>(accounts: &'a [AccountInfo<'a>], _args: CompressArgs)
Plugin::validate_compress,
)?;

let mut plugin_hashes = vec![];
if let Some(plugin_registry) = plugin_registry {
let mut registry_records = plugin_registry.registry;

// It should already be sorted but we just want to make sure.
registry_records.sort_by(RegistryRecord::compare_offsets);

for (i, record) in registry_records.into_iter().enumerate() {
let plugin = Plugin::deserialize(
&mut &(*ctx.accounts.asset.data).borrow()[record.offset..],
)?;

let hashable_plugin_schema = HashablePluginSchema {
index: i,
authority: record.authority,
plugin,
};

let plugin_hash = hashable_plugin_schema.hash()?;
plugin_hashes.push(plugin_hash);
}
}

let asset_hash = asset.hash()?;
let hashed_asset_schema = HashedAssetSchema {
asset_hash,
plugin_hashes,
};

let hashed_asset = HashedAsset::new(hashed_asset_schema.hash()?);
let serialized_data = hashed_asset.try_to_vec()?;

resize_or_reallocate_account(
let compression_proof = compress_into_account_space(
asset,
plugin_registry,
ctx.accounts.asset,
payer,
ctx.accounts.system_program,
serialized_data.len(),
)?;

sol_memcpy(
&mut ctx.accounts.asset.try_borrow_mut_data()?,
&serialized_data,
serialized_data.len(),
);
compression_proof.wrap()?;
}
Key::HashedAsset => return Err(MplCoreError::AlreadyCompressed.into()),
_ => return Err(MplCoreError::IncorrectAccount.into()),
Expand Down
45 changes: 11 additions & 34 deletions programs/mpl-core/src/processor/decompress.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use borsh::{BorshDeserialize, BorshSerialize};
use mpl_utils::assert_signer;
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy,
system_program,
};
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, system_program};

use crate::{
error::MplCoreError,
instruction::accounts::DecompressAccounts,
plugins::{create_meta_idempotent, initialize_plugin, Plugin, PluginType},
plugins::{Plugin, PluginType},
state::{Asset, Collection, CompressionProof, Key},
utils::{load_key, resize_or_reallocate_account, validate_asset_permissions, verify_proof},
utils::{
load_key, rebuild_account_state_from_proof_data, validate_asset_permissions, verify_proof,
},
};

#[repr(C)]
Expand Down Expand Up @@ -41,41 +40,19 @@ pub(crate) fn decompress<'a>(

match load_key(ctx.accounts.asset, 0)? {
Key::HashedAsset => {
// Verify the proof and rebuild Asset struct in account.
// Verify the proof and rebuild Asset struct in account space.
let (asset, plugins) = verify_proof(ctx.accounts.asset, &args.compression_proof)?;

let serialized_data = asset.try_to_vec()?;
resize_or_reallocate_account(
// Use the data from the compression proof to rebuild the account.
rebuild_account_state_from_proof_data(
asset,
plugins,
ctx.accounts.asset,
payer,
ctx.accounts.system_program,
serialized_data.len(),
)?;

sol_memcpy(
&mut ctx.accounts.asset.try_borrow_mut_data()?,
&serialized_data,
serialized_data.len(),
);

// Add the plugins.
if !plugins.is_empty() {
create_meta_idempotent(ctx.accounts.asset, payer, ctx.accounts.system_program)?;

for plugin in plugins {
initialize_plugin::<Asset>(
&plugin.plugin,
&plugin.authority,
ctx.accounts.asset,
payer,
ctx.accounts.system_program,
)?;
}
}

// Validate permissions.
//let (asset, _, plugin_registry) = fetch_core_data::<Asset>(ctx.accounts.asset)?;

// Validate asset permissions.
let _ = validate_asset_permissions(
ctx.accounts.authority,
ctx.accounts.asset,
Expand Down
60 changes: 48 additions & 12 deletions programs/mpl-core/src/processor/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ use crate::{
error::MplCoreError,
instruction::accounts::TransferAccounts,
plugins::{Plugin, PluginType},
state::{Asset, Collection, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount},
utils::{load_key, validate_asset_permissions, verify_proof},
state::{Asset, Collection, CompressionProof, Key, SolanaAccount, Wrappable},
utils::{
compress_into_account_space, load_key, rebuild_account_state_from_proof_data,
validate_asset_permissions, verify_proof,
},
};

#[repr(C)]
Expand All @@ -22,29 +25,61 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs)

// Guards.
assert_signer(ctx.accounts.authority)?;
if let Some(payer) = ctx.accounts.payer {
let payer = if let Some(payer) = ctx.accounts.payer {
assert_signer(payer)?;
}
payer
} else {
ctx.accounts.authority
};

match load_key(ctx.accounts.asset, 0)? {
Key::HashedAsset => {
let compression_proof = args
.compression_proof
.ok_or(MplCoreError::MissingCompressionProof)?;
let (mut asset, _) = verify_proof(ctx.accounts.asset, &compression_proof)?;

if ctx.accounts.authority.key != &asset.owner {
return Err(MplCoreError::InvalidAuthority.into());
}
let system_program = ctx
.accounts
.system_program
.ok_or(MplCoreError::MissingSystemProgram)?;

// TODO: Check delegates in compressed case.
// Verify the proof and rebuild Asset struct in account space.
let (mut asset, plugins) = verify_proof(ctx.accounts.asset, &compression_proof)?;

// Set the new owner.
asset.owner = *ctx.accounts.new_owner.key;

asset.wrap()?;
// Use the data from the compression proof to rebuild the account.
rebuild_account_state_from_proof_data(
asset,
plugins,
ctx.accounts.asset,
payer,
system_program,
)?;

let (asset, _, plugin_registry) = validate_asset_permissions(
ctx.accounts.authority,
ctx.accounts.asset,
ctx.accounts.collection,
Some(ctx.accounts.new_owner),
Asset::check_transfer,
Collection::check_transfer,
PluginType::check_transfer,
Asset::validate_transfer,
Collection::validate_transfer,
Plugin::validate_transfer,
)?;

let compression_proof = compress_into_account_space(
asset,
plugin_registry,
ctx.accounts.asset,
payer,
system_program,
)?;

// Make a new hashed asset with updated owner and save to account.
HashedAsset::new(asset.hash()?).save(ctx.accounts.asset, 0)
compression_proof.wrap()
}
Key::Asset => {
let (mut asset, _, _) = validate_asset_permissions(
Expand All @@ -60,6 +95,7 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs)
Plugin::validate_transfer,
)?;

// Set the new owner.
asset.owner = *ctx.accounts.new_owner.key;
asset.save(ctx.accounts.asset, 0)
}
Expand Down
34 changes: 34 additions & 0 deletions programs/mpl-core/src/state/compression_proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;

use crate::state::{Asset, HashablePluginSchema, UpdateAuthority, Wrappable};

/// A simple struct to store the compression proof of an asset.
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
pub struct CompressionProof {
/// The owner of the asset.
pub owner: Pubkey, //32
/// The update authority of the asset.
pub update_authority: UpdateAuthority, //33
/// The name of the asset.
pub name: String, //4
/// The URI of the asset that points to the off-chain data.
pub uri: String, //4
/// The plugins for the asset.
pub plugins: Vec<HashablePluginSchema>, //4
}

impl CompressionProof {
pub fn new(asset: Asset, plugins: Vec<HashablePluginSchema>) -> Self {
Self {
owner: asset.owner,
update_authority: asset.update_authority,
name: asset.name,
uri: asset.uri,
plugins,
}
}
}

impl Wrappable for CompressionProof {}
30 changes: 30 additions & 0 deletions programs/mpl-core/src/state/hashable_plugin_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use borsh::{BorshDeserialize, BorshSerialize};
use std::cmp::Ordering;

use crate::{
plugins::Plugin,
state::{Authority, Compressible},
};

/// A type that stores a plugin's authorities and deserialized data into a
/// schema that will be later hashed into a hashed asset. Also used in
/// `CompressionProof`.
#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
pub struct HashablePluginSchema {
/// This is the order the plugins are stored in the account, allowing us
/// to keep track of their order in the hashing.
pub index: usize,
/// The authorities who have permission to utilize a plugin.
pub authority: Authority,
/// The deserialized plugin.
pub plugin: Plugin,
}

impl HashablePluginSchema {
/// Associated function for sorting `RegistryRecords` by offset.
pub fn compare_indeces(a: &HashablePluginSchema, b: &HashablePluginSchema) -> Ordering {
a.index.cmp(&b.index)
}
}

impl Compressible for HashablePluginSchema {}
28 changes: 1 addition & 27 deletions programs/mpl-core/src/state/hashed_asset_schema.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
use borsh::{BorshDeserialize, BorshSerialize};
use std::cmp::Ordering;

use crate::{
plugins::Plugin,
state::{Authority, Compressible},
};

/// A type that stores a plugin's authorities and deserialized data into a
/// schema that will be later hashed into a hashed asset.
#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
pub struct HashablePluginSchema {
/// This is the order the plugins are stored in the account, allowing us
/// to keep track of their order in the hashing.
pub index: usize,
/// The authorities who have permission to utilize a plugin.
pub authority: Authority,
/// The deserialized plugin.
pub plugin: Plugin,
}

impl HashablePluginSchema {
/// Associated function for sorting `RegistryRecords` by offset.
pub fn compare_indeces(a: &HashablePluginSchema, b: &HashablePluginSchema) -> Ordering {
a.index.cmp(&b.index)
}
}

impl Compressible for HashablePluginSchema {}
use crate::state::Compressible;

/// The hashed asset schema is a schema that contains a hash of the asset and a vec of plugin hashes.
#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
Expand Down
Loading

0 comments on commit e0c7f7a

Please sign in to comment.