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

Prevent duplicate external plugins #92

Merged
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
16 changes: 16 additions & 0 deletions clients/js/src/generated/errors/mplCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,22 @@ export class ExternalPluginNotFoundError extends ProgramError {
codeToErrorMap.set(0x1f, ExternalPluginNotFoundError);
nameToErrorMap.set('ExternalPluginNotFound', ExternalPluginNotFoundError);

/** ExternalPluginAlreadyExists: External Plugin already exists */
export class ExternalPluginAlreadyExistsError extends ProgramError {
override readonly name: string = 'ExternalPluginAlreadyExists';

readonly code: number = 0x20; // 32

constructor(program: Program, cause?: Error) {
super('External Plugin already exists', program, cause);
}
}
codeToErrorMap.set(0x20, ExternalPluginAlreadyExistsError);
nameToErrorMap.set(
'ExternalPluginAlreadyExists',
ExternalPluginAlreadyExistsError
);

/**
* Attempts to resolve a custom program error from the provided error code.
* @category Errors
Expand Down
3 changes: 3 additions & 0 deletions clients/rust/src/generated/errors/mpl_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ pub enum MplCoreError {
/// 31 (0x1F) - External Plugin not found
#[error("External Plugin not found")]
ExternalPluginNotFound,
/// 32 (0x20) - External Plugin already exists
#[error("External Plugin already exists")]
ExternalPluginAlreadyExists,
}

impl solana_program::program_error::PrintProgramError for MplCoreError {
Expand Down
77 changes: 77 additions & 0 deletions clients/rust/tests/add_external_plugins.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(feature = "test-sbf")]
pub mod setup;
use mpl_core::{
errors::MplCoreError,
instructions::AddExternalPluginV1Builder,
types::{
DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPlugin, ExternalPluginInitInfo,
Expand Down Expand Up @@ -260,3 +261,79 @@ async fn test_add_data_store() {
)
.await;
}

#[tokio::test]
async fn test_cannot_add_duplicate_external_plugin() {
let mut context = program_test().start_with_context().await;

let asset = Keypair::new();
create_asset(
&mut context,
CreateAssetHelperArgs {
owner: None,
payer: None,
asset: &asset,
data_state: None,
name: None,
uri: None,
authority: None,
update_authority: None,
collection: None,
plugins: vec![],
external_plugins: vec![],
},
)
.await
.unwrap();

let owner = context.payer.pubkey();
let update_authority = context.payer.pubkey();
assert_asset(
&mut context,
AssertAssetHelperArgs {
asset: asset.pubkey(),
owner,
update_authority: Some(UpdateAuthority::Address(update_authority)),
name: None,
uri: None,
plugins: vec![],
external_plugins: vec![],
},
)
.await;

let add_external_plugin_ix0 = AddExternalPluginV1Builder::new()
.asset(asset.pubkey())
.payer(context.payer.pubkey())
.init_info(ExternalPluginInitInfo::DataStore(DataStoreInitInfo {
init_plugin_authority: Some(PluginAuthority::UpdateAuthority),
data_authority: PluginAuthority::UpdateAuthority,
schema: None,
}))
.instruction();

let add_external_plugin_ix1 = AddExternalPluginV1Builder::new()
.asset(asset.pubkey())
.payer(context.payer.pubkey())
.init_info(ExternalPluginInitInfo::DataStore(DataStoreInitInfo {
init_plugin_authority: Some(PluginAuthority::UpdateAuthority),
data_authority: PluginAuthority::UpdateAuthority,
schema: None,
}))
.instruction();

let tx = Transaction::new_signed_with_payer(
&[add_external_plugin_ix0, add_external_plugin_ix1],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);

let error = context
.banks_client
.process_transaction(tx)
.await
.unwrap_err();

assert_custom_instruction_error!(1, error, MplCoreError::ExternalPluginAlreadyExists);
}
29 changes: 29 additions & 0 deletions clients/rust/tests/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,32 @@ pub async fn airdrop(
context.banks_client.process_transaction(tx).await.unwrap();
Ok(())
}

#[macro_export]
macro_rules! assert_custom_instruction_error {
($ix:expr, $error:expr, $matcher:pat) => {
match $error {
solana_program_test::BanksClientError::TransactionError(
solana_sdk::transaction::TransactionError::InstructionError(
$ix,
solana_sdk::instruction::InstructionError::Custom(x),
),
) => match num_traits::FromPrimitive::from_i32(x as i32) {
Some($matcher) => assert!(true),
Some(other) => {
assert!(
false,
"Expected another custom instruction error than '{:#?}'",
other
)
}
None => assert!(false, "Expected custom instruction error"),
},
err => assert!(
false,
"Expected custom instruction error but got '{:#?}'",
err
),
};
};
}
5 changes: 5 additions & 0 deletions idls/mpl_core.json
Original file line number Diff line number Diff line change
Expand Up @@ -4108,6 +4108,11 @@
"code": 31,
"name": "ExternalPluginNotFound",
"msg": "External Plugin not found"
},
{
"code": 32,
"name": "ExternalPluginAlreadyExists",
"msg": "External Plugin already exists"
}
],
"metadata": {
Expand Down
4 changes: 4 additions & 0 deletions programs/mpl-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ pub enum MplCoreError {
/// 31 - External Plugin not found
#[error("External Plugin not found")]
ExternalPluginNotFound,

/// 32 - External Plugin already exists
#[error("External Plugin already exists")]
ExternalPluginAlreadyExists,
}

impl PrintProgramError for MplCoreError {
Expand Down
16 changes: 16 additions & 0 deletions programs/mpl-core/src/plugins/external_plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,19 @@ impl ExternalPluginKey {
}
}
}

impl From<&ExternalPluginInitInfo> for ExternalPluginKey {
fn from(init_info: &ExternalPluginInitInfo) -> Self {
match init_info {
ExternalPluginInitInfo::LifecycleHook(init_info) => {
ExternalPluginKey::LifecycleHook(init_info.hooked_program)
}
ExternalPluginInitInfo::Oracle(init_info) => {
ExternalPluginKey::Oracle(init_info.base_address)
}
ExternalPluginInitInfo::DataStore(init_info) => {
ExternalPluginKey::DataStore(init_info.data_authority)
}
}
}
}
9 changes: 8 additions & 1 deletion programs/mpl-core/src/plugins/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>(
// You cannot add a duplicate plugin.
if plugin_registry
.registry
.iter_mut()
.iter()
.any(|record| record.plugin_type == plugin_type)
{
return Err(MplCoreError::PluginAlreadyExists.into());
Expand Down Expand Up @@ -318,6 +318,13 @@ pub fn initialize_external_plugin<'a, T: DataBlob + SolanaAccount>(
let header_offset = core.get_size();
let plugin_type = init_info.into();

// You cannot add a duplicate plugin.
for record in plugin_registry.external_registry.iter() {
if ExternalPluginKey::from_record(account, record)? == ExternalPluginKey::from(init_info) {
return Err(MplCoreError::ExternalPluginAlreadyExists.into());
}
}

let (authority, lifecycle_checks) = match &init_info {
ExternalPluginInitInfo::LifecycleHook(init_info) => {
(init_info.init_plugin_authority, &init_info.lifecycle_checks)
Expand Down