Skip to content

Commit

Permalink
Move asset and collection validations to util functions
Browse files Browse the repository at this point in the history
  • Loading branch information
danenbm committed Mar 8, 2024
1 parent 6a41cec commit 293db50
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 325 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 @@ -92,6 +92,10 @@ pub enum MplCoreError {
/// 20 - Missing update authority
#[error("Missing update authority")]
MissingUpdateAuthority,

/// 20 - Missing new owner
#[error("Missing new owner")]
MissingNewOwner,
}

impl PrintProgramError for MplCoreError {
Expand Down
18 changes: 14 additions & 4 deletions programs/mpl-core/src/plugins/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl Plugin {
pub(crate) fn validate_update(
plugin: &Plugin,
authority: &AccountInfo,
_unused: Option<&AccountInfo>,
authorities: &Authority,
) -> Result<ValidationResult, ProgramError> {
match plugin {
Expand Down Expand Up @@ -145,6 +146,7 @@ impl Plugin {
pub(crate) fn validate_burn(
plugin: &Plugin,
authority: &AccountInfo,
_unused: Option<&AccountInfo>,
authorities: &Authority,
) -> Result<ValidationResult, ProgramError> {
match plugin {
Expand All @@ -160,12 +162,13 @@ impl Plugin {
}

/// Route the validation of the transfer action to the appropriate plugin.
pub(crate) fn validate_transfer<'a>(
pub(crate) fn validate_transfer(
plugin: &Plugin,
authority: &AccountInfo<'a>,
new_owner: &AccountInfo<'a>,
authority: &AccountInfo,
new_owner: Option<&AccountInfo>,
authorities: &Authority,
) -> Result<ValidationResult, ProgramError> {
let new_owner = new_owner.ok_or(MplCoreError::MissingNewOwner)?;
match plugin {
Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()),
Plugin::Royalties(royalties) => {
Expand Down Expand Up @@ -383,12 +386,18 @@ pub(crate) fn validate_plugin_checks<'a, F>(
key: Key,
checks: &BTreeMap<PluginType, (Key, CheckResult, RegistryRecord)>,
authority: &AccountInfo<'a>,
new_owner: Option<&AccountInfo>,
asset: &AccountInfo<'a>,
collection: Option<&AccountInfo<'a>>,
validate_fp: F,
) -> Result<bool, ProgramError>
where
F: Fn(&Plugin, &AccountInfo<'a>, &Authority) -> Result<ValidationResult, ProgramError>,
F: Fn(
&Plugin,
&AccountInfo<'a>,
Option<&AccountInfo>,
&Authority,
) -> Result<ValidationResult, ProgramError>,
{
for (_, (check_key, check_result, registry_record)) in checks {
if *check_key == key
Expand All @@ -407,6 +416,7 @@ where
let result = validate_fp(
&Plugin::load(account, registry_record.offset)?,
authority,
new_owner,
&registry_record.authority,
)?;
match result {
Expand Down
142 changes: 22 additions & 120 deletions programs/mpl-core/src/processor/burn.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use std::collections::BTreeMap;

use borsh::{BorshDeserialize, BorshSerialize};
use mpl_utils::assert_signer;
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

use crate::{
error::MplCoreError,
instruction::accounts::{BurnAccounts, BurnCollectionAccounts},
plugins::{
validate_plugin_checks, CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult,
plugins::{Plugin, PluginType},
state::{Asset, Collection, Compressible, CompressionProof, Key},
utils::{
close_program_account, load_key, validate_asset_permissions,
validate_collection_permissions, verify_proof,
},
state::{Asset, Collection, Compressible, CompressionProof, Key, SolanaAccount},
utils::{close_program_account, fetch_core_data, load_key, verify_proof},
};

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
Expand Down Expand Up @@ -45,82 +44,18 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnArgs) -> Progr
asset.wrap()?;
}
Key::Asset => {
let (_, _, plugin_registry) = fetch_core_data::<Asset>(ctx.accounts.asset)?;

let mut checks: BTreeMap<PluginType, (Key, CheckResult, RegistryRecord)> =
BTreeMap::new();

// The asset approval overrides the collection approval.
let asset_approval = Asset::check_burn();
let core_check = match asset_approval {
CheckResult::None => (Key::Collection, Collection::check_burn()),
_ => (Key::Asset, asset_approval),
};

// Check the collection plugins first.
ctx.accounts.collection.and_then(|collection_info| {
fetch_core_data::<Collection>(collection_info)
.map(|(_, _, registry)| {
registry.map(|r| {
r.check_registry(Key::Collection, PluginType::check_burn, &mut checks);
r
})
})
.ok()?
});

// Next check the asset plugins. Plugins on the asset override the collection plugins,
// so we don't need to validate the collection plugins if the asset has a plugin.
if let Some(registry) = plugin_registry.as_ref() {
registry.check_registry(Key::Asset, PluginType::check_burn, &mut checks);
}

solana_program::msg!("checks: {:#?}", checks);

// Do the core validation.
let mut approved = matches!(
core_check,
(
Key::Asset | Key::Collection,
CheckResult::CanApprove | CheckResult::CanReject
)
) && {
(match core_check.0 {
Key::Collection => Collection::load(
ctx.accounts
.collection
.ok_or(MplCoreError::InvalidCollection)?,
0,
)?
.validate_burn(ctx.accounts.authority)?,
Key::Asset => {
Asset::load(ctx.accounts.asset, 0)?.validate_burn(ctx.accounts.authority)?
}
_ => return Err(MplCoreError::IncorrectAccount.into()),
}) == ValidationResult::Approved
};

approved = validate_plugin_checks(
Key::Collection,
&checks,
ctx.accounts.authority,
ctx.accounts.asset,
ctx.accounts.collection,
Box::new(Plugin::validate_burn),
)? || approved;

approved = validate_plugin_checks(
Key::Asset,
&checks,
let _ = validate_asset_permissions(
ctx.accounts.authority,
ctx.accounts.asset,
ctx.accounts.collection,
Box::new(Plugin::validate_burn),
)? || approved;

if !approved {
return Err(MplCoreError::InvalidAuthority.into());
}
None,
Asset::check_burn,
Collection::check_burn,
PluginType::check_burn,
Asset::validate_burn,
Collection::validate_burn,
Plugin::validate_burn,
)?;
}
_ => return Err(MplCoreError::IncorrectAccount.into()),
}
Expand All @@ -146,47 +81,14 @@ pub(crate) fn burn_collection<'a>(
assert_signer(payer)?;
}

let (collection, _, plugin_registry) = fetch_core_data::<Collection>(ctx.accounts.collection)?;

// let checks: [(Key, CheckResult); PluginType::COUNT + 2];

let mut approved = false;
match Collection::check_burn() {
CheckResult::CanApprove | CheckResult::CanReject => {
match collection.validate_burn(ctx.accounts.authority)? {
ValidationResult::Approved => {
approved = true;
}
ValidationResult::Rejected => return Err(MplCoreError::InvalidAuthority.into()),
ValidationResult::Pass => (),
}
}
CheckResult::None => (),
};

if let Some(plugin_registry) = plugin_registry {
for record in plugin_registry.registry {
if matches!(
PluginType::check_burn(&record.plugin_type),
CheckResult::CanApprove | CheckResult::CanReject
) {
let result = Plugin::validate_burn(
&Plugin::load(ctx.accounts.collection, record.offset)?,
ctx.accounts.authority,
&record.authority,
)?;
if result == ValidationResult::Rejected {
return Err(MplCoreError::InvalidAuthority.into());
} else if result == ValidationResult::Approved {
approved = true;
}
}
}
};

if !approved {
return Err(MplCoreError::InvalidAuthority.into());
}
let _ = validate_collection_permissions(
ctx.accounts.authority,
ctx.accounts.collection,
Collection::check_burn,
PluginType::check_burn,
Collection::validate_burn,
Plugin::validate_burn,
)?;

process_burn(ctx.accounts.collection, ctx.accounts.authority)
}
Expand Down
98 changes: 12 additions & 86 deletions programs/mpl-core/src/processor/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
use std::collections::BTreeMap;

use borsh::{BorshDeserialize, BorshSerialize};
use mpl_utils::assert_signer;
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

use crate::{
error::MplCoreError,
instruction::accounts::TransferAccounts,
plugins::{
validate_plugin_checks, CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult,
},
state::{
Asset, Authority, Collection, Compressible, CompressionProof, HashedAsset, Key,
SolanaAccount,
},
utils::{fetch_core_data, load_key, verify_proof},
plugins::{Plugin, PluginType},
state::{Asset, Collection, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount},
utils::{load_key, validate_asset_permissions, verify_proof},
};

#[repr(C)]
Expand Down Expand Up @@ -54,86 +47,19 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs)
HashedAsset::new(asset.hash()?).save(ctx.accounts.asset, 0)
}
Key::Asset => {
let (mut asset, _, plugin_registry) = fetch_core_data::<Asset>(ctx.accounts.asset)?;

let mut checks: BTreeMap<PluginType, (Key, CheckResult, RegistryRecord)> =
BTreeMap::new();

// The asset approval overrides the collection approval.
let asset_approval = Asset::check_transfer();
let core_check = match asset_approval {
CheckResult::None => (Key::Collection, Collection::check_transfer()),
_ => (Key::Asset, asset_approval),
};

// Check the collection plugins first.
if let Some(collection_info) = ctx.accounts.collection {
fetch_core_data::<Collection>(collection_info).map(|(_, _, registry)| {
registry.map(|r| {
r.check_registry(Key::Collection, PluginType::check_transfer, &mut checks);
r
})
})?;
}

// Next check the asset plugins. Plugins on the asset override the collection plugins,
// so we don't need to validate the collection plugins if the asset has a plugin.
if let Some(registry) = plugin_registry.as_ref() {
registry.check_registry(Key::Asset, PluginType::check_transfer, &mut checks);
}

solana_program::msg!("checks: {:#?}", checks);

// Do the core validation.
let mut approved = matches!(
core_check,
(
Key::Asset | Key::Collection,
CheckResult::CanApprove | CheckResult::CanReject
)
) && {
(match core_check.0 {
Key::Collection => Collection::load(
ctx.accounts
.collection
.ok_or(MplCoreError::InvalidCollection)?,
0,
)?
.validate_transfer()?,
Key::Asset => Asset::load(ctx.accounts.asset, 0)?
.validate_transfer(ctx.accounts.authority)?,
_ => return Err(MplCoreError::IncorrectAccount.into()),
}) == ValidationResult::Approved
};
solana_program::msg!("approved: {:#?}", approved);

let custom_args = |plugin: &Plugin,
authority_info: &AccountInfo<'a>,
authority: &Authority| {
Plugin::validate_transfer(plugin, authority_info, ctx.accounts.new_owner, authority)
};

approved = validate_plugin_checks(
Key::Collection,
&checks,
ctx.accounts.authority,
ctx.accounts.asset,
ctx.accounts.collection,
Box::new(custom_args),
)? || approved;

approved = validate_plugin_checks(
Key::Asset,
&checks,
let (mut asset, _, _) = validate_asset_permissions(
ctx.accounts.authority,
ctx.accounts.asset,
ctx.accounts.collection,
Box::new(custom_args),
)? || approved;
Some(ctx.accounts.new_owner),
Asset::check_transfer,
Collection::check_transfer,
PluginType::check_transfer,
Asset::validate_transfer,
Collection::validate_transfer,
Plugin::validate_transfer,
)?;

if !approved {
return Err(MplCoreError::InvalidAuthority.into());
}
asset.owner = *ctx.accounts.new_owner.key;
asset.save(ctx.accounts.asset, 0)
}
Expand Down
Loading

0 comments on commit 293db50

Please sign in to comment.