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

Merge in main #175

Merged
merged 16 commits into from
Jul 30, 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
594 changes: 587 additions & 7 deletions clients/js/test/externalPlugins/oracle.test.ts

Large diffs are not rendered by default.

155 changes: 154 additions & 1 deletion clients/js/test/revokeAuthority.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
pluginAuthorityPair,
approveCollectionPluginAuthorityV1,
revokeCollectionPluginAuthorityV1,
updatePluginAuthority,
ownerPluginAuthority,
} from '../src';
import {
assertAsset,
Expand Down Expand Up @@ -95,7 +97,7 @@ test('it can remove the default authority from a plugin to make it immutable', a
});
});

test('it can remove a pubkey authority from a plugin if that pubkey is the signer authority', async (t) => {
test('it can remove a pubkey authority from an owner-managed plugin if that pubkey is the signer authority', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const pubkeyAuth = await generateSignerWithSol(umi);
Expand Down Expand Up @@ -147,6 +149,157 @@ test('it can remove a pubkey authority from a plugin if that pubkey is the signe
});
});

test('it can remove an update authority from an owner-managed plugin if that pubkey is the signer authority', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const owner = generateSigner(umi);

const asset = await createAsset(umi, {
owner: owner.publicKey,
plugins: [
pluginAuthorityPair({ type: 'FreezeDelegate', data: { frozen: false } }),
],
});

await approvePluginAuthorityV1(umi, {
asset: asset.publicKey,
authority: owner,
pluginType: PluginType.FreezeDelegate,
newAuthority: updatePluginAuthority(),
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: owner.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
freezeDelegate: {
authority: { type: 'UpdateAuthority' },
frozen: false,
},
});

await revokePluginAuthorityV1(umi, {
asset: asset.publicKey,
pluginType: PluginType.FreezeDelegate,
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: owner.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
freezeDelegate: {
authority: {
type: 'Owner',
},
frozen: false,
},
});
});

test('it can remove a pubkey authority from an authority-managed plugin if that pubkey is the signer authority', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const pubkeyAuth = await generateSignerWithSol(umi);

const asset = await createAsset(umi, {
plugins: [
pluginAuthorityPair({ type: 'Attributes', data: { attributeList: [] } }),
],
});

await approvePluginAuthorityV1(umi, {
asset: asset.publicKey,
pluginType: PluginType.Attributes,
newAuthority: addressPluginAuthority(pubkeyAuth.publicKey),
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: umi.identity.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
attributes: {
authority: {
type: 'Address',
address: pubkeyAuth.publicKey,
},
attributeList: [],
},
});

const umi2 = await createUmi();

await revokePluginAuthorityV1(umi2, {
payer: umi2.identity,
asset: asset.publicKey,
authority: pubkeyAuth,
pluginType: PluginType.Attributes,
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: umi.identity.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
attributes: {
authority: {
type: 'UpdateAuthority',
},
attributeList: [],
},
});
});

test('it can remove an owner authority from an authority-managed plugin if that pubkey is the signer authority', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const owner = generateSigner(umi);

const asset = await createAsset(umi, {
owner: owner.publicKey,
plugins: [
pluginAuthorityPair({ type: 'Attributes', data: { attributeList: [] } }),
],
});

await approvePluginAuthorityV1(umi, {
asset: asset.publicKey,
pluginType: PluginType.Attributes,
newAuthority: ownerPluginAuthority(),
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: owner.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
attributes: {
authority: {
type: 'Owner',
},
attributeList: [],
},
});

const umi2 = await createUmi();

await revokePluginAuthorityV1(umi2, {
payer: umi2.identity,
asset: asset.publicKey,
authority: owner,
pluginType: PluginType.Attributes,
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
asset: asset.publicKey,
owner: owner.publicKey,
updateAuthority: { type: 'Address', address: umi.identity.publicKey },
attributes: {
authority: {
type: 'UpdateAuthority',
},
attributeList: [],
},
});
});

test('it cannot remove a none authority from a plugin', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
Expand Down
18 changes: 17 additions & 1 deletion clients/rust/src/hooked/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use num_traits::FromPrimitive;
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};

use crate::{
accounts::{BaseAssetV1, PluginHeaderV1},
accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1},
errors::MplCoreError,
types::{
ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalPluginAdapterType, LinkedDataKey,
Expand Down Expand Up @@ -84,6 +84,22 @@ pub fn fetch_plugin<T: DataBlob + SolanaAccount, U: CrateDeserialize>(
))
}

/// Fetch the plugin on an asset.
pub fn fetch_asset_plugin<U: CrateDeserialize>(
account: &AccountInfo,
plugin_type: PluginType,
) -> Result<(PluginAuthority, U, usize), std::io::Error> {
fetch_plugin::<BaseAssetV1, U>(account, plugin_type)
}

/// Fetch the plugin on a collection.
pub fn fetch_collection_plugin<U: CrateDeserialize>(
account: &AccountInfo,
plugin_type: PluginType,
) -> Result<(PluginAuthority, U, usize), std::io::Error> {
fetch_plugin::<BaseCollectionV1, U>(account, plugin_type)
}

/// Fetch the plugin registry, dropping any unknown plugins (i.e. `PluginType`s that are too new
/// for this client to know about).
pub fn fetch_plugins(account_data: &[u8]) -> Result<Vec<RegistryRecord>, std::io::Error> {
Expand Down
20 changes: 1 addition & 19 deletions programs/mpl-core/src/plugins/autograph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashSet};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{program_error::ProgramError, pubkey::Pubkey};

use crate::{error::MplCoreError, plugins::PluginType, state::Authority};
use crate::error::MplCoreError;

use super::{
abstain, approve, Plugin, PluginValidation, PluginValidationContext, ValidationResult,
Expand Down Expand Up @@ -120,22 +120,4 @@ impl PluginValidation for Autograph {
_ => abstain!(),
}
}

/// Validate the revoke plugin authority lifecycle action.
fn validate_revoke_plugin_authority(
&self,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
if ctx.self_authority
== &(Authority::Address {
address: *ctx.authority_info.key,
})
&& ctx.target_plugin.is_some()
&& PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Autograph
{
approve!()
} else {
abstain!()
}
}
}
20 changes: 1 addition & 19 deletions programs/mpl-core/src/plugins/burn_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::program_error::ProgramError;

use crate::{
plugins::{abstain, approve, PluginType},
plugins::{abstain, approve},
state::{Authority, DataBlob},
};

Expand Down Expand Up @@ -52,22 +52,4 @@ impl PluginValidation for BurnDelegate {
abstain!()
}
}

/// Validate the revoke plugin authority lifecycle action.
fn validate_revoke_plugin_authority(
&self,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
if ctx.self_authority
== &(Authority::Address {
address: *ctx.authority_info.key,
})
&& ctx.target_plugin.is_some()
&& PluginType::from(ctx.target_plugin.unwrap()) == PluginType::BurnDelegate
{
approve!()
} else {
abstain!()
}
}
}
24 changes: 1 addition & 23 deletions programs/mpl-core/src/plugins/edition.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::program_error::ProgramError;

use crate::{plugins::approve, state::Authority};

use super::{
abstain, reject, Plugin, PluginType, PluginValidation, PluginValidationContext,
ValidationResult,
};
use super::{abstain, reject, Plugin, PluginValidation, PluginValidationContext, ValidationResult};

/// The edition plugin allows the creator to set an edition number on the asset
/// The default authority for this plugin is the creator.
Expand Down Expand Up @@ -45,21 +40,4 @@ impl PluginValidation for Edition {
_ => abstain!(),
}
}
/// Validate the revoke plugin authority lifecycle action.
fn validate_revoke_plugin_authority(
&self,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
if ctx.self_authority
== &(Authority::Address {
address: *ctx.authority_info.key,
})
&& ctx.target_plugin.is_some()
&& PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Edition
{
approve!()
} else {
abstain!()
}
}
}
56 changes: 56 additions & 0 deletions programs/mpl-core/src/plugins/external_plugin_adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use strum::EnumCount;

use crate::{
error::MplCoreError,
plugins::lifecycle::{approve, reject},
state::{AssetV1, SolanaAccount},
};

Expand Down Expand Up @@ -271,6 +272,61 @@ impl ExternalPluginAdapter {
}
}

/// Validate the add external plugin adapter lifecycle event.
pub(crate) fn validate_update_external_plugin_adapter(
external_plugin_adapter: &ExternalPluginAdapter,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
let resolved_authorities = ctx
.resolved_authorities
.ok_or(MplCoreError::InvalidAuthority)?;
let base_result = if resolved_authorities.contains(ctx.self_authority) {
solana_program::msg!("Base: Approved");
ValidationResult::Approved
} else {
ValidationResult::Pass
};

let result = match external_plugin_adapter {
ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => {
lifecycle_hook.validate_update_external_plugin_adapter(ctx)
}
ExternalPluginAdapter::Oracle(oracle) => {
oracle.validate_update_external_plugin_adapter(ctx)
}
ExternalPluginAdapter::AppData(app_data) => {
app_data.validate_update_external_plugin_adapter(ctx)
}
ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => {
lifecycle_hook.validate_update_external_plugin_adapter(ctx)
}
ExternalPluginAdapter::LinkedAppData(app_data) => {
app_data.validate_update_external_plugin_adapter(ctx)
}
// Here we block the update of a DataSection plugin because this is only done internally.
ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Rejected),
}?;

match (&base_result, &result) {
(ValidationResult::Approved, ValidationResult::Approved) => {
approve!()
}
(ValidationResult::Approved, ValidationResult::Rejected) => {
reject!()
}
(ValidationResult::Rejected, ValidationResult::Approved) => {
reject!()
}
(ValidationResult::Rejected, ValidationResult::Rejected) => {
reject!()
}
(ValidationResult::Pass, _) => Ok(result),
(ValidationResult::ForceApproved, _) => unreachable!(),
(_, ValidationResult::Pass) => Ok(base_result),
(_, ValidationResult::ForceApproved) => unreachable!(),
}
}

/// Load and deserialize a plugin from an offset in the account.
pub fn load(account: &AccountInfo, offset: usize) -> Result<Self, ProgramError> {
let mut bytes: &[u8] = &(*account.data).borrow()[offset..];
Expand Down
Loading
Loading