From 8bdfa1fd808f1a8360fb9267ffae2615ed613a1f Mon Sep 17 00:00:00 2001 From: blockiosaurus <blockiosaurus@gmail.com> Date: Fri, 8 Mar 2024 17:25:16 -0500 Subject: [PATCH] Minor helper and test changes. --- .../instructions/addCollectionPlugin.ts | 19 ++++- .../src/generated/instructions/addPlugin.ts | 19 ++++- clients/js/test/addPlugin.test.ts | 76 ++++++++++++++++++- clients/js/test/approveAuthority.test.ts | 3 +- clients/js/test/collect.test.ts | 3 +- clients/js/test/createCollection.test.ts | 13 ++-- .../js/test/plugins/asset/delegate.test.ts | 2 + .../plugins/asset/delegateTransfer.test.ts | 1 + clients/js/test/plugins/asset/freeze.test.ts | 1 + .../collectionUpdateDelegate.test.ts | 1 + clients/js/test/removePlugin.test.ts | 1 + clients/js/test/revokeAuthority.test.ts | 5 ++ clients/js/test/update.test.ts | 2 + .../instructions/add_collection_plugin.rs | 19 +++++ .../src/generated/instructions/add_plugin.rs | 19 +++++ idls/mpl_core.json | 16 ++++ programs/mpl-core/src/plugins/mod.rs | 15 +--- programs/mpl-core/src/plugins/utils.rs | 48 +++++------- programs/mpl-core/src/processor/add_plugin.rs | 61 ++++++--------- .../src/processor/approve_plugin_authority.rs | 18 +---- programs/mpl-core/src/processor/create.rs | 4 +- .../src/processor/create_collection.rs | 4 +- .../mpl-core/src/processor/remove_plugin.rs | 48 ++---------- .../src/processor/revoke_plugin_authority.rs | 48 ++---------- programs/mpl-core/src/processor/update.rs | 19 +---- programs/mpl-core/src/utils.rs | 56 +++++++++++++- 26 files changed, 307 insertions(+), 214 deletions(-) diff --git a/clients/js/src/generated/instructions/addCollectionPlugin.ts b/clients/js/src/generated/instructions/addCollectionPlugin.ts index 55009253..3594ed55 100644 --- a/clients/js/src/generated/instructions/addCollectionPlugin.ts +++ b/clients/js/src/generated/instructions/addCollectionPlugin.ts @@ -8,6 +8,8 @@ import { Context, + Option, + OptionOrNullable, Pda, PublicKey, Signer, @@ -17,6 +19,7 @@ import { import { Serializer, mapSerializer, + option, struct, u8, } from '@metaplex-foundation/umi/serializers'; @@ -25,7 +28,14 @@ import { ResolvedAccountsWithIndices, getAccountMetasAndSigners, } from '../shared'; -import { Plugin, PluginArgs, getPluginSerializer } from '../types'; +import { + Authority, + AuthorityArgs, + Plugin, + PluginArgs, + getAuthoritySerializer, + getPluginSerializer, +} from '../types'; // Accounts. export type AddCollectionPluginInstructionAccounts = { @@ -45,9 +55,13 @@ export type AddCollectionPluginInstructionAccounts = { export type AddCollectionPluginInstructionData = { discriminator: number; plugin: Plugin; + initAuthority: Option<Authority>; }; -export type AddCollectionPluginInstructionDataArgs = { plugin: PluginArgs }; +export type AddCollectionPluginInstructionDataArgs = { + plugin: PluginArgs; + initAuthority: OptionOrNullable<AuthorityArgs>; +}; export function getAddCollectionPluginInstructionDataSerializer(): Serializer< AddCollectionPluginInstructionDataArgs, @@ -62,6 +76,7 @@ export function getAddCollectionPluginInstructionDataSerializer(): Serializer< [ ['discriminator', u8()], ['plugin', getPluginSerializer()], + ['initAuthority', option(getAuthoritySerializer())], ], { description: 'AddCollectionPluginInstructionData' } ), diff --git a/clients/js/src/generated/instructions/addPlugin.ts b/clients/js/src/generated/instructions/addPlugin.ts index 9c84b988..80af0032 100644 --- a/clients/js/src/generated/instructions/addPlugin.ts +++ b/clients/js/src/generated/instructions/addPlugin.ts @@ -8,6 +8,8 @@ import { Context, + Option, + OptionOrNullable, Pda, PublicKey, Signer, @@ -17,6 +19,7 @@ import { import { Serializer, mapSerializer, + option, struct, u8, } from '@metaplex-foundation/umi/serializers'; @@ -25,7 +28,14 @@ import { ResolvedAccountsWithIndices, getAccountMetasAndSigners, } from '../shared'; -import { Plugin, PluginArgs, getPluginSerializer } from '../types'; +import { + Authority, + AuthorityArgs, + Plugin, + PluginArgs, + getAuthoritySerializer, + getPluginSerializer, +} from '../types'; // Accounts. export type AddPluginInstructionAccounts = { @@ -47,9 +57,13 @@ export type AddPluginInstructionAccounts = { export type AddPluginInstructionData = { discriminator: number; plugin: Plugin; + initAuthority: Option<Authority>; }; -export type AddPluginInstructionDataArgs = { plugin: PluginArgs }; +export type AddPluginInstructionDataArgs = { + plugin: PluginArgs; + initAuthority: OptionOrNullable<AuthorityArgs>; +}; export function getAddPluginInstructionDataSerializer(): Serializer< AddPluginInstructionDataArgs, @@ -64,6 +78,7 @@ export function getAddPluginInstructionDataSerializer(): Serializer< [ ['discriminator', u8()], ['plugin', getPluginSerializer()], + ['initAuthority', option(getAuthoritySerializer())], ], { description: 'AddPluginInstructionData' } ), diff --git a/clients/js/test/addPlugin.test.ts b/clients/js/test/addPlugin.test.ts index 6a39fc81..173eab77 100644 --- a/clients/js/test/addPlugin.test.ts +++ b/clients/js/test/addPlugin.test.ts @@ -10,6 +10,7 @@ import { PluginType, addCollectionPlugin, addPlugin, + authority, create, createCollection, fetchAsset, @@ -50,7 +51,8 @@ test('it can add a plugin to an asset', async (t) => { plugin: { __kind: 'Freeze', fields: [{ frozen: false }], - } + }, + initAuthority: null }).sendAndConfirm(umi); const asset1 = await fetchAssetWithPlugins(umi, assetAddress.publicKey); @@ -87,6 +89,75 @@ test('it can add a plugin to an asset', async (t) => { }); }); +test('it can add a plugin to an asset with a different authority than the default', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const delegateAddress = generateSigner(umi); + + // When we create a new account. + await create(umi, { + dataState: DataState.AccountState, + asset: assetAddress, + name: 'Test Bread', + uri: 'https://example.com/bread', + plugins: [], + }).sendAndConfirm(umi); + + // Then an account was created with the correct data. + const asset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", asset); + t.like(asset, <Asset>{ + publicKey: assetAddress.publicKey, + updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); + + await addPlugin(umi, { + asset: assetAddress.publicKey, + plugin: { + __kind: 'Freeze', + fields: [{ frozen: false }], + }, + initAuthority: authority('Pubkey', { address: delegateAddress.publicKey }), + }).sendAndConfirm(umi); + + const asset1 = await fetchAssetWithPlugins(umi, assetAddress.publicKey); + // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + t.like(asset1, <AssetWithPlugins>{ + publicKey: assetAddress.publicKey, + updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + pluginHeader: { + key: 3, + pluginRegistryOffset: BigInt(120), + }, + pluginRegistry: { + key: 4, + registry: [ + { + pluginType: 2, + offset: BigInt(118), + authority: authority('Pubkey', { address: delegateAddress.publicKey }) + }, + ], + }, + plugins: [ + { + authority: authority('Pubkey', { address: delegateAddress.publicKey }), + plugin: { + __kind: 'Freeze', + fields: [{ frozen: false }], + }, + }, + ], + }); +}); + test('it can add a plugin to a collection', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); @@ -115,7 +186,8 @@ test('it can add a plugin to a collection', async (t) => { plugin: { __kind: 'Freeze', fields: [{ frozen: false }], - } + }, + initAuthority: null }).sendAndConfirm(umi); const asset1 = await fetchCollectionWithPlugins(umi, collectionAddress.publicKey); diff --git a/clients/js/test/approveAuthority.test.ts b/clients/js/test/approveAuthority.test.ts index 854a965d..41b91028 100644 --- a/clients/js/test/approveAuthority.test.ts +++ b/clients/js/test/approveAuthority.test.ts @@ -44,7 +44,8 @@ test('it can add an authority to a plugin', async (t) => { await addPlugin(umi, { asset: assetAddress.publicKey, - plugin: plugin('Freeze', [{ frozen: false }]) + plugin: plugin('Freeze', [{ frozen: false }]), + initAuthority: null }) .append( approvePluginAuthority(umi, { diff --git a/clients/js/test/collect.test.ts b/clients/js/test/collect.test.ts index c20689be..b6f78baa 100644 --- a/clients/js/test/collect.test.ts +++ b/clients/js/test/collect.test.ts @@ -68,7 +68,8 @@ test('it can add asset plugin with collect amount', async (t) => { await addPlugin(umi, { asset: assetAddress.publicKey, - plugin: plugin('Freeze', [{ frozen: true }]) + plugin: plugin('Freeze', [{ frozen: true }]), + initAuthority: null }).sendAndConfirm(umi); t.assert(await hasCollectAmount(umi, assetAddress.publicKey), 'Collect amount not found') diff --git a/clients/js/test/createCollection.test.ts b/clients/js/test/createCollection.test.ts index 2d6bd5f7..3964b68e 100644 --- a/clients/js/test/createCollection.test.ts +++ b/clients/js/test/createCollection.test.ts @@ -127,13 +127,14 @@ test('it can create a new asset with a collection with collection delegate', asy await addCollectionPlugin(umi, { collection: collectionAddress.publicKey, plugin: plugin('UpdateDelegate', [{}]), + initAuthority: null + }).sendAndConfirm(umi); + + await approveCollectionPluginAuthority(umi, { + collection: collectionAddress.publicKey, + pluginType: PluginType.UpdateDelegate, + newAuthority: authority('Pubkey', { address: delegate.publicKey }), }).sendAndConfirm(umi); - -await approveCollectionPluginAuthority(umi, { - collection: collectionAddress.publicKey, - pluginType: PluginType.UpdateDelegate, - newAuthority: authority('Pubkey', { address: delegate.publicKey}), -}).sendAndConfirm(umi); // When we create a new account. diff --git a/clients/js/test/plugins/asset/delegate.test.ts b/clients/js/test/plugins/asset/delegate.test.ts index 780157e9..ba05bdd9 100644 --- a/clients/js/test/plugins/asset/delegate.test.ts +++ b/clients/js/test/plugins/asset/delegate.test.ts @@ -35,6 +35,7 @@ test('it can delegate a new authority', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { @@ -102,6 +103,7 @@ test('a delegate can freeze the token', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { diff --git a/clients/js/test/plugins/asset/delegateTransfer.test.ts b/clients/js/test/plugins/asset/delegateTransfer.test.ts index dc539707..c383f214 100644 --- a/clients/js/test/plugins/asset/delegateTransfer.test.ts +++ b/clients/js/test/plugins/asset/delegateTransfer.test.ts @@ -35,6 +35,7 @@ test('a delegate can transfer the asset', async (t) => { __kind: 'Transfer', fields: [{}], }, + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { diff --git a/clients/js/test/plugins/asset/freeze.test.ts b/clients/js/test/plugins/asset/freeze.test.ts index 9e4ecb48..2b0cf0a2 100644 --- a/clients/js/test/plugins/asset/freeze.test.ts +++ b/clients/js/test/plugins/asset/freeze.test.ts @@ -28,6 +28,7 @@ test('it can freeze and unfreeze an asset', async (t) => { await addPlugin(umi, { asset: assetAddress.publicKey, plugin: plugin('Freeze', [{ frozen: true }]), + initAuthority: null }).sendAndConfirm(umi); const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); diff --git a/clients/js/test/plugins/collection/collectionUpdateDelegate.test.ts b/clients/js/test/plugins/collection/collectionUpdateDelegate.test.ts index 1394d700..476cbed7 100644 --- a/clients/js/test/plugins/collection/collectionUpdateDelegate.test.ts +++ b/clients/js/test/plugins/collection/collectionUpdateDelegate.test.ts @@ -37,6 +37,7 @@ test('it can create a new asset with a collection if it is the collection update __kind: 'UpdateDelegate', fields: [{}], }, + initAuthority: null }).sendAndConfirm(umi); // console.log(JSON.stringify(await fetchCollectionWithPlugins(umi, collectionAddress.publicKey), (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); diff --git a/clients/js/test/removePlugin.test.ts b/clients/js/test/removePlugin.test.ts index 0d075379..89f24173 100644 --- a/clients/js/test/removePlugin.test.ts +++ b/clients/js/test/removePlugin.test.ts @@ -46,6 +46,7 @@ test('it can remove a plugin from an asset', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); const asset1 = await fetchAssetWithPlugins(umi, assetAddress.publicKey); diff --git a/clients/js/test/revokeAuthority.test.ts b/clients/js/test/revokeAuthority.test.ts index c7810f66..1eb98bff 100644 --- a/clients/js/test/revokeAuthority.test.ts +++ b/clients/js/test/revokeAuthority.test.ts @@ -50,6 +50,7 @@ test('it can remove an authority from a plugin', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }) .append( approvePluginAuthority(umi, { @@ -168,6 +169,7 @@ test('it can remove the default authority from a plugin to make it immutable', a __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { @@ -230,6 +232,7 @@ test('it can remove a pubkey authority from a plugin if that pubkey is the signe await addPlugin(umi, { asset: assetAddress.publicKey, plugin: plugin('Freeze', [{ frozen: false }]), + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { @@ -308,6 +311,7 @@ test('it can remove a owner authority from a plugin with other authority', async await addPlugin(umi, { asset: assetAddress.publicKey, plugin: plugin('Freeze', [{ frozen: false }]), + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { @@ -384,6 +388,7 @@ test('it cannot remove a none authority from a plugin', async (t) => { await addPlugin(umi, { asset: assetAddress.publicKey, plugin: plugin('Freeze', [{ frozen: false }]), + initAuthority: null }).sendAndConfirm(umi); await approvePluginAuthority(umi, { diff --git a/clients/js/test/update.test.ts b/clients/js/test/update.test.ts index e1baf0a6..dafe8b40 100644 --- a/clients/js/test/update.test.ts +++ b/clients/js/test/update.test.ts @@ -95,6 +95,7 @@ test('it can update an asset with plugins to be larger', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); await update(umi, { @@ -158,6 +159,7 @@ test('it can update an asset with plugins to be smaller', async (t) => { __kind: 'Freeze', fields: [{ frozen: false }], }, + initAuthority: null }).sendAndConfirm(umi); await update(umi, { diff --git a/clients/rust/src/generated/instructions/add_collection_plugin.rs b/clients/rust/src/generated/instructions/add_collection_plugin.rs index 5bda5b42..9587f355 100644 --- a/clients/rust/src/generated/instructions/add_collection_plugin.rs +++ b/clients/rust/src/generated/instructions/add_collection_plugin.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Authority; use crate::generated::types::Plugin; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -98,6 +99,7 @@ impl AddCollectionPluginInstructionData { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AddCollectionPluginInstructionArgs { pub plugin: Plugin, + pub init_authority: Option<Authority>, } /// Instruction builder for `AddCollectionPlugin`. @@ -117,6 +119,7 @@ pub struct AddCollectionPluginBuilder { system_program: Option<solana_program::pubkey::Pubkey>, log_wrapper: Option<solana_program::pubkey::Pubkey>, plugin: Option<Plugin>, + init_authority: Option<Authority>, __remaining_accounts: Vec<solana_program::instruction::AccountMeta>, } @@ -165,6 +168,12 @@ impl AddCollectionPluginBuilder { self.plugin = Some(plugin); self } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: Authority) -> &mut Self { + self.init_authority = Some(init_authority); + self + } /// Add an aditional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -196,6 +205,7 @@ impl AddCollectionPluginBuilder { }; let args = AddCollectionPluginInstructionArgs { plugin: self.plugin.clone().expect("plugin is not set"), + init_authority: self.init_authority.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -381,6 +391,7 @@ impl<'a, 'b> AddCollectionPluginCpiBuilder<'a, 'b> { system_program: None, log_wrapper: None, plugin: None, + init_authority: None, __remaining_accounts: Vec::new(), }); Self { instruction } @@ -437,6 +448,12 @@ impl<'a, 'b> AddCollectionPluginCpiBuilder<'a, 'b> { self.instruction.plugin = Some(plugin); self } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: Authority) -> &mut Self { + self.instruction.init_authority = Some(init_authority); + self + } /// Add an additional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -480,6 +497,7 @@ impl<'a, 'b> AddCollectionPluginCpiBuilder<'a, 'b> { ) -> solana_program::entrypoint::ProgramResult { let args = AddCollectionPluginInstructionArgs { plugin: self.instruction.plugin.clone().expect("plugin is not set"), + init_authority: self.instruction.init_authority.clone(), }; let instruction = AddCollectionPluginCpi { __program: self.instruction.__program, @@ -513,6 +531,7 @@ struct AddCollectionPluginCpiBuilderInstruction<'a, 'b> { system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, plugin: Option<Plugin>, + init_authority: Option<Authority>, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. __remaining_accounts: Vec<( &'b solana_program::account_info::AccountInfo<'a>, diff --git a/clients/rust/src/generated/instructions/add_plugin.rs b/clients/rust/src/generated/instructions/add_plugin.rs index b1a752c7..866f064b 100644 --- a/clients/rust/src/generated/instructions/add_plugin.rs +++ b/clients/rust/src/generated/instructions/add_plugin.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Authority; use crate::generated::types::Plugin; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -107,6 +108,7 @@ impl AddPluginInstructionData { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AddPluginInstructionArgs { pub plugin: Plugin, + pub init_authority: Option<Authority>, } /// Instruction builder for `AddPlugin`. @@ -128,6 +130,7 @@ pub struct AddPluginBuilder { system_program: Option<solana_program::pubkey::Pubkey>, log_wrapper: Option<solana_program::pubkey::Pubkey>, plugin: Option<Plugin>, + init_authority: Option<Authority>, __remaining_accounts: Vec<solana_program::instruction::AccountMeta>, } @@ -183,6 +186,12 @@ impl AddPluginBuilder { self.plugin = Some(plugin); self } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: Authority) -> &mut Self { + self.init_authority = Some(init_authority); + self + } /// Add an aditional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -215,6 +224,7 @@ impl AddPluginBuilder { }; let args = AddPluginInstructionArgs { plugin: self.plugin.clone().expect("plugin is not set"), + init_authority: self.init_authority.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -419,6 +429,7 @@ impl<'a, 'b> AddPluginCpiBuilder<'a, 'b> { system_program: None, log_wrapper: None, plugin: None, + init_authority: None, __remaining_accounts: Vec::new(), }); Self { instruction } @@ -482,6 +493,12 @@ impl<'a, 'b> AddPluginCpiBuilder<'a, 'b> { self.instruction.plugin = Some(plugin); self } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: Authority) -> &mut Self { + self.instruction.init_authority = Some(init_authority); + self + } /// Add an additional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -525,6 +542,7 @@ impl<'a, 'b> AddPluginCpiBuilder<'a, 'b> { ) -> solana_program::entrypoint::ProgramResult { let args = AddPluginInstructionArgs { plugin: self.instruction.plugin.clone().expect("plugin is not set"), + init_authority: self.instruction.init_authority.clone(), }; let instruction = AddPluginCpi { __program: self.instruction.__program, @@ -561,6 +579,7 @@ struct AddPluginCpiBuilderInstruction<'a, 'b> { system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, plugin: Option<Plugin>, + init_authority: Option<Authority>, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. __remaining_accounts: Vec<( &'b solana_program::account_info::AccountInfo<'a>, diff --git a/idls/mpl_core.json b/idls/mpl_core.json index eee448f3..bfccae2a 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -1537,6 +1537,14 @@ "type": { "defined": "Plugin" } + }, + { + "name": "initAuthority", + "type": { + "option": { + "defined": "Authority" + } + } } ] } @@ -1551,6 +1559,14 @@ "type": { "defined": "Plugin" } + }, + { + "name": "initAuthority", + "type": { + "option": { + "defined": "Authority" + } + } } ] } diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index c52a72ba..ce469112 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -51,17 +51,8 @@ pub enum Plugin { impl Plugin { /// Get the default authority for a plugin which defines who must allow the plugin to be created. - pub fn default_authority(&self) -> Authority { - // match self { - // Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), - // Plugin::Royalties(_) => Ok(Authority::UpdateAuthority), - // Plugin::Freeze(_) => Ok(Authority::Owner), - // Plugin::Burn(_) => Ok(Authority::Owner), - // Plugin::Transfer(_) => Ok(Authority::Owner), - // Plugin::UpdateDelegate(_) => Ok(Authority::UpdateAuthority), - // } - - PluginType::from(self).default_authority() + pub fn manager(&self) -> Authority { + PluginType::from(self).manager() } /// Load and deserialize a plugin from an offset in the account. @@ -141,7 +132,7 @@ impl From<&Plugin> for PluginType { impl PluginType { /// Get the default authority for a plugin which defines who must allow the plugin to be created. - pub fn default_authority(&self) -> Authority { + pub fn manager(&self) -> Authority { match self { PluginType::Reserved => Authority::None, PluginType::Royalties => Authority::UpdateAuthority, diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index a56f9609..fa0826e2 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -6,37 +6,20 @@ use solana_program::{ use crate::{ error::MplCoreError, - state::{Asset, Authority, Collection, CoreAsset, DataBlob, Key, SolanaAccount}, - utils::{assert_authority, load_key, resize_or_reallocate_account}, + state::{Asset, Authority, CoreAsset, DataBlob, Key, SolanaAccount}, + utils::{assert_authority, resize_or_reallocate_account}, }; use super::{Plugin, PluginHeader, PluginRegistry, PluginType, RegistryRecord}; /// Create plugin header and registry if it doesn't exist -pub fn create_meta_idempotent<'a>( +pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, -) -> ProgramResult { - let header_offset = match load_key(account, 0)? { - Key::Asset => { - let asset = { - let mut bytes: &[u8] = &(*account.data).borrow(); - Asset::deserialize(&mut bytes)? - }; - - asset.get_size() - } - Key::Collection => { - let collection = { - let mut bytes: &[u8] = &(*account.data).borrow(); - Collection::deserialize(&mut bytes)? - }; - - collection.get_size() - } - _ => return Err(MplCoreError::IncorrectAccount.into()), - }; +) -> Result<(T, PluginHeader, PluginRegistry), ProgramError> { + let core = T::load(account, 0)?; + let header_offset = core.get_size(); // Check if the plugin header and registry exist. if header_offset == account.data_len() { @@ -60,9 +43,15 @@ pub fn create_meta_idempotent<'a>( header.save(account, header_offset)?; registry.save(account, header.plugin_registry_offset)?; - } - Ok(()) + Ok((core, header, registry)) + } else { + // They exist, so load them. + let header = PluginHeader::load(account, header_offset)?; + let registry = PluginRegistry::load(account, header.plugin_registry_offset)?; + + Ok((core, header, registry)) + } } /// Create plugin header and registry if it doesn't exist @@ -101,7 +90,7 @@ pub fn create_plugin_meta<'a, T: SolanaAccount + DataBlob>( /// Assert that the Plugin metadata has been initialized. pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { let mut bytes: &[u8] = &(*account.data).borrow(); - let asset = Asset::deserialize(&mut bytes).unwrap(); + let asset = Asset::deserialize(&mut bytes)?; if asset.get_size() == account.data_len() { return Err(MplCoreError::PluginsNotInitialized.into()); @@ -174,6 +163,7 @@ pub fn list_plugins(account: &AccountInfo) -> Result<Vec<PluginType>, ProgramErr .collect()) } +//TODO: Take in the header and registry so we don't have to reload it. /// Add a plugin to the registry and initialize it. pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( plugin: &Plugin, @@ -261,7 +251,7 @@ pub fn delete_plugin<'a, T: DataBlob>( let serialized_registry_record = registry_record.try_to_vec()?; // Only allow the default authority to delete the plugin. - if authority_type != ®istry_record.plugin_type.default_authority() { + if authority_type != ®istry_record.plugin_type.manager() { return Err(MplCoreError::InvalidAuthority.into()); } @@ -369,7 +359,7 @@ pub fn revoke_authority_on_plugin<'a>( solana_program::msg!("registry_record.authority: {:?}", registry_record.authority); // TODO inspect this logic - if (*authority_type != registry_record.plugin_type.default_authority() && + if (*authority_type != registry_record.plugin_type.manager() && // pubkey authorities can remove themselves if they are a signer authority_type != ®istry_record.authority) || // Unable to revoke a None authority @@ -379,7 +369,7 @@ pub fn revoke_authority_on_plugin<'a>( } let authority_bytes = registry_record.authority.try_to_vec()?; - registry_record.authority = registry_record.plugin_type.default_authority(); + registry_record.authority = registry_record.plugin_type.manager(); let new_size = account .data_len() diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index ee781daf..209bd968 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -5,13 +5,15 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ instruction::accounts::{AddCollectionPluginAccounts, AddPluginAccounts}, plugins::{create_meta_idempotent, initialize_plugin, Plugin}, - state::{Asset, Collection}, + state::{Asset, Authority, Collection, DataBlob, SolanaAccount}, + utils::resolve_payer, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub(crate) struct AddPluginArgs { plugin: Plugin, + init_authority: Option<Authority>, } pub(crate) fn add_plugin<'a>( @@ -22,33 +24,22 @@ pub(crate) fn add_plugin<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; - let _default_auth = args.plugin.default_authority(); - - create_meta_idempotent(ctx.accounts.asset, payer, ctx.accounts.system_program)?; - - initialize_plugin::<Asset>( - &args.plugin, - &args.plugin.default_authority(), + process_add_plugin::<Asset>( ctx.accounts.asset, payer, ctx.accounts.system_program, - )?; - - process_add_plugin() + &args.plugin, + &args.init_authority.unwrap_or(args.plugin.manager()), + ) } #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub(crate) struct AddCollectionPluginArgs { plugin: Plugin, + init_authority: Option<Authority>, } pub(crate) fn add_collection_plugin<'a>( @@ -59,30 +50,24 @@ pub(crate) fn add_collection_plugin<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; - - let _default_auth = args.plugin.default_authority(); + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; - create_meta_idempotent(ctx.accounts.collection, payer, ctx.accounts.system_program)?; - - initialize_plugin::<Collection>( - &args.plugin, - &args.plugin.default_authority(), + process_add_plugin::<Collection>( ctx.accounts.collection, payer, ctx.accounts.system_program, - )?; - - process_add_plugin() + &args.plugin, + &args.init_authority.unwrap_or(args.plugin.manager()), + ) } -//TODO -fn process_add_plugin() -> ProgramResult { - Ok(()) +fn process_add_plugin<'a, T: DataBlob + SolanaAccount>( + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, + plugin: &Plugin, + authority: &Authority, +) -> ProgramResult { + create_meta_idempotent::<T>(account, payer, system_program)?; + initialize_plugin::<T>(plugin, authority, account, payer, system_program) } diff --git a/programs/mpl-core/src/processor/approve_plugin_authority.rs b/programs/mpl-core/src/processor/approve_plugin_authority.rs index 3ef4e7c4..f28963f4 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -9,7 +9,7 @@ use crate::{ }, plugins::{approve_authority_on_plugin, PluginType}, state::{Asset, Authority, Collection, CoreAsset, DataBlob, SolanaAccount}, - utils::fetch_core_data, + utils::{fetch_core_data, resolve_payer}, }; #[repr(C)] @@ -27,13 +27,7 @@ pub(crate) fn approve_plugin_authority<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; process_approve_plugin_authority::<Asset>( ctx.accounts.asset, @@ -60,13 +54,7 @@ pub(crate) fn approve_collection_plugin_authority<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; process_approve_plugin_authority::<Collection>( ctx.accounts.collection, diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 68337b91..8ab210e4 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -100,7 +100,7 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P ); if args.data_state == DataState::AccountState { - create_meta_idempotent( + create_meta_idempotent::<Asset>( ctx.accounts.asset, ctx.accounts.payer, ctx.accounts.system_program, @@ -109,7 +109,7 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P for plugin in &args.plugins { initialize_plugin::<Asset>( plugin, - &plugin.default_authority(), + &plugin.manager(), ctx.accounts.asset, ctx.accounts.payer, ctx.accounts.system_program, diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index effae81c..155be97a 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -77,7 +77,7 @@ pub(crate) fn create_collection<'a>( drop(serialized_data); - create_meta_idempotent( + create_meta_idempotent::<Collection>( ctx.accounts.collection, ctx.accounts.payer, ctx.accounts.system_program, @@ -86,7 +86,7 @@ pub(crate) fn create_collection<'a>( for plugin in args.plugins { initialize_plugin::<Collection>( &plugin, - &plugin.default_authority(), + &plugin.manager(), ctx.accounts.collection, ctx.accounts.payer, ctx.accounts.system_program, diff --git a/programs/mpl-core/src/processor/remove_plugin.rs b/programs/mpl-core/src/processor/remove_plugin.rs index 38e973ec..1d6d6dc3 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -6,8 +6,8 @@ use crate::{ error::MplCoreError, instruction::accounts::{RemoveCollectionPluginAccounts, RemovePluginAccounts}, plugins::{delete_plugin, PluginType}, - state::{Asset, Authority, Collection, SolanaAccount, UpdateAuthority}, - utils::fetch_core_data, + state::{Asset, Authority, Collection}, + utils::{fetch_core_data, resolve_payer, resolve_to_authority}, }; #[repr(C)] @@ -24,13 +24,7 @@ pub(crate) fn remove_plugin<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (asset, plugin_header, plugin_registry) = fetch_core_data::<Asset>(ctx.accounts.asset)?; @@ -40,32 +34,8 @@ pub(crate) fn remove_plugin<'a>( } //TODO: Make this better. - let authority_type = if ctx.accounts.authority.key == &asset.owner { - Authority::Owner - } else if let UpdateAuthority::Address(update_authority) = asset.update_authority { - if ctx.accounts.authority.key == &update_authority { - Authority::UpdateAuthority - } else { - return Err(MplCoreError::InvalidAuthority.into()); - } - } else if let UpdateAuthority::Collection(collection_address) = asset.update_authority { - match ctx.accounts.collection { - Some(collection_info) => { - if collection_info.key != &collection_address { - return Err(MplCoreError::InvalidCollection.into()); - } - let collection = Collection::load(collection_info, 0)?; - if ctx.accounts.authority.key == &collection.update_authority { - Authority::UpdateAuthority - } else { - return Err(MplCoreError::InvalidAuthority.into()); - } - } - None => return Err(MplCoreError::InvalidAuthority.into()), - } - } else { - return Err(MplCoreError::InvalidAuthority.into()); - }; + let authority_type = + resolve_to_authority(ctx.accounts.authority, ctx.accounts.collection, &asset)?; delete_plugin( &args.plugin_type, @@ -93,13 +63,7 @@ pub(crate) fn remove_collection_plugin<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (collection, plugin_header, plugin_registry) = fetch_core_data::<Collection>(ctx.accounts.collection)?; diff --git a/programs/mpl-core/src/processor/revoke_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_plugin_authority.rs index 07c40f6e..06a4dd72 100644 --- a/programs/mpl-core/src/processor/revoke_plugin_authority.rs +++ b/programs/mpl-core/src/processor/revoke_plugin_authority.rs @@ -8,8 +8,8 @@ use crate::{ RevokeCollectionPluginAuthorityAccounts, RevokePluginAuthorityAccounts, }, plugins::{revoke_authority_on_plugin, PluginHeader, PluginRegistry, PluginType}, - state::{Asset, Authority, Collection, SolanaAccount, UpdateAuthority}, - utils::fetch_core_data, + state::{Asset, Authority, Collection}, + utils::{fetch_core_data, resolve_payer, resolve_to_authority}, }; #[repr(C)] @@ -26,43 +26,13 @@ pub(crate) fn revoke_plugin_authority<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (asset, plugin_header, mut plugin_registry) = fetch_core_data::<Asset>(ctx.accounts.asset)?; //TODO: Make this better. - let authority_type = if ctx.accounts.authority.key == &asset.owner { - Authority::Owner - } else if asset.update_authority == UpdateAuthority::Address(*ctx.accounts.authority.key) { - Authority::UpdateAuthority - } else if let UpdateAuthority::Collection(collection_address) = asset.update_authority { - match ctx.accounts.collection { - Some(collection_info) => { - if collection_info.key != &collection_address { - return Err(MplCoreError::InvalidCollection.into()); - } - let collection: Collection = Collection::load(collection_info, 0)?; - if ctx.accounts.authority.key == &collection.update_authority { - Authority::UpdateAuthority - } else { - Authority::Pubkey { - address: *ctx.accounts.authority.key, - } - } - } - None => return Err(MplCoreError::InvalidCollection.into()), - } - } else { - Authority::Pubkey { - address: *ctx.accounts.authority.key, - } - }; + let authority_type = + resolve_to_authority(ctx.accounts.authority, ctx.accounts.collection, &asset)?; process_revoke_plugin_authority( ctx.accounts.asset, @@ -89,13 +59,7 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (_, plugin_header, mut plugin_registry) = fetch_core_data::<Collection>(ctx.accounts.collection)?; diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 4181593a..8062a719 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -10,7 +10,8 @@ use crate::{ plugins::{Plugin, PluginType, RegistryRecord}, state::{Asset, Collection, DataBlob, SolanaAccount, UpdateAuthority}, utils::{ - resize_or_reallocate_account, validate_asset_permissions, validate_collection_permissions, + resize_or_reallocate_account, resolve_payer, validate_asset_permissions, + validate_collection_permissions, }, }; @@ -27,13 +28,7 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( ctx.accounts.authority, @@ -151,13 +146,7 @@ pub(crate) fn update_collection<'a>( // Guards. assert_signer(ctx.accounts.authority)?; - let payer = match ctx.accounts.payer { - Some(payer) => { - assert_signer(payer)?; - payer - } - None => ctx.accounts.authority, - }; + let payer = resolve_payer(ctx.accounts.authority, ctx.accounts.payer)?; let (mut asset, plugin_header, plugin_registry) = validate_collection_permissions( ctx.accounts.authority, diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 597c8fc3..9b751e57 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -1,11 +1,13 @@ +use std::collections::BTreeMap; + use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; use num_traits::{FromPrimitive, ToPrimitive}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, program_error::ProgramError, program_memory::sol_memcpy, rent::Rent, system_instruction, sysvar::Sysvar, }; -use std::collections::BTreeMap; use crate::{ error::MplCoreError, @@ -15,7 +17,7 @@ use crate::{ }, state::{ Asset, Authority, Collection, Compressible, CompressionProof, CoreAsset, DataBlob, - HashablePluginSchema, HashedAsset, HashedAssetSchema, Key, SolanaAccount, + HashablePluginSchema, HashedAsset, HashedAssetSchema, Key, SolanaAccount, UpdateAuthority, }, }; @@ -377,7 +379,7 @@ pub fn rebuild_account_state_from_proof_data<'a>( // Add the plugins. if !plugins.is_empty() { - create_meta_idempotent(asset_info, payer, system_program)?; + create_meta_idempotent::<Asset>(asset_info, payer, system_program)?; for plugin in plugins { initialize_plugin::<Asset>( @@ -444,3 +446,51 @@ pub fn compress_into_account_space<'a>( Ok(compression_proof) } + +pub(crate) fn resolve_to_authority( + authority_info: &AccountInfo, + maybe_collection_info: Option<&AccountInfo>, + asset: &Asset, +) -> Result<Authority, ProgramError> { + let authority_type = if authority_info.key == &asset.owner { + Authority::Owner + } else if asset.update_authority == UpdateAuthority::Address(*authority_info.key) { + Authority::UpdateAuthority + } else if let UpdateAuthority::Collection(collection_address) = asset.update_authority { + match maybe_collection_info { + Some(collection_info) => { + if collection_info.key != &collection_address { + return Err(MplCoreError::InvalidCollection.into()); + } + let collection: Collection = Collection::load(collection_info, 0)?; + if authority_info.key == &collection.update_authority { + Authority::UpdateAuthority + } else { + Authority::Pubkey { + address: *authority_info.key, + } + } + } + None => return Err(MplCoreError::InvalidCollection.into()), + } + } else { + Authority::Pubkey { + address: *authority_info.key, + } + }; + Ok(authority_type) +} + +/// Resolves the payer for the transaction for an optional payer pattern. +pub(crate) fn resolve_payer<'a>( + authority: &'a AccountInfo<'a>, + payer: Option<&'a AccountInfo<'a>>, +) -> Result<&'a AccountInfo<'a>, ProgramError> { + match payer { + Some(payer) => { + assert_signer(payer).unwrap(); + Ok(payer) + } + None => Ok(authority), + } +}