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

Fixing create IXes to properly run create validations. #173

Merged
merged 1 commit into from
Jul 29, 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
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,
});
});
9 changes: 8 additions & 1 deletion programs/mpl-core/src/plugins/update_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ impl DataBlob for UpdateDelegate {
impl PluginValidation for UpdateDelegate {
fn validate_create(
&self,
_ctx: &PluginValidationContext,
ctx: &PluginValidationContext,
) -> Result<ValidationResult, ProgramError> {
if !self.additional_delegates.is_empty() {
return Err(MplCoreError::NotAvailable.into());
}

if let Some(resolved_authorities) = ctx.resolved_authorities {
if resolved_authorities.contains(ctx.self_authority) {
return approve!();
}
}

abstain!()
}

Expand Down
30 changes: 23 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,26 @@ pub(crate) fn process_create<'a>(
);

if args.data_state == DataState::AccountState {
// Validate asset permissions.
let _ = validate_asset_permissions(
blockiosaurus marked this conversation as resolved.
Show resolved Hide resolved
accounts,
authority,
ctx.accounts.asset,
ctx.accounts.collection,
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
29 changes: 2 additions & 27 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 @@ -170,30 +169,6 @@ pub(crate) fn process_create_collection<'a>(
ctx.accounts.system_program,
)?;
for plugin_init_info in &plugins {
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,
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,
&mut plugin_header,
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!(),
}
blockiosaurus marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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
Loading