From 401743acf7146269eedb316fe025851b9af8d9ee Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Tue, 27 Feb 2024 22:32:36 -0500 Subject: [PATCH] Adding UpdateDelegate for collections. --- clients/js/src/generated/types/index.ts | 1 + clients/js/src/generated/types/plugin.ts | 19 ++- clients/js/src/generated/types/pluginType.ts | 1 + .../js/src/generated/types/updateDelegate.ts | 22 +++ .../js/test/collectionUpdateDelegate.test.ts | 136 ++++++++++++++++++ clients/rust/src/generated/types/mod.rs | 2 + clients/rust/src/generated/types/plugin.rs | 2 + .../rust/src/generated/types/plugin_type.rs | 1 + .../src/generated/types/update_delegate.rs | 13 ++ idls/mpl_core_program.json | 18 +++ programs/mpl-core/src/plugins/collection.rs | 16 ++- programs/mpl-core/src/plugins/lifecycle.rs | 23 ++- programs/mpl-core/src/plugins/mod.rs | 8 ++ .../mpl-core/src/plugins/update_delegate.rs | 98 +++++++++++++ programs/mpl-core/src/plugins/utils.rs | 6 +- .../mpl-core/src/processor/add_authority.rs | 77 +++++++--- programs/mpl-core/src/processor/burn.rs | 2 +- programs/mpl-core/src/processor/create.rs | 2 +- programs/mpl-core/src/processor/transfer.rs | 3 +- programs/mpl-core/src/processor/update.rs | 3 +- programs/mpl-core/src/state/asset.rs | 12 ++ programs/mpl-core/src/state/collection.rs | 12 +- programs/mpl-core/src/state/traits.rs | 11 +- programs/mpl-core/src/utils.rs | 46 +++++- 24 files changed, 490 insertions(+), 44 deletions(-) create mode 100644 clients/js/src/generated/types/updateDelegate.ts create mode 100644 clients/js/test/collectionUpdateDelegate.test.ts create mode 100644 clients/rust/src/generated/types/update_delegate.rs create mode 100644 programs/mpl-core/src/plugins/update_delegate.rs diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 1d71a172..e23143fc 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -24,3 +24,4 @@ export * from './registryRecord'; export * from './royalties'; export * from './ruleSet'; export * from './transfer'; +export * from './updateDelegate'; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index ce520290..c3cd3389 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -26,11 +26,14 @@ import { RoyaltiesArgs, Transfer, TransferArgs, + UpdateDelegate, + UpdateDelegateArgs, getBurnSerializer, getCollectionSerializer, getFreezeSerializer, getRoyaltiesSerializer, getTransferSerializer, + getUpdateDelegateSerializer, } from '.'; export type Plugin = @@ -39,7 +42,8 @@ export type Plugin = | { __kind: 'Freeze'; fields: [Freeze] } | { __kind: 'Burn'; fields: [Burn] } | { __kind: 'Transfer'; fields: [Transfer] } - | { __kind: 'Collection'; fields: [Collection] }; + | { __kind: 'Collection'; fields: [Collection] } + | { __kind: 'UpdateDelegate'; fields: [UpdateDelegate] }; export type PluginArgs = | { __kind: 'Reserved' } @@ -47,7 +51,8 @@ export type PluginArgs = | { __kind: 'Freeze'; fields: [FreezeArgs] } | { __kind: 'Burn'; fields: [BurnArgs] } | { __kind: 'Transfer'; fields: [TransferArgs] } - | { __kind: 'Collection'; fields: [CollectionArgs] }; + | { __kind: 'Collection'; fields: [CollectionArgs] } + | { __kind: 'UpdateDelegate'; fields: [UpdateDelegateArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -83,6 +88,12 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getCollectionSerializer()])], ]), ], + [ + 'UpdateDelegate', + struct>([ + ['fields', tuple([getUpdateDelegateSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -112,6 +123,10 @@ export function plugin( kind: 'Collection', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'UpdateDelegate', + 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 09e39f9e..58bca11f 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -15,6 +15,7 @@ export enum PluginType { Burn, Transfer, Collection, + UpdateDelegate, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/generated/types/updateDelegate.ts b/clients/js/src/generated/types/updateDelegate.ts new file mode 100644 index 00000000..3bb5e03f --- /dev/null +++ b/clients/js/src/generated/types/updateDelegate.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 UpdateDelegate = {}; + +export type UpdateDelegateArgs = UpdateDelegate; + +export function getUpdateDelegateSerializer(): Serializer< + UpdateDelegateArgs, + UpdateDelegate +> { + return struct([], { + description: 'UpdateDelegate', + }) as Serializer; +} diff --git a/clients/js/test/collectionUpdateDelegate.test.ts b/clients/js/test/collectionUpdateDelegate.test.ts new file mode 100644 index 00000000..1ea68ccf --- /dev/null +++ b/clients/js/test/collectionUpdateDelegate.test.ts @@ -0,0 +1,136 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + AssetWithPlugins, + CollectionWithPlugins, + DataState, + PluginType, + addAuthority, + addPlugin, + create, + createCollection, + fetchAssetWithPlugins, + fetchCollectionWithPlugins, +} from '../src'; +import { createUmi } from './_setup'; + +test('it can create a new asset with a collection if it is the collection update delegate', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const collectionAddress = generateSigner(umi); + const assetAddress = generateSigner(umi); + const updateDelegate = generateSigner(umi); + + // When we create a new account. + await createCollection(umi, { + collectionAddress, + name: 'Test Bread Collection', + uri: 'https://example.com/bread', + plugins: [] + }).sendAndConfirm(umi); + + await addPlugin(umi, { + assetAddress: collectionAddress.publicKey, + plugin: { + __kind: 'UpdateDelegate', + fields: [{}], + } + }).sendAndConfirm(umi); + + // console.log(JSON.stringify(await fetchCollectionWithPlugins(umi, collectionAddress.publicKey), (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + + await addAuthority(umi, { + assetAddress: collectionAddress.publicKey, + pluginType: PluginType.UpdateDelegate, + newAuthority: { + __kind: 'Pubkey', + address: updateDelegate.publicKey, + } + }).sendAndConfirm(umi); + + const collection = await fetchCollectionWithPlugins(umi, collectionAddress.publicKey); + // console.log("Account State:", collection); + t.like(collection, { + publicKey: collectionAddress.publicKey, + updateAuthority: umi.identity.publicKey, + name: 'Test Bread Collection', + uri: 'https://example.com/bread', + pluginHeader: { + key: 3, + pluginRegistryOffset: BigInt(105), + }, + pluginRegistry: { + key: 4, + registry: [ + { + pluginType: PluginType.UpdateDelegate, + offset: BigInt(104), + authorities: [ + { __kind: 'UpdateAuthority' }, + { __kind: 'Pubkey', address: updateDelegate.publicKey } + ], + }, + ], + }, + plugins: [ + { + authorities: [ + { __kind: 'UpdateAuthority' }, + { __kind: 'Pubkey', address: updateDelegate.publicKey } + ], + plugin: { + __kind: 'UpdateDelegate', + fields: [{}], + }, + }, + ], + }); + + // When we create a new account. + await create(umi, { + dataState: DataState.AccountState, + assetAddress, + name: 'Test Bread', + uri: 'https://example.com/bread', + collection: collectionAddress.publicKey, + plugins: [{ + __kind: 'Collection', fields: [{ + collectionAddress: collectionAddress.publicKey, + managed: true + }] + }], + }).sendAndConfirm(umi); + + const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); + console.log("Asset State:", asset); + t.like(asset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + pluginHeader: { + key: 3, + pluginRegistryOffset: BigInt(151), + }, + pluginRegistry: { + key: 4, + registry: [ + { + pluginType: PluginType.Collection, + offset: BigInt(117), + authorities: [{ __kind: 'UpdateAuthority' }], + }, + ], + }, + plugins: [ + { + authorities: [{ __kind: 'UpdateAuthority' }], + plugin: { + __kind: 'Collection', + fields: [{ collectionAddress: collectionAddress.publicKey, managed: true }], + }, + }, + ], + }); +}); \ No newline at end of file diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index 13e80187..e7d293e7 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod registry_record; pub(crate) mod royalties; pub(crate) mod rule_set; pub(crate) mod transfer; +pub(crate) mod update_delegate; pub use self::authority::*; pub use self::burn::*; @@ -42,3 +43,4 @@ pub use self::registry_record::*; pub use self::royalties::*; pub use self::rule_set::*; pub use self::transfer::*; +pub use self::update_delegate::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index fc170d0f..9fa18b42 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -10,6 +10,7 @@ use crate::generated::types::Collection; use crate::generated::types::Freeze; use crate::generated::types::Royalties; use crate::generated::types::Transfer; +use crate::generated::types::UpdateDelegate; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -22,4 +23,5 @@ pub enum Plugin { Burn(Burn), Transfer(Transfer), Collection(Collection), + UpdateDelegate(UpdateDelegate), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index 33e18959..61422210 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -17,4 +17,5 @@ pub enum PluginType { Burn, Transfer, Collection, + UpdateDelegate, } diff --git a/clients/rust/src/generated/types/update_delegate.rs b/clients/rust/src/generated/types/update_delegate.rs new file mode 100644 index 00000000..6e1b4312 --- /dev/null +++ b/clients/rust/src/generated/types/update_delegate.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 UpdateDelegate {} diff --git a/idls/mpl_core_program.json b/idls/mpl_core_program.json index 82201e69..713a2052 100644 --- a/idls/mpl_core_program.json +++ b/idls/mpl_core_program.json @@ -1059,6 +1059,13 @@ "fields": [] } }, + { + "name": "UpdateDelegate", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "AddAuthorityArgs", "type": { @@ -1396,6 +1403,14 @@ "defined": "Collection" } ] + }, + { + "name": "UpdateDelegate", + "fields": [ + { + "defined": "UpdateDelegate" + } + ] } ] } @@ -1422,6 +1437,9 @@ }, { "name": "Collection" + }, + { + "name": "UpdateDelegate" } ] } diff --git a/programs/mpl-core/src/plugins/collection.rs b/programs/mpl-core/src/plugins/collection.rs index 7e91fc09..f82c662a 100644 --- a/programs/mpl-core/src/plugins/collection.rs +++ b/programs/mpl-core/src/plugins/collection.rs @@ -7,8 +7,10 @@ use crate::{ BurnAccounts, CompressAccounts, CreateAccounts, DecompressAccounts, TransferAccounts, UpdateAccounts, }, + plugins::{fetch_plugin, PluginType}, processor::{BurnArgs, CompressArgs, CreateArgs, DecompressArgs, TransferArgs, UpdateArgs}, state::{Authority, CollectionData, DataBlob, SolanaAccount}, + utils::assert_collection_authority, }; use super::{PluginValidation, ValidationResult}; @@ -43,8 +45,8 @@ impl PluginValidation for Collection { _authorities: &[Authority], ) -> Result { match ctx.collection { - Some(collection) => { - let collection = CollectionData::load(collection, 0)?; + Some(collection_info) => { + let collection = CollectionData::load(collection_info, 0)?; solana_program::msg!("Collection: {:?}", collection); // Check that the collection update authority is a signer. let authority = match ctx.update_authority { @@ -54,7 +56,15 @@ impl PluginValidation for Collection { assert_signer(authority)?; - if authority.key != &collection.update_authority { + let maybe_update_delegate = + fetch_plugin(collection_info, PluginType::UpdateDelegate); + + if let Ok((mut authorities, _, _)) = maybe_update_delegate { + authorities.push(Authority::UpdateAuthority); + if assert_collection_authority(&collection, authority, &authorities).is_err() { + return Ok(ValidationResult::Rejected); + } + } else if authority.key != &collection.update_authority { return Ok(ValidationResult::Rejected); } diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index ad537712..d6c1390f 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -39,8 +39,8 @@ impl PluginType { /// Check if a plugin is permitted to approve or deny an update action. pub fn check_update(&self) -> CheckResult { - #[allow(clippy::match_single_binding)] match self { + PluginType::Collection => CheckResult::CanApprove, _ => CheckResult::None, } } @@ -97,6 +97,9 @@ impl Plugin { Plugin::Burn(burn) => burn.validate_create(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_create(ctx, args, authorities), Plugin::Collection(collection) => collection.validate_create(ctx, args, authorities), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_create(ctx, args, authorities) + } } } @@ -114,6 +117,9 @@ impl Plugin { Plugin::Burn(burn) => burn.validate_update(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_update(ctx, args, authorities), Plugin::Collection(collection) => collection.validate_update(ctx, args, authorities), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_update(ctx, args, authorities) + } } } @@ -139,6 +145,9 @@ impl Plugin { Plugin::Collection(collection) => { collection.validate_update_plugin(asset, ctx, args, authorities) } + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_update_plugin(asset, ctx, args, authorities) + } } } @@ -156,6 +165,9 @@ impl Plugin { Plugin::Burn(burn) => burn.validate_burn(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_burn(ctx, args, authorities), Plugin::Collection(collection) => collection.validate_burn(ctx, args, authorities), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_burn(ctx, args, authorities) + } } } @@ -173,6 +185,9 @@ impl Plugin { Plugin::Burn(burn) => burn.validate_transfer(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_transfer(ctx, args, authorities), Plugin::Collection(collection) => collection.validate_transfer(ctx, args, authorities), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_transfer(ctx, args, authorities) + } } } @@ -190,6 +205,9 @@ impl Plugin { Plugin::Burn(burn) => burn.validate_compress(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_compress(ctx, args, authorities), Plugin::Collection(collection) => collection.validate_compress(ctx, args, authorities), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_compress(ctx, args, authorities) + } } } @@ -209,6 +227,9 @@ impl Plugin { Plugin::Collection(collection) => { collection.validate_decompress(ctx, args, authorities) } + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_decompress(ctx, args, authorities) + } } } } diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index 32c6f587..2fb92a73 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -6,6 +6,7 @@ mod plugin_header; mod plugin_registry; mod royalties; mod transfer; +mod update_delegate; mod utils; pub use burn::*; @@ -16,6 +17,7 @@ pub use plugin_header::*; pub use plugin_registry::*; pub use royalties::*; pub use transfer::*; +pub use update_delegate::*; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, @@ -45,6 +47,8 @@ pub enum Plugin { Transfer(Transfer), /// Collection plugin. Collection(Collection), + /// Update Delegate plugin. + UpdateDelegate(UpdateDelegate), } impl Plugin { @@ -57,6 +61,7 @@ impl Plugin { Plugin::Burn(_) => Ok(Authority::Owner), Plugin::Transfer(_) => Ok(Authority::Owner), Plugin::Collection(_) => Ok(Authority::UpdateAuthority), + Plugin::UpdateDelegate(_) => Ok(Authority::UpdateAuthority), } } } @@ -79,6 +84,8 @@ pub enum PluginType { Transfer, /// Collection plugin. Collection, + /// Update Delegate plugin. + UpdateDelegate, } impl DataBlob for PluginType { @@ -100,6 +107,7 @@ impl From<&Plugin> for PluginType { Plugin::Burn(_) => PluginType::Burn, Plugin::Transfer(_) => PluginType::Transfer, Plugin::Collection(_) => PluginType::Collection, + Plugin::UpdateDelegate(_) => PluginType::UpdateDelegate, } } } diff --git a/programs/mpl-core/src/plugins/update_delegate.rs b/programs/mpl-core/src/plugins/update_delegate.rs new file mode 100644 index 00000000..c1deaebe --- /dev/null +++ b/programs/mpl-core/src/plugins/update_delegate.rs @@ -0,0 +1,98 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; + +use crate::{ + instruction::accounts::{ + BurnAccounts, CompressAccounts, CreateAccounts, DecompressAccounts, TransferAccounts, + UpdateAccounts, + }, + processor::{BurnArgs, CompressArgs, CreateArgs, DecompressArgs, TransferArgs, UpdateArgs}, + state::{Authority, DataBlob}, +}; + +use super::{PluginValidation, ValidationResult}; + +/// This plugin manages additional permissions to burn. +/// Any authorities approved are given permission to burn the asset on behalf of the owner. +#[repr(C)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct UpdateDelegate {} + +impl UpdateDelegate { + /// Initialize the UpdateDelegate plugin. + pub fn new() -> Self { + Self {} + } +} + +impl Default for UpdateDelegate { + fn default() -> Self { + Self::new() + } +} + +impl DataBlob for UpdateDelegate { + fn get_initial_size() -> usize { + 0 + } + + fn get_size(&self) -> usize { + 0 + } +} + +impl PluginValidation for UpdateDelegate { + fn validate_create( + &self, + _ctx: &CreateAccounts, + _args: &CreateArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_update( + &self, + _ctx: &UpdateAccounts, + _args: &UpdateArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_burn( + &self, + _ctx: &BurnAccounts, + _args: &BurnArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_transfer( + &self, + _ctx: &TransferAccounts, + _args: &TransferArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_compress( + &self, + _ctx: &CompressAccounts, + _args: &CompressArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_decompress( + &self, + _ctx: &DecompressAccounts, + _args: &DecompressArgs, + _authorities: &[Authority], + ) -> Result { + Ok(ValidationResult::Pass) + } +} diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index cdf99ab6..0fc4634e 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -7,7 +7,7 @@ use solana_program::{ use crate::{ error::MplCoreError, - state::{Asset, Authority, CollectionData, DataBlob, Key, SolanaAccount}, + state::{Asset, Authority, CollectionData, CoreAsset, DataBlob, Key, SolanaAccount}, utils::{assert_authority, load_key, resolve_authority_to_default}, }; @@ -290,11 +290,11 @@ pub fn delete_plugin<'a>( /// Add an authority to a plugin. //TODO: Prevent duplicate authorities. #[allow(clippy::too_many_arguments)] -pub fn add_authority_to_plugin<'a>( +pub fn add_authority_to_plugin<'a, T: CoreAsset>( plugin_type: &PluginType, authority: &AccountInfo<'a>, new_authority: &Authority, - asset: &Asset, + asset: &T, account: &AccountInfo<'a>, plugin_header: &PluginHeader, plugin_registry: &mut PluginRegistry, diff --git a/programs/mpl-core/src/processor/add_authority.rs b/programs/mpl-core/src/processor/add_authority.rs index d85566ee..c54d44d5 100644 --- a/programs/mpl-core/src/processor/add_authority.rs +++ b/programs/mpl-core/src/processor/add_authority.rs @@ -6,8 +6,8 @@ use crate::{ error::MplCoreError, instruction::accounts::AddAuthorityAccounts, plugins::{add_authority_to_plugin, PluginType}, - state::Authority, - utils::fetch_core_data, + state::{Asset, Authority, CollectionData, Key}, + utils::{fetch_core_data, load_key}, }; #[repr(C)] @@ -33,29 +33,62 @@ pub(crate) fn add_authority<'a>( None => ctx.accounts.authority, }; - let (asset, plugin_header, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?; + match load_key(ctx.accounts.asset_address, 0)? { + Key::Asset => { + let (asset, plugin_header, plugin_registry) = + fetch_core_data::(ctx.accounts.asset_address)?; - let plugin_header = match plugin_header { - Some(header) => header, - None => return Err(MplCoreError::PluginsNotInitialized.into()), - }; + let plugin_header = match plugin_header { + Some(header) => header, + None => return Err(MplCoreError::PluginsNotInitialized.into()), + }; - let mut plugin_registry = match plugin_registry { - Some(registry) => registry, - None => return Err(MplCoreError::PluginsNotInitialized.into()), - }; + let mut plugin_registry = match plugin_registry { + Some(registry) => registry, + None => return Err(MplCoreError::PluginsNotInitialized.into()), + }; + + add_authority_to_plugin( + &args.plugin_type, + ctx.accounts.authority, + &args.new_authority, + &asset, + ctx.accounts.asset_address, + &plugin_header, + &mut plugin_registry, + payer, + ctx.accounts.system_program, + )?; + } + Key::Collection => { + solana_program::msg!("Add authority to collection"); + let (collection, plugin_header, plugin_registry) = + fetch_core_data::(ctx.accounts.asset_address)?; - add_authority_to_plugin( - &args.plugin_type, - ctx.accounts.authority, - &args.new_authority, - &asset, - ctx.accounts.asset_address, - &plugin_header, - &mut plugin_registry, - payer, - ctx.accounts.system_program, - )?; + let plugin_header = match plugin_header { + Some(header) => header, + None => return Err(MplCoreError::PluginsNotInitialized.into()), + }; + + let mut plugin_registry = match plugin_registry { + Some(registry) => registry, + None => return Err(MplCoreError::PluginsNotInitialized.into()), + }; + + add_authority_to_plugin( + &args.plugin_type, + ctx.accounts.authority, + &args.new_authority, + &collection, + ctx.accounts.asset_address, + &plugin_header, + &mut plugin_registry, + payer, + ctx.accounts.system_program, + )?; + } + _ => return Err(MplCoreError::IncorrectAccount.into()), + } Ok(()) } diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index a56caef5..1770129b 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -41,7 +41,7 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnArgs) -> Progr asset.wrap()?; } Key::Asset => { - let (asset, _, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?; + let (asset, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset_address)?; let mut approved = false; match Asset::check_transfer() { diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 0f3e5d2d..3b73879e 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -104,7 +104,7 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P )?; } - let (_, _, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?; + let (_, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset_address)?; let mut approved = true; // match Asset::check_create() { diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index cc2a9fd2..b1b5ab93 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -77,7 +77,8 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) // } // }?; - let (mut asset, _, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?; + let (mut asset, _, plugin_registry) = + fetch_core_data::(ctx.accounts.asset_address)?; let mut approved = false; match Asset::check_transfer() { diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index a68f6f9f..69d0338e 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -33,7 +33,8 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P None => ctx.accounts.authority, }; - let (mut asset, plugin_header, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?; + let (mut asset, plugin_header, plugin_registry) = + fetch_core_data::(ctx.accounts.asset_address)?; let asset_size = asset.get_size() as isize; let mut approved = false; diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index d8922a8b..03575b04 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -14,6 +14,8 @@ use crate::{ }, }; +use super::CoreAsset; + /// The Core Asset structure that exists at the beginning of every asset account. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount, Eq, PartialEq)] pub struct Asset { @@ -151,3 +153,13 @@ impl From for Asset { } } } + +impl CoreAsset for Asset { + fn update_authority(&self) -> &Pubkey { + &self.update_authority + } + + fn owner(&self) -> &Pubkey { + &self.owner + } +} diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index 765d8b1d..171d9a87 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; -use super::{DataBlob, Key, SolanaAccount}; +use super::{CoreAsset, DataBlob, Key, SolanaAccount}; /// The representation of a collection of assets. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] @@ -59,3 +59,13 @@ impl SolanaAccount for CollectionData { Key::Collection } } + +impl CoreAsset for CollectionData { + fn update_authority(&self) -> &Pubkey { + &self.update_authority + } + + fn owner(&self) -> &Pubkey { + &self.update_authority + } +} diff --git a/programs/mpl-core/src/state/traits.rs b/programs/mpl-core/src/state/traits.rs index 80253016..97552db8 100644 --- a/programs/mpl-core/src/state/traits.rs +++ b/programs/mpl-core/src/state/traits.rs @@ -2,7 +2,7 @@ use crate::{error::MplCoreError, state::Key, utils::load_key}; use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, keccak, msg, program::invoke, - program_error::ProgramError, + program_error::ProgramError, pubkey::Pubkey, }; /// A trait for generic blobs of data that have size. @@ -56,3 +56,12 @@ pub trait Compressible: BorshSerialize + BorshDeserialize { invoke(&spl_noop::instruction(serialized_data), &[]) } } + +/// A trait for core assets. +pub trait CoreAsset { + /// Get the update authority of the asset. + fn update_authority(&self) -> &Pubkey; + + /// Get the owner of the asset. + fn owner(&self) -> &Pubkey; +} diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 9b9434dc..173ea5ed 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -7,7 +7,7 @@ use solana_program::{ use crate::{ error::MplCoreError, plugins::{PluginHeader, PluginRegistry}, - state::{Asset, Authority, DataBlob, Key, SolanaAccount}, + state::{Asset, Authority, CollectionData, CoreAsset, DataBlob, Key, SolanaAccount}, }; /// Load the one byte key from the account data at the given offset. @@ -19,19 +19,51 @@ pub fn load_key(account: &AccountInfo, offset: usize) -> Result( + asset: &T, authority: &AccountInfo, authorities: &[Authority], ) -> ProgramResult { + solana_program::msg!("Update authority: {:?}", asset.update_authority()); for auth_iter in authorities { + solana_program::msg!("Check if {:?} matches {:?}", authority.key, auth_iter); match auth_iter { Authority::None => (), Authority::Owner => { - if &asset.owner == authority.key { + if asset.owner() == authority.key { return Ok(()); } } + Authority::UpdateAuthority => { + if asset.update_authority() == authority.key { + return Ok(()); + } + } + Authority::Pubkey { address } => { + if authority.key == address { + return Ok(()); + } + } + Authority::Permanent { address } => { + if authority.key == address { + return Ok(()); + } + } + } + } + + Err(MplCoreError::InvalidAuthority.into()) +} + +/// Assert that the account info address is in the authorities array. +pub fn assert_collection_authority( + asset: &CollectionData, + authority: &AccountInfo, + authorities: &[Authority], +) -> ProgramResult { + for auth_iter in authorities { + match auth_iter { + Authority::None | Authority::Owner => (), Authority::UpdateAuthority => { if &asset.update_authority == authority.key { return Ok(()); @@ -63,10 +95,10 @@ pub fn resolve_authority_to_default(asset: &Asset, authority: &AccountInfo) -> A } /// Fetch the core data from the account; asset, plugin header (if present), and plugin registry (if present). -pub fn fetch_core_data( +pub fn fetch_core_data( account: &AccountInfo, -) -> Result<(Asset, Option, Option), ProgramError> { - let asset = Asset::load(account, 0)?; +) -> Result<(T, Option, Option), ProgramError> { + let asset = T::load(account, 0)?; if asset.get_size() != account.data_len() { let plugin_header = PluginHeader::load(account, asset.get_size())?;