diff --git a/clients/js/src/generated/types/addBlocker.ts b/clients/js/src/generated/types/addBlocker.ts new file mode 100644 index 00000000..1e94c222 --- /dev/null +++ b/clients/js/src/generated/types/addBlocker.ts @@ -0,0 +1,23 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; + +export type AddBlocker = {}; + +export type AddBlockerArgs = AddBlocker; + +export function getAddBlockerSerializer(): Serializer< + AddBlockerArgs, + AddBlocker +> { + return struct([], { description: 'AddBlocker' }) as Serializer< + AddBlockerArgs, + AddBlocker + >; +} diff --git a/clients/js/src/generated/types/immutableMetadata.ts b/clients/js/src/generated/types/immutableMetadata.ts new file mode 100644 index 00000000..fe3ca4e2 --- /dev/null +++ b/clients/js/src/generated/types/immutableMetadata.ts @@ -0,0 +1,22 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; + +export type ImmutableMetadata = {}; + +export type ImmutableMetadataArgs = ImmutableMetadata; + +export function getImmutableMetadataSerializer(): Serializer< + ImmutableMetadataArgs, + ImmutableMetadata +> { + return struct([], { + description: 'ImmutableMetadata', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 6e8c0e4d..d21be145 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -6,6 +6,7 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './addBlocker'; export * from './attribute'; export * from './attributes'; export * from './burnDelegate'; @@ -18,6 +19,7 @@ export * from './extraAccounts'; export * from './freezeDelegate'; export * from './hashablePluginSchema'; export * from './hashedAssetSchema'; +export * from './immutableMetadata'; export * from './key'; export * from './masterEdition'; export * from './permanentBurnDelegate'; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 76d8d14a..1fe904b4 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -15,6 +15,8 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { + AddBlocker, + AddBlockerArgs, Attributes, AttributesArgs, BurnDelegate, @@ -23,6 +25,8 @@ import { EditionArgs, FreezeDelegate, FreezeDelegateArgs, + ImmutableMetadata, + ImmutableMetadataArgs, MasterEdition, MasterEditionArgs, PermanentBurnDelegate, @@ -37,10 +41,12 @@ import { TransferDelegateArgs, UpdateDelegate, UpdateDelegateArgs, + getAddBlockerSerializer, getAttributesSerializer, getBurnDelegateSerializer, getEditionSerializer, getFreezeDelegateSerializer, + getImmutableMetadataSerializer, getMasterEditionSerializer, getPermanentBurnDelegateSerializer, getPermanentFreezeDelegateSerializer, @@ -61,7 +67,9 @@ export type Plugin = | { __kind: 'PermanentTransferDelegate'; fields: [PermanentTransferDelegate] } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegate] } | { __kind: 'Edition'; fields: [Edition] } - | { __kind: 'MasterEdition'; fields: [MasterEdition] }; + | { __kind: 'MasterEdition'; fields: [MasterEdition] } + | { __kind: 'AddBlocker'; fields: [AddBlocker] } + | { __kind: 'ImmutableMetadata'; fields: [ImmutableMetadata] }; export type PluginArgs = | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } @@ -77,7 +85,9 @@ export type PluginArgs = } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegateArgs] } | { __kind: 'Edition'; fields: [EditionArgs] } - | { __kind: 'MasterEdition'; fields: [MasterEditionArgs] }; + | { __kind: 'MasterEdition'; fields: [MasterEditionArgs] } + | { __kind: 'AddBlocker'; fields: [AddBlockerArgs] } + | { __kind: 'ImmutableMetadata'; fields: [ImmutableMetadataArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -148,6 +158,18 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getMasterEditionSerializer()])], ]), ], + [ + 'AddBlocker', + struct>([ + ['fields', tuple([getAddBlockerSerializer()])], + ]), + ], + [ + 'ImmutableMetadata', + struct>([ + ['fields', tuple([getImmutableMetadataSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -201,6 +223,14 @@ export function plugin( kind: 'MasterEdition', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'AddBlocker', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function plugin( + kind: 'ImmutableMetadata', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; export function plugin( kind: K, data?: any diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts index 6ce51bd3..759a518a 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -20,6 +20,8 @@ export enum PluginType { PermanentBurnDelegate, Edition, MasterEdition, + AddBlocker, + ImmutableMetadata, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/plugins.ts b/clients/js/src/plugins.ts index 6cfaad35..d8450612 100644 --- a/clients/js/src/plugins.ts +++ b/clients/js/src/plugins.ts @@ -74,6 +74,12 @@ export type CreatePluginArgs = | { type: 'MasterEdition'; data: MasterEditionArgs; + } + | { + type: 'ImmutableMetadata'; + } + | { + type: 'AddBlocker'; }; export function createPlugin(args: CreatePluginArgs): BasePlugin { diff --git a/clients/js/src/types.ts b/clients/js/src/types.ts index 06eb2b80..ca56bf03 100644 --- a/clients/js/src/types.ts +++ b/clients/js/src/types.ts @@ -13,6 +13,8 @@ import { PermanentBurnDelegate, Edition, MasterEdition, + ImmutableMetadata, + AddBlocker, } from './generated'; export type BasePluginAuthority = { @@ -46,6 +48,8 @@ export type PermanentTransferDelegatePlugin = BasePlugin & export type PermanentBurnDelegatePlugin = BasePlugin & PermanentBurnDelegate; export type EditionPlugin = BasePlugin & Edition; export type MasterEditionPlugin = BasePlugin & MasterEdition; +export type AddBlockerPlugin = BasePlugin & AddBlocker; +export type ImmutableMetadataPlugin = BasePlugin & ImmutableMetadata; export type PluginsList = { royalties?: RoyaltiesPlugin; @@ -59,4 +63,6 @@ export type PluginsList = { permanentBurnDelegate?: PermanentBurnDelegatePlugin; edition?: EditionPlugin; masterEdition?: MasterEditionPlugin; + addBlocker?: AddBlockerPlugin; + immutableMetadata?: ImmutableMetadataPlugin; }; diff --git a/clients/js/test/plugins/asset/addBlocker.test.ts b/clients/js/test/plugins/asset/addBlocker.test.ts new file mode 100644 index 00000000..cf6c875e --- /dev/null +++ b/clients/js/test/plugins/asset/addBlocker.test.ts @@ -0,0 +1,178 @@ +import test from 'ava'; +import { generateSigner } from '@metaplex-foundation/umi'; + +import { addPluginV1, createPlugin, pluginAuthorityPair } from '../../../src'; +import { + DEFAULT_ASSET, + assertAsset, + createAsset, + createUmi, +} from '../../_setup'; + +test('it cannot add UA-managed plugin if addBlocker had been added on creation', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + + const asset = await createAsset(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'AddBlocker', + }), + ], + }); + + const result = addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'Attributes', + data: { + attributeList: [], + }, + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it can add plugins unless AddBlocker is added', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'Attributes', + data: { + attributeList: [], + }, + }), + }).sendAndConfirm(umi); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'AddBlocker', + }), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [], + }, + addBlocker: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); + + const result = addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'Attributes', + data: { + attributeList: [], + }, + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it can add owner-managed plugins even if AddBlocker had been added', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + + const asset = await createAsset(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'AddBlocker', + }), + ], + }); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ type: 'FreezeDelegate', data: { frozen: false } }), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + freezeDelegate: { + authority: { + type: 'Owner', + }, + frozen: false, + }, + addBlocker: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it states that UA is the only one who can add the AddBlocker', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + const randomUser = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: updateAuthority.publicKey, + }); + + // random keypair can't add AddBlocker + let result = addPluginV1(umi, { + authority: randomUser, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'AddBlocker', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'NoApprovals', + }); + + // Owner can't add AddBlocker + result = addPluginV1(umi, { + authority: umi.identity, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'AddBlocker', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'NoApprovals', + }); + + // UA CAN add AddBlocker + await addPluginV1(umi, { + authority: updateAuthority, + asset: asset.publicKey, + plugin: createPlugin({ type: 'AddBlocker' }), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + addBlocker: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); diff --git a/clients/js/test/plugins/asset/immutableMetadata.test.ts b/clients/js/test/plugins/asset/immutableMetadata.test.ts new file mode 100644 index 00000000..b6e65c07 --- /dev/null +++ b/clients/js/test/plugins/asset/immutableMetadata.test.ts @@ -0,0 +1,127 @@ +import test from 'ava'; +import { generateSigner } from '@metaplex-foundation/umi'; + +import { + addPluginV1, + createPlugin, + pluginAuthorityPair, + updateV1, +} from '../../../src'; +import { + DEFAULT_ASSET, + assertAsset, + createAsset, + createUmi, +} from '../../_setup'; + +test('it can prevent the asset from metadata updating', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + + const asset = await createAsset(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'ImmutableMetadata', + }), + ], + }); + + const result = updateV1(umi, { + asset: asset.publicKey, + newName: 'bread', + newUri: 'https://example.com/bread', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it can mutate its metadata unless ImmutableMetadata plugin is added', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi); + + await updateV1(umi, { + asset: asset.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + name: 'Test Bread 2', + uri: 'https://example.com/bread2', + }); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + const result = updateV1(umi, { + asset: asset.publicKey, + newName: 'Test Bread 3', + newUri: 'https://example.com/bread3', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it states that UA is the only one who can add the ImmutableMetadata', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + const randomUser = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: updateAuthority.publicKey, + }); + + // random keypair can't add ImmutableMetadata + let result = addPluginV1(umi, { + authority: randomUser, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'NoApprovals', + }); + + // Owner can't add ImmutableMetadata + result = addPluginV1(umi, { + authority: umi.identity, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'NoApprovals', + }); + + // UA CAN add ImmutableMetadata + await addPluginV1(umi, { + authority: updateAuthority, + asset: asset.publicKey, + plugin: createPlugin({ type: 'ImmutableMetadata' }), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + immutableMetadata: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); diff --git a/clients/js/test/plugins/collection/addBlocker.test.ts b/clients/js/test/plugins/collection/addBlocker.test.ts new file mode 100644 index 00000000..1228d633 --- /dev/null +++ b/clients/js/test/plugins/collection/addBlocker.test.ts @@ -0,0 +1,165 @@ +import test from 'ava'; + +import { generateSigner } from '@metaplex-foundation/umi'; +import { + createPlugin, + addCollectionPluginV1, + pluginAuthorityPair, + addPluginV1, + updatePluginAuthority, +} from '../../../src'; +import { + DEFAULT_COLLECTION, + assertCollection, + createAsset, + createCollection, + createUmi, +} from '../../_setup'; + +test('it can add addBlocker to collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + await addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'AddBlocker', + }), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + addBlocker: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it cannot add UA-managed plugin to a collection if addBlocker had been added on creation', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'AddBlocker', + }), + ], + }); + + const result = addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it cannot add UA-managed plugin to an asset in a collection if addBlocker had been added on creation', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'AddBlocker', + }), + ], + }); + const asset = await createAsset(umi, { + collection: collection.publicKey, + }); + + const result = addPluginV1(umi, { + collection: collection.publicKey, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it prevents plugins from being added to both collection and plugins when collection is created with AddBlocker', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'AddBlocker', + authority: updatePluginAuthority(), + }), + ], + updateAuthority, + }); + const asset = await createAsset(umi, { + collection: collection.publicKey, + authority: updateAuthority, + }); + + let result = addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); + + result = addPluginV1(umi, { + collection: collection.publicKey, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it prevents plugins from being added to both collection and plugins when AddBlocker is added to a collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + const asset = await createAsset(umi, { collection: collection.publicKey }); + + await addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'AddBlocker', + }), + }).sendAndConfirm(umi); + + let result = addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); + + result = addPluginV1(umi, { + collection: collection.publicKey, + asset: asset.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); diff --git a/clients/js/test/plugins/collection/immutableMetadata.test.ts b/clients/js/test/plugins/collection/immutableMetadata.test.ts new file mode 100644 index 00000000..eb4a8d88 --- /dev/null +++ b/clients/js/test/plugins/collection/immutableMetadata.test.ts @@ -0,0 +1,157 @@ +import test from 'ava'; +import { generateSigner } from '@metaplex-foundation/umi'; +import { + createPlugin, + addCollectionPluginV1, + updateV1, + updateCollectionV1, + updatePluginAuthority, + pluginAuthorityPair, +} from '../../../src'; +import { + DEFAULT_COLLECTION, + assertCollection, + createAsset, + createCollection, + createUmi, +} from '../../_setup'; + +test('it can add immutableMetadata to collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + await addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + immutableMetadata: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can prevent collection assets metadata from being updated', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + await addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + collection: collection.publicKey, + }); + + const result = updateV1(umi, { + collection: collection.publicKey, + asset: asset.publicKey, + newName: 'Test Bread 3', + newUri: 'https://example.com/bread3', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); + +test('it states that UA is the only one who can add the ImmutableMetadata', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + const randomUser = generateSigner(umi); + const collection = await createCollection(umi, { updateAuthority }); + + // random keypair can't add ImmutableMetadata + let result = addCollectionPluginV1(umi, { + authority: randomUser, + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); + + // Payer for the the collection can't add ImmutableMetadata + result = addCollectionPluginV1(umi, { + authority: umi.identity, + collection: collection.publicKey, + plugin: createPlugin({ + type: 'ImmutableMetadata', + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); + + // UA CAN add ImmutableMetadata + await addCollectionPluginV1(umi, { + authority: updateAuthority, + collection: collection.publicKey, + plugin: createPlugin({ type: 'ImmutableMetadata' }), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + immutableMetadata: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it prevents both collection and asset from their meta updating when ImmutableMetadata is added', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'ImmutableMetadata', + authority: updatePluginAuthority(), + }), + ], + updateAuthority, + }); + const asset = await createAsset(umi, { + collection: collection.publicKey, + authority: updateAuthority, + }); + + let result = updateV1(umi, { + collection: collection.publicKey, + asset: asset.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); + + result = updateCollectionV1(umi, { + authority: updateAuthority, + collection: collection.publicKey, + newName: 'Test', + newUri: 'Test', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidAuthority', + }); +}); diff --git a/clients/rust/src/generated/types/add_blocker.rs b/clients/rust/src/generated/types/add_blocker.rs new file mode 100644 index 00000000..a9c34951 --- /dev/null +++ b/clients/rust/src/generated/types/add_blocker.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AddBlocker {} diff --git a/clients/rust/src/generated/types/immutable_metadata.rs b/clients/rust/src/generated/types/immutable_metadata.rs new file mode 100644 index 00000000..4f0af1e8 --- /dev/null +++ b/clients/rust/src/generated/types/immutable_metadata.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ImmutableMetadata {} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index effaf2cf..6049941c 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod r#add_blocker; pub(crate) mod r#attribute; pub(crate) mod r#attributes; pub(crate) mod r#burn_delegate; @@ -17,6 +18,7 @@ pub(crate) mod r#extra_accounts; pub(crate) mod r#freeze_delegate; pub(crate) mod r#hashable_plugin_schema; pub(crate) mod r#hashed_asset_schema; +pub(crate) mod r#immutable_metadata; pub(crate) mod r#key; pub(crate) mod r#master_edition; pub(crate) mod r#permanent_burn_delegate; @@ -33,6 +35,7 @@ pub(crate) mod r#transfer_delegate; pub(crate) mod r#update_authority; pub(crate) mod r#update_delegate; +pub use self::r#add_blocker::*; pub use self::r#attribute::*; pub use self::r#attributes::*; pub use self::r#burn_delegate::*; @@ -45,6 +48,7 @@ pub use self::r#extra_accounts::*; pub use self::r#freeze_delegate::*; pub use self::r#hashable_plugin_schema::*; pub use self::r#hashed_asset_schema::*; +pub use self::r#immutable_metadata::*; pub use self::r#key::*; pub use self::r#master_edition::*; pub use self::r#permanent_burn_delegate::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 57a77a63..92d9191b 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -5,10 +5,12 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::AddBlocker; use crate::generated::types::Attributes; use crate::generated::types::BurnDelegate; use crate::generated::types::Edition; use crate::generated::types::FreezeDelegate; +use crate::generated::types::ImmutableMetadata; use crate::generated::types::MasterEdition; use crate::generated::types::PermanentBurnDelegate; use crate::generated::types::PermanentFreezeDelegate; @@ -33,4 +35,6 @@ pub enum Plugin { PermanentBurnDelegate(PermanentBurnDelegate), Edition(Edition), MasterEdition(MasterEdition), + AddBlocker(AddBlocker), + ImmutableMetadata(ImmutableMetadata), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index d6d4518c..5b51e362 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -25,4 +25,6 @@ pub enum PluginType { PermanentBurnDelegate, Edition, MasterEdition, + AddBlocker, + ImmutableMetadata, } diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 6f4105e8..eb3ec77e 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -5,9 +5,9 @@ use std::{cmp::Ordering, io::ErrorKind}; use crate::{ accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1}, types::{ - Attributes, BurnDelegate, Edition, FreezeDelegate, Key, MasterEdition, - PermanentBurnDelegate, PermanentFreezeDelegate, PermanentTransferDelegate, PluginAuthority, - Royalties, TransferDelegate, UpdateDelegate, + AddBlocker, Attributes, BurnDelegate, Edition, FreezeDelegate, ImmutableMetadata, Key, + MasterEdition, PermanentBurnDelegate, PermanentFreezeDelegate, PermanentTransferDelegate, + PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, }, }; @@ -131,6 +131,18 @@ pub struct MasterEditionPlugin { pub master_edition: MasterEdition, } +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct AddBlockerPlugin { + pub base: BasePlugin, + pub add_blocker: AddBlocker, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct ImmutableMetadataPlugin { + pub base: BasePlugin, + pub immutable_metadata: ImmutableMetadata, +} + #[derive(Debug, Default)] pub struct PluginsList { pub royalties: Option, @@ -144,6 +156,8 @@ pub struct PluginsList { pub permanent_burn_delegate: Option, pub edition: Option, pub master_edition: Option, + pub add_blocker: Option, + pub immutable_metadata: Option, } #[derive(Debug)] diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index cc7c9ae8..ecf95a51 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -35,6 +35,8 @@ impl From<&Plugin> for PluginType { Plugin::PermanentBurnDelegate(_) => PluginType::PermanentBurnDelegate, Plugin::Edition(_) => PluginType::Edition, Plugin::MasterEdition(_) => PluginType::MasterEdition, + Plugin::AddBlocker(_) => PluginType::AddBlocker, + Plugin::ImmutableMetadata(_) => PluginType::ImmutableMetadata, } } } diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index b9a56416..6725a30f 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -6,11 +6,11 @@ use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, errors::MplCoreError, types::{Plugin, PluginAuthority, PluginType, RegistryRecord}, - AttributesPlugin, BaseAuthority, BasePlugin, BurnDelegatePlugin, DataBlob, EditionPlugin, - FreezeDelegatePlugin, MasterEditionPlugin, PermanentBurnDelegatePlugin, - PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe, - PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, - UpdateDelegatePlugin, + AddBlockerPlugin, AttributesPlugin, BaseAuthority, BasePlugin, BurnDelegatePlugin, DataBlob, + EditionPlugin, FreezeDelegatePlugin, ImmutableMetadataPlugin, MasterEditionPlugin, + PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, + PluginRegistryV1Safe, PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, + TransferDelegatePlugin, UpdateDelegatePlugin, }; /// Fetch the plugin from the registry. @@ -192,6 +192,15 @@ pub(crate) fn registry_records_to_plugin_list( master_edition, }) } + Plugin::AddBlocker(add_blocker) => { + acc.add_blocker = Some(AddBlockerPlugin { base, add_blocker }) + } + Plugin::ImmutableMetadata(immutable_metadata) => { + acc.immutable_metadata = Some(ImmutableMetadataPlugin { + base, + immutable_metadata, + }) + } } } Ok(acc) diff --git a/idls/mpl_core.json b/idls/mpl_core.json index bcebe037..2088bd58 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -1437,6 +1437,13 @@ ] } }, + { + "name": "AddBlocker", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "Attribute", "type": { @@ -1500,6 +1507,13 @@ ] } }, + { + "name": "ImmutableMetadata", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "MasterEdition", "type": { @@ -2182,6 +2196,22 @@ "defined": "MasterEdition" } ] + }, + { + "name": "AddBlocker", + "fields": [ + { + "defined": "AddBlocker" + } + ] + }, + { + "name": "ImmutableMetadata", + "fields": [ + { + "defined": "ImmutableMetadata" + } + ] } ] } @@ -2223,6 +2253,12 @@ }, { "name": "MasterEdition" + }, + { + "name": "AddBlocker" + }, + { + "name": "ImmutableMetadata" } ] } diff --git a/programs/mpl-core/src/plugins/add_blocker.rs b/programs/mpl-core/src/plugins/add_blocker.rs new file mode 100644 index 00000000..813923bd --- /dev/null +++ b/programs/mpl-core/src/plugins/add_blocker.rs @@ -0,0 +1,40 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; + +use crate::state::{Authority, DataBlob}; + +use super::{PluginType, PluginValidation, PluginValidationContext, ValidationResult}; + +/// The AddBlocker plugin prevents any plugin except for owner-managed plugins from being added. +/// The default authority for this plugin is None. + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)] +pub struct AddBlocker {} + +impl DataBlob for AddBlocker { + fn get_initial_size() -> usize { + 0 + } + + fn get_size(&self) -> usize { + 0 + } +} + +impl PluginValidation for AddBlocker { + fn validate_add_plugin( + &self, + ctx: &PluginValidationContext, + ) -> Result { + if let Some(plugin) = ctx.target_plugin { + if plugin.manager() == Authority::Owner + || PluginType::from(plugin) == PluginType::AddBlocker + { + return Ok(ValidationResult::Pass); + } + } + + Ok(ValidationResult::Rejected) + } +} diff --git a/programs/mpl-core/src/plugins/immutable_metadata.rs b/programs/mpl-core/src/plugins/immutable_metadata.rs new file mode 100644 index 00000000..fb9d1107 --- /dev/null +++ b/programs/mpl-core/src/plugins/immutable_metadata.rs @@ -0,0 +1,33 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; + +use crate::state::DataBlob; + +use super::{PluginValidation, PluginValidationContext, ValidationResult}; + +/// The immutable metadata plugin allows its authority to prevent plugin's meta from changing. +/// The default authority for this plugin is None. + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)] +pub struct ImmutableMetadata {} + +impl DataBlob for ImmutableMetadata { + fn get_initial_size() -> usize { + 0 + } + + fn get_size(&self) -> usize { + 0 + } +} + +impl PluginValidation for ImmutableMetadata { + /// Validate the update lifecycle action. + fn validate_update( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Rejected) + } +} diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index fb35c3f8..4a2feca4 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -28,6 +28,7 @@ impl PluginType { /// Check permissions for the add plugin lifecycle event. pub fn check_add_plugin(plugin_type: &PluginType) -> CheckResult { match plugin_type { + PluginType::AddBlocker => CheckResult::CanReject, PluginType::Royalties => CheckResult::CanReject, PluginType::UpdateDelegate => CheckResult::CanApprove, PluginType::PermanentFreezeDelegate => CheckResult::CanReject, @@ -91,6 +92,7 @@ impl PluginType { pub fn check_update(plugin_type: &PluginType) -> CheckResult { #[allow(clippy::match_single_binding)] match plugin_type { + PluginType::ImmutableMetadata => CheckResult::CanReject, PluginType::UpdateDelegate => CheckResult::CanApprove, _ => CheckResult::None, } @@ -160,6 +162,10 @@ impl Plugin { } Plugin::Edition(edition) => edition.validate_add_plugin(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_add_plugin(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_add_plugin(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_add_plugin(ctx) + } } } @@ -194,6 +200,10 @@ impl Plugin { } Plugin::Edition(edition) => edition.validate_remove_plugin(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_remove_plugin(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_remove_plugin(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_remove_plugin(ctx) + } } } @@ -234,6 +244,10 @@ impl Plugin { Plugin::MasterEdition(master_edition) => { master_edition.validate_approve_plugin_authority(ctx) } + Plugin::AddBlocker(add_blocker) => add_blocker.validate_approve_plugin_authority(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_approve_plugin_authority(ctx) + } } } @@ -273,6 +287,10 @@ impl Plugin { Plugin::MasterEdition(master_edition) => { master_edition.validate_revoke_plugin_authority(ctx) } + Plugin::AddBlocker(add_blocker) => add_blocker.validate_revoke_plugin_authority(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_revoke_plugin_authority(ctx) + } } } @@ -297,6 +315,10 @@ impl Plugin { Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_create(ctx), Plugin::Edition(edition) => edition.validate_create(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_create(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_create(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_create(ctx) + } } } @@ -321,6 +343,10 @@ impl Plugin { Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_update(ctx), Plugin::Edition(edition) => edition.validate_update(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_update(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_update(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_update(ctx) + } } } @@ -358,6 +384,10 @@ impl Plugin { } Plugin::Edition(edition) => edition.validate_update_plugin(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_update_plugin(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_update_plugin(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_update_plugin(ctx) + } }?; match (&base_result, &result) { @@ -401,6 +431,8 @@ impl Plugin { Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_burn(ctx), Plugin::Edition(edition) => edition.validate_burn(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_burn(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_burn(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => immutable_metadata.validate_burn(ctx), } } @@ -425,6 +457,10 @@ impl Plugin { Plugin::PermanentBurnDelegate(burn_transfer) => burn_transfer.validate_transfer(ctx), Plugin::Edition(edition) => edition.validate_transfer(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_transfer(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_transfer(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_transfer(ctx) + } } } @@ -449,6 +485,10 @@ impl Plugin { Plugin::PermanentBurnDelegate(burn_transfer) => burn_transfer.validate_compress(ctx), Plugin::Edition(edition) => edition.validate_compress(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_compress(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_compress(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_compress(ctx) + } } } @@ -475,6 +515,10 @@ impl Plugin { } Plugin::Edition(edition) => edition.validate_decompress(ctx), Plugin::MasterEdition(master_edition) => master_edition.validate_decompress(ctx), + Plugin::AddBlocker(add_blocker) => add_blocker.validate_decompress(ctx), + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_decompress(ctx) + } } } } diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index b1f4cb0a..ba778d3b 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -1,7 +1,9 @@ +mod add_blocker; mod attributes; mod burn_delegate; mod edition; mod freeze_delegate; +mod immutable_metadata; mod lifecycle; mod master_edition; mod permanent_burn_delegate; @@ -14,10 +16,12 @@ mod transfer; mod update_delegate; mod utils; +pub use add_blocker::*; pub use attributes::*; pub use burn_delegate::*; pub use edition::*; pub use freeze_delegate::*; +pub use immutable_metadata::*; pub use lifecycle::*; pub use master_edition::*; use num_derive::ToPrimitive; @@ -69,6 +73,10 @@ pub enum Plugin { Edition(Edition), /// Master Edition plugin allows creators to specify the max supply and master edition details MasterEdition(MasterEdition), + /// AddBlocker plugin. Prevents plugins from being added. + AddBlocker(AddBlocker), + /// ImmutableMetadata plugin. Makes metadata of the asset immutable. + ImmutableMetadata(ImmutableMetadata), } impl Plugin { @@ -135,6 +143,10 @@ pub enum PluginType { Edition, /// The Master Edition plugin. MasterEdition, + /// AddBlocker plugin. + AddBlocker, + /// ImmutableMetadata plugin. + ImmutableMetadata, } impl DataBlob for PluginType { @@ -150,6 +162,8 @@ impl DataBlob for PluginType { impl From<&Plugin> for PluginType { fn from(plugin: &Plugin) -> Self { match plugin { + Plugin::AddBlocker(_) => PluginType::AddBlocker, + Plugin::ImmutableMetadata(_) => PluginType::ImmutableMetadata, Plugin::Royalties(_) => PluginType::Royalties, Plugin::FreezeDelegate(_) => PluginType::FreezeDelegate, Plugin::BurnDelegate(_) => PluginType::BurnDelegate, @@ -169,6 +183,8 @@ impl PluginType { /// Get the default authority for a plugin which defines who must allow the plugin to be created. pub fn manager(&self) -> Authority { match self { + PluginType::AddBlocker => Authority::UpdateAuthority, + PluginType::ImmutableMetadata => Authority::UpdateAuthority, PluginType::Royalties => Authority::UpdateAuthority, PluginType::FreezeDelegate => Authority::Owner, PluginType::BurnDelegate => Authority::Owner,