From 82167723c77164401eabac26ca4639f27664b6e6 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 25 Apr 2024 10:48:38 -0700 Subject: [PATCH] Master edition collection plugin (#81) - allow specifying max supply, name and uri on the collection - the fields are informational only and can be freely updated by the creator --- clients/js/src/generated/types/index.ts | 1 + .../js/src/generated/types/masterEdition.ts | 42 +++++ clients/js/src/generated/types/plugin.ts | 19 ++- clients/js/src/generated/types/pluginType.ts | 1 + clients/js/src/plugins.ts | 5 + clients/js/src/types.ts | 3 + .../plugins/collection/masterEdition.test.ts | 155 ++++++++++++++++++ .../src/generated/types/master_edition.rs | 17 ++ clients/rust/src/generated/types/mod.rs | 2 + clients/rust/src/generated/types/plugin.rs | 2 + .../rust/src/generated/types/plugin_type.rs | 1 + clients/rust/src/hooked/advanced_types.rs | 13 +- clients/rust/src/hooked/mod.rs | 1 + clients/rust/src/hooked/plugin.rs | 13 +- idls/mpl_core.json | 37 +++++ programs/mpl-core/src/plugins/edition.rs | 9 +- programs/mpl-core/src/plugins/lifecycle.rs | 15 ++ .../mpl-core/src/plugins/master_edition.rs | 18 ++ programs/mpl-core/src/plugins/mod.rs | 8 + programs/mpl-core/src/processor/add_plugin.rs | 6 + programs/mpl-core/src/processor/create.rs | 5 + .../src/processor/create_collection.rs | 1 + 22 files changed, 358 insertions(+), 16 deletions(-) create mode 100644 clients/js/src/generated/types/masterEdition.ts create mode 100644 clients/js/test/plugins/collection/masterEdition.test.ts create mode 100644 clients/rust/src/generated/types/master_edition.rs create mode 100644 programs/mpl-core/src/plugins/master_edition.rs diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 14773ab0..6e8c0e4d 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -19,6 +19,7 @@ export * from './freezeDelegate'; export * from './hashablePluginSchema'; export * from './hashedAssetSchema'; export * from './key'; +export * from './masterEdition'; export * from './permanentBurnDelegate'; export * from './permanentFreezeDelegate'; export * from './permanentTransferDelegate'; diff --git a/clients/js/src/generated/types/masterEdition.ts b/clients/js/src/generated/types/masterEdition.ts new file mode 100644 index 00000000..f11ec2a2 --- /dev/null +++ b/clients/js/src/generated/types/masterEdition.ts @@ -0,0 +1,42 @@ +/** + * 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 { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + string, + struct, + u32, +} from '@metaplex-foundation/umi/serializers'; + +export type MasterEdition = { + maxSupply: Option; + name: Option; + uri: Option; +}; + +export type MasterEditionArgs = { + maxSupply: OptionOrNullable; + name: OptionOrNullable; + uri: OptionOrNullable; +}; + +export function getMasterEditionSerializer(): Serializer< + MasterEditionArgs, + MasterEdition +> { + return struct( + [ + ['maxSupply', option(u32())], + ['name', option(string())], + ['uri', option(string())], + ], + { description: 'MasterEdition' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 26466b13..76d8d14a 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -23,6 +23,8 @@ import { EditionArgs, FreezeDelegate, FreezeDelegateArgs, + MasterEdition, + MasterEditionArgs, PermanentBurnDelegate, PermanentBurnDelegateArgs, PermanentFreezeDelegate, @@ -39,6 +41,7 @@ import { getBurnDelegateSerializer, getEditionSerializer, getFreezeDelegateSerializer, + getMasterEditionSerializer, getPermanentBurnDelegateSerializer, getPermanentFreezeDelegateSerializer, getPermanentTransferDelegateSerializer, @@ -57,7 +60,8 @@ export type Plugin = | { __kind: 'Attributes'; fields: [Attributes] } | { __kind: 'PermanentTransferDelegate'; fields: [PermanentTransferDelegate] } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegate] } - | { __kind: 'Edition'; fields: [Edition] }; + | { __kind: 'Edition'; fields: [Edition] } + | { __kind: 'MasterEdition'; fields: [MasterEdition] }; export type PluginArgs = | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } @@ -72,7 +76,8 @@ export type PluginArgs = fields: [PermanentTransferDelegateArgs]; } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegateArgs] } - | { __kind: 'Edition'; fields: [EditionArgs] }; + | { __kind: 'Edition'; fields: [EditionArgs] } + | { __kind: 'MasterEdition'; fields: [MasterEditionArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -137,6 +142,12 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getEditionSerializer()])], ]), ], + [ + 'MasterEdition', + struct>([ + ['fields', tuple([getMasterEditionSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -186,6 +197,10 @@ export function plugin( kind: 'Edition', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'MasterEdition', + 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 14f79584..6ce51bd3 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -19,6 +19,7 @@ export enum PluginType { PermanentTransferDelegate, PermanentBurnDelegate, Edition, + MasterEdition, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/plugins.ts b/clients/js/src/plugins.ts index cdcc0e66..6cfaad35 100644 --- a/clients/js/src/plugins.ts +++ b/clients/js/src/plugins.ts @@ -15,6 +15,7 @@ import { PluginType, UpdateDelegateArgs, EditionArgs, + MasterEditionArgs, } from './generated'; import { BasePluginAuthority, PluginsList } from './types'; import { mapPluginAuthority } from './authority'; @@ -69,6 +70,10 @@ export type CreatePluginArgs = | { type: 'Edition'; data: EditionArgs; + } + | { + type: 'MasterEdition'; + data: MasterEditionArgs; }; export function createPlugin(args: CreatePluginArgs): BasePlugin { diff --git a/clients/js/src/types.ts b/clients/js/src/types.ts index 1d26922a..06eb2b80 100644 --- a/clients/js/src/types.ts +++ b/clients/js/src/types.ts @@ -12,6 +12,7 @@ import { UpdateAuthority, PermanentBurnDelegate, Edition, + MasterEdition, } from './generated'; export type BasePluginAuthority = { @@ -44,6 +45,7 @@ export type PermanentTransferDelegatePlugin = BasePlugin & PermanentTransferDelegate; export type PermanentBurnDelegatePlugin = BasePlugin & PermanentBurnDelegate; export type EditionPlugin = BasePlugin & Edition; +export type MasterEditionPlugin = BasePlugin & MasterEdition; export type PluginsList = { royalties?: RoyaltiesPlugin; @@ -56,4 +58,5 @@ export type PluginsList = { permanentTransferDelegate?: PermanentTransferDelegatePlugin; permanentBurnDelegate?: PermanentBurnDelegatePlugin; edition?: EditionPlugin; + masterEdition?: MasterEditionPlugin; }; diff --git a/clients/js/test/plugins/collection/masterEdition.test.ts b/clients/js/test/plugins/collection/masterEdition.test.ts new file mode 100644 index 00000000..f99d676d --- /dev/null +++ b/clients/js/test/plugins/collection/masterEdition.test.ts @@ -0,0 +1,155 @@ +import test from 'ava'; +import { none, some } from '@metaplex-foundation/umi'; +import { + pluginAuthorityPair, + updatePluginAuthority, + createPlugin, + addPluginV1, + addCollectionPluginV1, +} from '../../../src'; +import { + DEFAULT_COLLECTION, + assertCollection, + createAsset, + createCollection, + createUmi, +} from '../../_setup'; + +test('it can add masterEdition to collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + await addCollectionPluginV1(umi, { + collection: collection.publicKey, + plugin: createPlugin({ + type: 'MasterEdition', + data: { + maxSupply: 100, + name: 'name', + uri: 'uri', + }, + }), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + masterEdition: { + authority: { + type: 'UpdateAuthority', + }, + maxSupply: some(100), + name: some('name'), + uri: some('uri'), + }, + }); +}); + +test('it can create collection with masterEdition', async (t) => { + const umi = await createUmi(); + + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'MasterEdition', + data: { + maxSupply: 100, + name: 'name', + uri: 'uri', + }, + authority: updatePluginAuthority(), + }), + ], + }); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + masterEdition: { + authority: { + type: 'UpdateAuthority', + }, + maxSupply: some(100), + name: some('name'), + uri: some('uri'), + }, + }); +}); + +test('it can create master edition with default values', async (t) => { + const umi = await createUmi(); + + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'MasterEdition', + data: { + maxSupply: null, + name: null, + uri: null, + }, + authority: updatePluginAuthority(), + }), + ], + }); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + masterEdition: { + authority: { + type: 'UpdateAuthority', + }, + maxSupply: none(), + name: none(), + uri: none(), + }, + }); +}); + +test('it cannot add masterEdition to asset', async (t) => { + const umi = await createUmi(); + + const asset = await createAsset(umi); + + const result = addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'MasterEdition', + data: { + maxSupply: 100, + name: 'name', + uri: 'uri', + }, + }), + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + name: 'InvalidPlugin', + }); +}); + +test('it cannot create asset with masterEdition', async (t) => { + const umi = await createUmi(); + + const result = createAsset(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'MasterEdition', + data: { + maxSupply: 100, + name: 'name', + uri: 'uri', + }, + authority: updatePluginAuthority(), + }), + ], + }); + + await t.throwsAsync(result, { + name: 'InvalidPlugin', + }); +}); diff --git a/clients/rust/src/generated/types/master_edition.rs b/clients/rust/src/generated/types/master_edition.rs new file mode 100644 index 00000000..566c8d77 --- /dev/null +++ b/clients/rust/src/generated/types/master_edition.rs @@ -0,0 +1,17 @@ +//! 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 MasterEdition { + pub max_supply: Option, + pub name: Option, + pub uri: Option, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index 4f1ba1d1..effaf2cf 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -18,6 +18,7 @@ pub(crate) mod r#freeze_delegate; pub(crate) mod r#hashable_plugin_schema; pub(crate) mod r#hashed_asset_schema; pub(crate) mod r#key; +pub(crate) mod r#master_edition; pub(crate) mod r#permanent_burn_delegate; pub(crate) mod r#permanent_freeze_delegate; pub(crate) mod r#permanent_transfer_delegate; @@ -45,6 +46,7 @@ pub use self::r#freeze_delegate::*; pub use self::r#hashable_plugin_schema::*; pub use self::r#hashed_asset_schema::*; pub use self::r#key::*; +pub use self::r#master_edition::*; pub use self::r#permanent_burn_delegate::*; pub use self::r#permanent_freeze_delegate::*; pub use self::r#permanent_transfer_delegate::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 3d2ff24c..57a77a63 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -9,6 +9,7 @@ use crate::generated::types::Attributes; use crate::generated::types::BurnDelegate; use crate::generated::types::Edition; use crate::generated::types::FreezeDelegate; +use crate::generated::types::MasterEdition; use crate::generated::types::PermanentBurnDelegate; use crate::generated::types::PermanentFreezeDelegate; use crate::generated::types::PermanentTransferDelegate; @@ -31,4 +32,5 @@ pub enum Plugin { PermanentTransferDelegate(PermanentTransferDelegate), PermanentBurnDelegate(PermanentBurnDelegate), Edition(Edition), + MasterEdition(MasterEdition), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index 58640a04..d6d4518c 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -24,4 +24,5 @@ pub enum PluginType { PermanentTransferDelegate, PermanentBurnDelegate, Edition, + MasterEdition, } diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 5c16eff9..6f4105e8 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, PermanentBurnDelegate, - PermanentFreezeDelegate, PermanentTransferDelegate, PluginAuthority, Royalties, - TransferDelegate, UpdateDelegate, + Attributes, BurnDelegate, Edition, FreezeDelegate, Key, MasterEdition, + PermanentBurnDelegate, PermanentFreezeDelegate, PermanentTransferDelegate, PluginAuthority, + Royalties, TransferDelegate, UpdateDelegate, }, }; @@ -125,6 +125,12 @@ pub struct EditionPlugin { pub edition: Edition, } +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct MasterEditionPlugin { + pub base: BasePlugin, + pub master_edition: MasterEdition, +} + #[derive(Debug, Default)] pub struct PluginsList { pub royalties: Option, @@ -137,6 +143,7 @@ pub struct PluginsList { pub permanent_transfer_delegate: Option, pub permanent_burn_delegate: Option, pub edition: Option, + pub master_edition: Option, } #[derive(Debug)] diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index 860e42a6..cc7c9ae8 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -34,6 +34,7 @@ impl From<&Plugin> for PluginType { Plugin::PermanentTransferDelegate(_) => PluginType::PermanentTransferDelegate, Plugin::PermanentBurnDelegate(_) => PluginType::PermanentBurnDelegate, Plugin::Edition(_) => PluginType::Edition, + Plugin::MasterEdition(_) => PluginType::MasterEdition, } } } diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 8f7ae261..b9a56416 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -7,9 +7,10 @@ use crate::{ errors::MplCoreError, types::{Plugin, PluginAuthority, PluginType, RegistryRecord}, AttributesPlugin, BaseAuthority, BasePlugin, BurnDelegatePlugin, DataBlob, EditionPlugin, - FreezeDelegatePlugin, PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, - PermanentTransferDelegatePlugin, PluginRegistryV1Safe, PluginsList, RegistryRecordSafe, - RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, UpdateDelegatePlugin, + FreezeDelegatePlugin, MasterEditionPlugin, PermanentBurnDelegatePlugin, + PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe, + PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, + UpdateDelegatePlugin, }; /// Fetch the plugin from the registry. @@ -185,6 +186,12 @@ pub(crate) fn registry_records_to_plugin_list( }) } Plugin::Edition(edition) => acc.edition = Some(EditionPlugin { base, edition }), + Plugin::MasterEdition(master_edition) => { + acc.master_edition = Some(MasterEditionPlugin { + base, + master_edition, + }) + } } } Ok(acc) diff --git a/idls/mpl_core.json b/idls/mpl_core.json index c0726ba4..bcebe037 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -1500,6 +1500,32 @@ ] } }, + { + "name": "MasterEdition", + "type": { + "kind": "struct", + "fields": [ + { + "name": "maxSupply", + "type": { + "option": "u32" + } + }, + { + "name": "name", + "type": { + "option": "string" + } + }, + { + "name": "uri", + "type": { + "option": "string" + } + } + ] + } + }, { "name": "PermanentBurnDelegate", "type": { @@ -2148,6 +2174,14 @@ "defined": "Edition" } ] + }, + { + "name": "MasterEdition", + "fields": [ + { + "defined": "MasterEdition" + } + ] } ] } @@ -2186,6 +2220,9 @@ }, { "name": "Edition" + }, + { + "name": "MasterEdition" } ] } diff --git a/programs/mpl-core/src/plugins/edition.rs b/programs/mpl-core/src/plugins/edition.rs index 1b1cde71..ab82359a 100644 --- a/programs/mpl-core/src/plugins/edition.rs +++ b/programs/mpl-core/src/plugins/edition.rs @@ -8,19 +8,12 @@ use super::{PluginType, PluginValidation, PluginValidationContext, ValidationRes /// The edition plugin allows the creator to set an edition number on the asset /// The default authority for this plugin is the creator. #[repr(C)] -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, Default, PartialEq, Eq)] pub struct Edition { /// The edition number. pub number: u32, } -impl Edition { - /// Initialize the Edition plugin, 0 by default. - pub fn new() -> Self { - Self { number: 0 } - } -} - impl PluginValidation for Edition { fn validate_add_plugin( &self, diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index f6487218..fb35c3f8 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -159,6 +159,7 @@ impl Plugin { permanent_burn.validate_add_plugin(ctx) } Plugin::Edition(edition) => edition.validate_add_plugin(ctx), + Plugin::MasterEdition(master_edition) => master_edition.validate_add_plugin(ctx), } } @@ -192,6 +193,7 @@ impl Plugin { permanent_burn.validate_remove_plugin(ctx) } Plugin::Edition(edition) => edition.validate_remove_plugin(ctx), + Plugin::MasterEdition(master_edition) => master_edition.validate_remove_plugin(ctx), } } @@ -229,6 +231,9 @@ impl Plugin { permanent_burn.validate_approve_plugin_authority(ctx) } Plugin::Edition(edition) => edition.validate_approve_plugin_authority(ctx), + Plugin::MasterEdition(master_edition) => { + master_edition.validate_approve_plugin_authority(ctx) + } } } @@ -265,6 +270,9 @@ impl Plugin { permanent_burn.validate_revoke_plugin_authority(ctx) } Plugin::Edition(edition) => edition.validate_revoke_plugin_authority(ctx), + Plugin::MasterEdition(master_edition) => { + master_edition.validate_revoke_plugin_authority(ctx) + } } } @@ -288,6 +296,7 @@ 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), } } @@ -311,6 +320,7 @@ 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), } } @@ -347,6 +357,7 @@ impl Plugin { permanent_burn.validate_update_plugin(ctx) } Plugin::Edition(edition) => edition.validate_update_plugin(ctx), + Plugin::MasterEdition(master_edition) => master_edition.validate_update_plugin(ctx), }?; match (&base_result, &result) { @@ -389,6 +400,7 @@ 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), } } @@ -412,6 +424,7 @@ impl Plugin { Plugin::Attributes(attributes_transfer) => attributes_transfer.validate_transfer(ctx), 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), } } @@ -435,6 +448,7 @@ 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), } } @@ -460,6 +474,7 @@ impl Plugin { permanent_burn.validate_decompress(ctx) } Plugin::Edition(edition) => edition.validate_decompress(ctx), + Plugin::MasterEdition(master_edition) => master_edition.validate_decompress(ctx), } } } diff --git a/programs/mpl-core/src/plugins/master_edition.rs b/programs/mpl-core/src/plugins/master_edition.rs new file mode 100644 index 00000000..74735125 --- /dev/null +++ b/programs/mpl-core/src/plugins/master_edition.rs @@ -0,0 +1,18 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::PluginValidation; + +/// The master edition plugin allows the creator to specify details on the master edition including max supply, name, and uri. +/// The default authority for this plugin is the creator. +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Default, Debug, PartialEq, Eq)] +pub struct MasterEdition { + /// The max supply of editions + pub max_supply: Option, + /// optional master edition name + pub name: Option, + /// optional master edition uri + pub uri: Option, +} + +impl PluginValidation for MasterEdition {} diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index 9d459490..b1f4cb0a 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -3,6 +3,7 @@ mod burn_delegate; mod edition; mod freeze_delegate; mod lifecycle; +mod master_edition; mod permanent_burn_delegate; mod permanent_freeze_delegate; mod permanent_transfer_delegate; @@ -18,6 +19,7 @@ pub use burn_delegate::*; pub use edition::*; pub use freeze_delegate::*; pub use lifecycle::*; +pub use master_edition::*; use num_derive::ToPrimitive; pub use permanent_burn_delegate::*; pub use permanent_freeze_delegate::*; @@ -65,6 +67,8 @@ pub enum Plugin { PermanentBurnDelegate(PermanentBurnDelegate), /// Edition plugin allows creators to add an edition number to the asset Edition(Edition), + /// Master Edition plugin allows creators to specify the max supply and master edition details + MasterEdition(MasterEdition), } impl Plugin { @@ -129,6 +133,8 @@ pub enum PluginType { PermanentBurnDelegate, /// The Edition plugin. Edition, + /// The Master Edition plugin. + MasterEdition, } impl DataBlob for PluginType { @@ -154,6 +160,7 @@ impl From<&Plugin> for PluginType { Plugin::PermanentTransferDelegate(_) => PluginType::PermanentTransferDelegate, Plugin::PermanentBurnDelegate(_) => PluginType::PermanentBurnDelegate, Plugin::Edition(_) => PluginType::Edition, + Plugin::MasterEdition(_) => PluginType::MasterEdition, } } } @@ -172,6 +179,7 @@ impl PluginType { PluginType::PermanentTransferDelegate => Authority::UpdateAuthority, PluginType::PermanentBurnDelegate => Authority::UpdateAuthority, PluginType::Edition => Authority::UpdateAuthority, + PluginType::MasterEdition => Authority::UpdateAuthority, } } } diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index c6ce277d..61704ba1 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -47,6 +47,12 @@ pub(crate) fn add_plugin<'a>( return Err(MplCoreError::NotAvailable.into()); } + // TODO move into plugin validation when asset/collection is part of validation context + let plugin_type = PluginType::from(&args.plugin); + if plugin_type == PluginType::MasterEdition { + return Err(MplCoreError::InvalidPlugin.into()); + } + //TODO: Seed with Rejected let validation_ctx = PluginValidationContext { self_authority: &args.init_authority.unwrap_or(args.plugin.manager()), diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index d07a44bd..04776856 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -125,6 +125,11 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateV1Args) -> let mut approved = true; let mut force_approved = false; for plugin in &plugins { + // TODO move into plugin validation when asset/collection is part of validation context + let plugin_type = PluginType::from(&plugin.plugin); + if plugin_type == PluginType::MasterEdition { + return Err(MplCoreError::InvalidPlugin.into()); + } if PluginType::check_create(&PluginType::from(&plugin.plugin)) != CheckResult::None { let validation_ctx = PluginValidationContext { diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index ed5f9816..a5b5be9a 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -95,6 +95,7 @@ pub(crate) fn create_collection<'a>( return Err(MplCoreError::InvalidAuthority.into()); } + // TODO move into plugin validation when asset/collection is part of validation context let plugin_type = PluginType::from(&plugin.plugin); if plugin_type == PluginType::Edition { return Err(MplCoreError::InvalidPlugin.into());