Skip to content

Commit

Permalink
Merge pull request #176 from metaplex-foundation/danenbm/merge-main-a…
Browse files Browse the repository at this point in the history
…gain

Merge latest main into staging
  • Loading branch information
danenbm authored Jul 30, 2024
2 parents c270a8f + a5aca91 commit 3ce1104
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 98 deletions.
2 changes: 1 addition & 1 deletion clients/js/test/createCollection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ test('it cannot create a new asset with a collection if it is not the collection
collection: collection.publicKey,
});

await t.throwsAsync(result, { name: 'InvalidAuthority' });
await t.throwsAsync(result, { name: 'NoApprovals' });
});

test('it cannot create a collection with an owner managed plugin', async (t) => {
Expand Down
161 changes: 161 additions & 0 deletions clients/js/test/externalPlugins/oracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
fetchAssetV1,
ExternalValidationResult,
ruleSet,
fetchCollection,
} from '../../src';

const createUmi = async () =>
Expand Down Expand Up @@ -3615,3 +3616,163 @@ test('it cannot update oracle on collection using update authority when differen
],
});
});

test('it can create an oracle on a collection with create set to reject', async (t) => {
const umi = await createUmi();
const oracleSigner = generateSigner(umi);
await fixedAccountInit(umi, {
signer: umi.identity,
account: oracleSigner,
args: {
oracleData: {
__kind: 'V1',
create: ExternalValidationResult.Rejected,
transfer: ExternalValidationResult.Pass,
burn: ExternalValidationResult.Pass,
update: ExternalValidationResult.Pass,
},
},
}).sendAndConfirm(umi);

const collection = generateSigner(umi);
await createCollection(umi, {
collection,
name: 'Test name',
uri: 'https://example.com',
plugins: [
{
type: 'Oracle',
resultsOffset: {
type: 'Anchor',
},
lifecycleChecks: {
create: [CheckResult.CAN_REJECT],
},
baseAddress: oracleSigner.publicKey,
},
],
}).sendAndConfirm(umi);

await assertCollection(t, umi, {
uri: 'https://example.com',
name: 'Test name',
collection: collection.publicKey,
updateAuthority: umi.identity.publicKey,
oracles: [
{
authority: {
type: 'UpdateAuthority',
},
type: 'Oracle',
resultsOffset: {
type: 'Anchor',
},
lifecycleChecks: {
create: [CheckResult.CAN_REJECT],
},
baseAddress: oracleSigner.publicKey,
baseAddressConfig: undefined,
},
],
});
});

test('it can use fixed address oracle on a collection to deny create', async (t) => {
const umi = await createUmi();
const account = generateSigner(umi);

// write to example program oracle account
await fixedAccountInit(umi, {
account,
signer: umi.identity,
payer: umi.identity,
args: {
oracleData: {
__kind: 'V1',
create: ExternalValidationResult.Rejected,
update: ExternalValidationResult.Pass,
transfer: ExternalValidationResult.Pass,
burn: ExternalValidationResult.Pass,
},
},
}).sendAndConfirm(umi);

const collectionSigner = generateSigner(umi);
await createCollection(umi, {
collection: collectionSigner,
name: 'Test name',
uri: 'https://example.com',
plugins: [
{
type: 'Oracle',
resultsOffset: {
type: 'Anchor',
},
lifecycleChecks: {
create: [CheckResult.CAN_REJECT],
},
baseAddress: account.publicKey,
},
],
}).sendAndConfirm(umi);

await assertCollection(t, umi, {
uri: 'https://example.com',
name: 'Test name',
collection: collectionSigner.publicKey,
updateAuthority: umi.identity.publicKey,
oracles: [
{
authority: {
type: 'UpdateAuthority',
},
type: 'Oracle',
resultsOffset: {
type: 'Anchor',
},
lifecycleChecks: {
create: [CheckResult.CAN_REJECT],
},
baseAddress: account.publicKey,
baseAddressConfig: undefined,
},
],
});

const collection = await fetchCollection(umi, collectionSigner.publicKey);

// create asset referencing the oracle account
const assetSigner = generateSigner(umi);
const result = create(umi, {
...DEFAULT_ASSET,
asset: assetSigner,
collection,
}).sendAndConfirm(umi);

await t.throwsAsync(result, { name: 'InvalidAuthority' });
await fixedAccountSet(umi, {
account: account.publicKey,
signer: umi.identity,
args: {
oracleData: {
__kind: 'V1',
create: ExternalValidationResult.Pass,
update: ExternalValidationResult.Pass,
transfer: ExternalValidationResult.Pass,
burn: ExternalValidationResult.Pass,
},
},
}).sendAndConfirm(umi);

await create(umi, {
...DEFAULT_ASSET,
asset: assetSigner,
collection,
}).sendAndConfirm(umi);

await assertAsset(t, umi, {
...DEFAULT_ASSET,
asset: assetSigner.publicKey,
owner: umi.identity.publicKey,
});
});
17 changes: 17 additions & 0 deletions programs/mpl-core/src/plugins/update_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ impl DataBlob for UpdateDelegate {
}

impl PluginValidation for UpdateDelegate {
fn validate_create(
&self,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
if let Some(resolved_authorities) = ctx.resolved_authorities {
if resolved_authorities.contains(ctx.self_authority) {
return approve!();
}
}

if self.additional_delegates.contains(ctx.authority_info.key) {
return approve!();
}

abstain!()
}

fn validate_add_plugin(
&self,
ctx: &PluginValidationContext,
Expand Down
31 changes: 24 additions & 7 deletions programs/mpl-core/src/processor/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ use crate::{
plugins::{
create_meta_idempotent, create_plugin_meta, initialize_external_plugin_adapter,
initialize_plugin, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter,
ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair, PluginType,
PluginValidationContext, ValidationResult,
ExternalPluginAdapterInitInfo, HookableLifecycleEvent, Plugin, PluginAuthorityPair,
PluginType, PluginValidationContext, ValidationResult,
},
state::{
AssetV1, Authority, CollectionV1, DataState, SolanaAccount, UpdateAuthority, COLLECT_AMOUNT,
},
utils::resolve_authority,
utils::{resolve_authority, validate_asset_permissions},
};

#[repr(C)]
Expand Down Expand Up @@ -101,10 +101,6 @@ pub(crate) fn process_create<'a>(
),
};

if update_authority.validate_create(&ctx.accounts, &args)? == ValidationResult::Rejected {
return Err(MplCoreError::InvalidAuthority.into());
}

let new_asset = AssetV1::new(
*ctx.accounts
.owner
Expand Down Expand Up @@ -151,6 +147,27 @@ pub(crate) fn process_create<'a>(
);

if args.data_state == DataState::AccountState {
// Validate asset permissions.
let _ = validate_asset_permissions(
accounts,
authority,
ctx.accounts.asset,
ctx.accounts.collection,
None,
None,
None,
None,
AssetV1::check_create,
CollectionV1::check_create,
PluginType::check_create,
AssetV1::validate_create,
CollectionV1::validate_create,
Plugin::validate_create,
Some(ExternalPluginAdapter::validate_create),
Some(HookableLifecycleEvent::Create),
)?;

// Validate permissions for the created asset.
let mut approved = true;
let mut force_approved = false;

Expand Down
31 changes: 2 additions & 29 deletions programs/mpl-core/src/processor/create_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ use crate::{
instruction::accounts::CreateCollectionV2Accounts,
plugins::{
create_meta_idempotent, create_plugin_meta, initialize_external_plugin_adapter,
initialize_plugin, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter,
ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair, PluginType,
PluginValidationContext, ValidationResult,
initialize_plugin, CheckResult, ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair,
PluginType, PluginValidationContext, ValidationResult,
},
state::{Authority, CollectionV1, Key},
};
Expand Down Expand Up @@ -176,32 +175,6 @@ pub(crate) fn process_create_collection<'a>(
return Err(MplCoreError::CannotAddDataSection.into());
}

let external_check_result_bits = ExternalCheckResultBits::from(
ExternalPluginAdapter::check_create(plugin_init_info),
);

if external_check_result_bits.can_reject() {
let validation_ctx = PluginValidationContext {
accounts,
asset_info: None,
collection_info: Some(ctx.accounts.collection),
// External plugin adapters are always managed by the update authority.
self_authority: &Authority::UpdateAuthority,
authority_info: authority,
resolved_authorities: None,
new_owner: None,
new_asset_authority: None,
new_collection_authority: None,
target_plugin: None,
};
if ExternalPluginAdapter::validate_create(
&ExternalPluginAdapter::from(plugin_init_info),
&validation_ctx,
)? == ValidationResult::Rejected
{
approved = false;
};
}
initialize_external_plugin_adapter::<CollectionV1>(
plugin_init_info,
Some(&new_collection),
Expand Down
19 changes: 19 additions & 0 deletions programs/mpl-core/src/state/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ impl AssetV1 {
/// The base length of the asset account with an empty name and uri and no seq.
pub const BASE_LENGTH: usize = 1 + 32 + 33 + 4 + 4 + 1;

/// Check permissions for the create lifecycle event.
pub fn check_create() -> CheckResult {
CheckResult::CanApprove
}

/// Check permissions for the add plugin lifecycle event.
pub fn check_add_plugin() -> CheckResult {
CheckResult::CanApprove
Expand Down Expand Up @@ -128,6 +133,20 @@ impl AssetV1 {
CheckResult::None
}

/// Validate the create lifecycle event.
pub fn validate_create(
&self,
_authority_info: &AccountInfo,
_new_plugin: Option<&Plugin>,
_: Option<&ExternalPluginAdapter>,
) -> Result<ValidationResult, ProgramError> {
// If the asset is part of a collection, the collection must approve the create.
match self.update_authority {
UpdateAuthority::Collection(_) => abstain!(),
_ => approve!(),
}
}

/// Validate the add plugin lifecycle event.
pub fn validate_add_plugin(
&self,
Expand Down
19 changes: 19 additions & 0 deletions programs/mpl-core/src/state/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ impl CollectionV1 {
}
}

/// Check permissions for the create lifecycle event.
pub fn check_create() -> CheckResult {
CheckResult::CanApprove
}

/// Check permissions for the add plugin lifecycle event.
pub fn check_add_plugin() -> CheckResult {
CheckResult::CanApprove
Expand Down Expand Up @@ -113,6 +118,20 @@ impl CollectionV1 {
CheckResult::None
}

/// Validate the create lifecycle event.
pub fn validate_create(
&self,
authority_info: &AccountInfo,
_new_plugin: Option<&Plugin>,
_: Option<&ExternalPluginAdapter>,
) -> Result<ValidationResult, ProgramError> {
if authority_info.key == &self.update_authority {
approve!()
} else {
abstain!()
}
}

/// Validate the add plugin lifecycle event.
pub fn validate_add_plugin(
&self,
Expand Down
Loading

0 comments on commit 3ce1104

Please sign in to comment.