From 885328d6691cddd8aff17456191557c77111bb59 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 20:12:44 -0700 Subject: [PATCH] Working basic delegate. --- clients/js/src/generated/accounts/index.ts | 2 + .../js/src/generated/accounts/pluginHeader.ts | 127 +++++++++++++++ .../src/generated/accounts/pluginRegistry.ts | 152 ++++++++++++++++++ clients/js/src/generated/errors/mplAsset.ts | 21 ++- clients/js/src/generated/types/authority.ts | 12 ++ clients/js/src/generated/types/delegate.ts | 15 +- .../generated/types/externalPluginRecord.ts | 37 +++++ clients/js/src/generated/types/index.ts | 4 +- clients/js/src/generated/types/key.ts | 1 + .../js/src/generated/types/pluginHeader.ts | 30 ---- .../js/src/generated/types/pluginRegistry.ts | 57 ------- .../js/src/generated/types/registryRecord.ts | 34 ++++ clients/js/test/delegate.test.ts | 10 +- clients/rust/src/generated/accounts/mod.rs | 4 + .../src/generated/accounts/plugin_header.rs | 38 +++++ .../src/generated/accounts/plugin_registry.rs | 39 +++++ .../rust/src/generated/errors/mpl_asset.rs | 7 +- clients/rust/src/generated/types/authority.rs | 7 + clients/rust/src/generated/types/delegate.rs | 2 + ..._registry.rs => external_plugin_record.rs} | 8 +- clients/rust/src/generated/types/key.rs | 1 + clients/rust/src/generated/types/mod.rs | 8 +- .../{plugin_header.rs => registry_record.rs} | 5 +- idls/mpl_asset_program.json | 133 ++++++++++----- programs/mpl-asset/src/plugins/delegate.rs | 21 ++- programs/mpl-asset/src/plugins/mod.rs | 29 ++-- programs/mpl-asset/src/plugins/utils.rs | 74 ++++++--- programs/mpl-asset/src/processor/delegate.rs | 31 +++- programs/mpl-asset/src/state/mod.rs | 7 + programs/mpl-asset/src/state/plugin_header.rs | 3 +- 30 files changed, 721 insertions(+), 198 deletions(-) create mode 100644 clients/js/src/generated/accounts/pluginHeader.ts create mode 100644 clients/js/src/generated/accounts/pluginRegistry.ts create mode 100644 clients/js/src/generated/types/externalPluginRecord.ts delete mode 100644 clients/js/src/generated/types/pluginHeader.ts delete mode 100644 clients/js/src/generated/types/pluginRegistry.ts create mode 100644 clients/js/src/generated/types/registryRecord.ts create mode 100644 clients/rust/src/generated/accounts/plugin_header.rs create mode 100644 clients/rust/src/generated/accounts/plugin_registry.rs rename clients/rust/src/generated/types/{plugin_registry.rs => external_plugin_record.rs} (74%) rename clients/rust/src/generated/types/{plugin_header.rs => registry_record.rs} (83%) diff --git a/clients/js/src/generated/accounts/index.ts b/clients/js/src/generated/accounts/index.ts index a9c118f1..2419a1f2 100644 --- a/clients/js/src/generated/accounts/index.ts +++ b/clients/js/src/generated/accounts/index.ts @@ -8,3 +8,5 @@ export * from './asset'; export * from './hashedAsset'; +export * from './pluginHeader'; +export * from './pluginRegistry'; diff --git a/clients/js/src/generated/accounts/pluginHeader.ts b/clients/js/src/generated/accounts/pluginHeader.ts new file mode 100644 index 00000000..c5d96a0e --- /dev/null +++ b/clients/js/src/generated/accounts/pluginHeader.ts @@ -0,0 +1,127 @@ +/** + * 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 { + Account, + Context, + Pda, + PublicKey, + RpcAccount, + RpcGetAccountOptions, + RpcGetAccountsOptions, + assertAccountExists, + deserializeAccount, + gpaBuilder, + publicKey as toPublicKey, +} from '@metaplex-foundation/umi'; +import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '../types'; + +export type PluginHeader = Account; + +export type PluginHeaderAccountData = { + key: Key; + pluginRegistryOffset: bigint; +}; + +export type PluginHeaderAccountDataArgs = { + key: KeyArgs; + pluginRegistryOffset: number | bigint; +}; + +export function getPluginHeaderAccountDataSerializer(): Serializer< + PluginHeaderAccountDataArgs, + PluginHeaderAccountData +> { + return struct( + [ + ['key', getKeySerializer()], + ['pluginRegistryOffset', u64()], + ], + { description: 'PluginHeaderAccountData' } + ) as Serializer; +} + +export function deserializePluginHeader(rawAccount: RpcAccount): PluginHeader { + return deserializeAccount(rawAccount, getPluginHeaderAccountDataSerializer()); +} + +export async function fetchPluginHeader( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + assertAccountExists(maybeAccount, 'PluginHeader'); + return deserializePluginHeader(maybeAccount); +} + +export async function safeFetchPluginHeader( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + return maybeAccount.exists ? deserializePluginHeader(maybeAccount) : null; +} + +export async function fetchAllPluginHeader( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts.map((maybeAccount) => { + assertAccountExists(maybeAccount, 'PluginHeader'); + return deserializePluginHeader(maybeAccount); + }); +} + +export async function safeFetchAllPluginHeader( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts + .filter((maybeAccount) => maybeAccount.exists) + .map((maybeAccount) => deserializePluginHeader(maybeAccount as RpcAccount)); +} + +export function getPluginHeaderGpaBuilder( + context: Pick +) { + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + return gpaBuilder(context, programId) + .registerFields<{ key: KeyArgs; pluginRegistryOffset: number | bigint }>({ + key: [0, getKeySerializer()], + pluginRegistryOffset: [1, u64()], + }) + .deserializeUsing((account) => + deserializePluginHeader(account) + ); +} + +export function getPluginHeaderSize(): number { + return 9; +} diff --git a/clients/js/src/generated/accounts/pluginRegistry.ts b/clients/js/src/generated/accounts/pluginRegistry.ts new file mode 100644 index 00000000..aaec2e54 --- /dev/null +++ b/clients/js/src/generated/accounts/pluginRegistry.ts @@ -0,0 +1,152 @@ +/** + * 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 { + Account, + Context, + Pda, + PublicKey, + RpcAccount, + RpcGetAccountOptions, + RpcGetAccountsOptions, + assertAccountExists, + deserializeAccount, + gpaBuilder, + publicKey as toPublicKey, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + ExternalPluginRecord, + ExternalPluginRecordArgs, + Key, + KeyArgs, + RegistryRecord, + RegistryRecordArgs, + getExternalPluginRecordSerializer, + getKeySerializer, + getRegistryRecordSerializer, +} from '../types'; + +export type PluginRegistry = Account; + +export type PluginRegistryAccountData = { + key: Key; + registry: Array; + externalPlugins: Array; +}; + +export type PluginRegistryAccountDataArgs = { + key: KeyArgs; + registry: Array; + externalPlugins: Array; +}; + +export function getPluginRegistryAccountDataSerializer(): Serializer< + PluginRegistryAccountDataArgs, + PluginRegistryAccountData +> { + return struct( + [ + ['key', getKeySerializer()], + ['registry', array(getRegistryRecordSerializer())], + ['externalPlugins', array(getExternalPluginRecordSerializer())], + ], + { description: 'PluginRegistryAccountData' } + ) as Serializer; +} + +export function deserializePluginRegistry( + rawAccount: RpcAccount +): PluginRegistry { + return deserializeAccount( + rawAccount, + getPluginRegistryAccountDataSerializer() + ); +} + +export async function fetchPluginRegistry( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + assertAccountExists(maybeAccount, 'PluginRegistry'); + return deserializePluginRegistry(maybeAccount); +} + +export async function safeFetchPluginRegistry( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + return maybeAccount.exists ? deserializePluginRegistry(maybeAccount) : null; +} + +export async function fetchAllPluginRegistry( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts.map((maybeAccount) => { + assertAccountExists(maybeAccount, 'PluginRegistry'); + return deserializePluginRegistry(maybeAccount); + }); +} + +export async function safeFetchAllPluginRegistry( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts + .filter((maybeAccount) => maybeAccount.exists) + .map((maybeAccount) => + deserializePluginRegistry(maybeAccount as RpcAccount) + ); +} + +export function getPluginRegistryGpaBuilder( + context: Pick +) { + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + return gpaBuilder(context, programId) + .registerFields<{ + key: KeyArgs; + registry: Array; + externalPlugins: Array; + }>({ + key: [0, getKeySerializer()], + registry: [1, array(getRegistryRecordSerializer())], + externalPlugins: [null, array(getExternalPluginRecordSerializer())], + }) + .deserializeUsing((account) => + deserializePluginRegistry(account) + ); +} diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index 71538e57..36cc5d75 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -80,30 +80,43 @@ export class PluginNotFoundError extends ProgramError { codeToErrorMap.set(0x4, PluginNotFoundError); nameToErrorMap.set('PluginNotFound', PluginNotFoundError); +/** NumericalOverflow: Numerical Overflow */ +export class NumericalOverflowError extends ProgramError { + readonly name: string = 'NumericalOverflow'; + + readonly code: number = 0x5; // 5 + + constructor(program: Program, cause?: Error) { + super('Numerical Overflow', program, cause); + } +} +codeToErrorMap.set(0x5, NumericalOverflowError); +nameToErrorMap.set('NumericalOverflow', NumericalOverflowError); + /** IncorrectAccount: Incorrect account */ export class IncorrectAccountError extends ProgramError { readonly name: string = 'IncorrectAccount'; - readonly code: number = 0x5; // 5 + readonly code: number = 0x6; // 6 constructor(program: Program, cause?: Error) { super('Incorrect account', program, cause); } } -codeToErrorMap.set(0x5, IncorrectAccountError); +codeToErrorMap.set(0x6, IncorrectAccountError); nameToErrorMap.set('IncorrectAccount', IncorrectAccountError); /** IncorrectAssetHash: Incorrect asset hash */ export class IncorrectAssetHashError extends ProgramError { readonly name: string = 'IncorrectAssetHash'; - readonly code: number = 0x6; // 6 + readonly code: number = 0x7; // 7 constructor(program: Program, cause?: Error) { super('Incorrect asset hash', program, cause); } } -codeToErrorMap.set(0x6, IncorrectAssetHashError); +codeToErrorMap.set(0x7, IncorrectAssetHashError); nameToErrorMap.set('IncorrectAssetHash', IncorrectAssetHashError); /** diff --git a/clients/js/src/generated/types/authority.ts b/clients/js/src/generated/types/authority.ts index 427fbdc3..e33ad202 100644 --- a/clients/js/src/generated/types/authority.ts +++ b/clients/js/src/generated/types/authority.ts @@ -20,12 +20,14 @@ import { Plugin, PluginArgs, getPluginSerializer } from '.'; export type Authority = | { __kind: 'Owner' } + | { __kind: 'Pubkey'; address: PublicKey } | { __kind: 'Permanent'; address: PublicKey } | { __kind: 'SameAs'; plugin: Plugin } | { __kind: 'Collection' }; export type AuthorityArgs = | { __kind: 'Owner' } + | { __kind: 'Pubkey'; address: PublicKey } | { __kind: 'Permanent'; address: PublicKey } | { __kind: 'SameAs'; plugin: PluginArgs } | { __kind: 'Collection' }; @@ -34,6 +36,12 @@ export function getAuthoritySerializer(): Serializer { return dataEnum( [ ['Owner', unit()], + [ + 'Pubkey', + struct>([ + ['address', publicKeySerializer()], + ]), + ], [ 'Permanent', struct>([ @@ -56,6 +64,10 @@ export function getAuthoritySerializer(): Serializer { export function authority( kind: 'Owner' ): GetDataEnumKind; +export function authority( + kind: 'Pubkey', + data: GetDataEnumKindContent +): GetDataEnumKind; export function authority( kind: 'Permanent', data: GetDataEnumKindContent diff --git a/clients/js/src/generated/types/delegate.ts b/clients/js/src/generated/types/delegate.ts index d775b8ce..55ad2c58 100644 --- a/clients/js/src/generated/types/delegate.ts +++ b/clients/js/src/generated/types/delegate.ts @@ -7,13 +7,18 @@ */ import { Serializer, bool, struct } from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '.'; -export type Delegate = { frozen: boolean }; +export type Delegate = { key: Key; frozen: boolean }; -export type DelegateArgs = Delegate; +export type DelegateArgs = { key: KeyArgs; frozen: boolean }; export function getDelegateSerializer(): Serializer { - return struct([['frozen', bool()]], { - description: 'Delegate', - }) as Serializer; + return struct( + [ + ['key', getKeySerializer()], + ['frozen', bool()], + ], + { description: 'Delegate' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/externalPluginRecord.ts b/clients/js/src/generated/types/externalPluginRecord.ts new file mode 100644 index 00000000..acb1f9b3 --- /dev/null +++ b/clients/js/src/generated/types/externalPluginRecord.ts @@ -0,0 +1,37 @@ +/** + * 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'; +import { + Authority, + AuthorityArgs, + RegistryData, + RegistryDataArgs, + getAuthoritySerializer, + getRegistryDataSerializer, +} from '.'; + +export type ExternalPluginRecord = { authority: Authority; data: RegistryData }; + +export type ExternalPluginRecordArgs = { + authority: AuthorityArgs; + data: RegistryDataArgs; +}; + +export function getExternalPluginRecordSerializer(): Serializer< + ExternalPluginRecordArgs, + ExternalPluginRecord +> { + return struct( + [ + ['authority', getAuthoritySerializer()], + ['data', getRegistryDataSerializer()], + ], + { description: 'ExternalPluginRecord' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index b5c2dccd..1695fdb1 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -12,11 +12,11 @@ export * from './compressionProof'; export * from './creator'; export * from './dataState'; export * from './delegate'; +export * from './externalPluginRecord'; export * from './extraAccounts'; export * from './key'; export * from './migrationLevel'; export * from './plugin'; -export * from './pluginHeader'; -export * from './pluginRegistry'; export * from './registryData'; +export * from './registryRecord'; export * from './royalties'; diff --git a/clients/js/src/generated/types/key.ts b/clients/js/src/generated/types/key.ts index 169a0952..8558a0b3 100644 --- a/clients/js/src/generated/types/key.ts +++ b/clients/js/src/generated/types/key.ts @@ -16,6 +16,7 @@ export enum Key { HashedCollection, PluginHeader, PluginRegistry, + Delegate, } export type KeyArgs = Key; diff --git a/clients/js/src/generated/types/pluginHeader.ts b/clients/js/src/generated/types/pluginHeader.ts deleted file mode 100644 index 696a2ee9..00000000 --- a/clients/js/src/generated/types/pluginHeader.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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, u64 } from '@metaplex-foundation/umi/serializers'; -import { Key, KeyArgs, getKeySerializer } from '.'; - -export type PluginHeader = { key: Key; pluginMapOffset: bigint }; - -export type PluginHeaderArgs = { - key: KeyArgs; - pluginMapOffset: number | bigint; -}; - -export function getPluginHeaderSerializer(): Serializer< - PluginHeaderArgs, - PluginHeader -> { - return struct( - [ - ['key', getKeySerializer()], - ['pluginMapOffset', u64()], - ], - { description: 'PluginHeader' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/pluginRegistry.ts b/clients/js/src/generated/types/pluginRegistry.ts deleted file mode 100644 index 9d265fc8..00000000 --- a/clients/js/src/generated/types/pluginRegistry.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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, - array, - struct, - tuple, -} from '@metaplex-foundation/umi/serializers'; -import { - Authority, - AuthorityArgs, - Key, - KeyArgs, - RegistryData, - RegistryDataArgs, - getAuthoritySerializer, - getKeySerializer, - getRegistryDataSerializer, -} from '.'; - -export type PluginRegistry = { - key: Key; - registry: Array<[Key, RegistryData]>; - externalPlugins: Array<[Authority, RegistryData]>; -}; - -export type PluginRegistryArgs = { - key: KeyArgs; - registry: Array<[KeyArgs, RegistryDataArgs]>; - externalPlugins: Array<[AuthorityArgs, RegistryDataArgs]>; -}; - -export function getPluginRegistrySerializer(): Serializer< - PluginRegistryArgs, - PluginRegistry -> { - return struct( - [ - ['key', getKeySerializer()], - [ - 'registry', - array(tuple([getKeySerializer(), getRegistryDataSerializer()])), - ], - [ - 'externalPlugins', - array(tuple([getAuthoritySerializer(), getRegistryDataSerializer()])), - ], - ], - { description: 'PluginRegistry' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/registryRecord.ts b/clients/js/src/generated/types/registryRecord.ts new file mode 100644 index 00000000..2680179a --- /dev/null +++ b/clients/js/src/generated/types/registryRecord.ts @@ -0,0 +1,34 @@ +/** + * 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'; +import { + Key, + KeyArgs, + RegistryData, + RegistryDataArgs, + getKeySerializer, + getRegistryDataSerializer, +} from '.'; + +export type RegistryRecord = { key: Key; data: RegistryData }; + +export type RegistryRecordArgs = { key: KeyArgs; data: RegistryDataArgs }; + +export function getRegistryRecordSerializer(): Serializer< + RegistryRecordArgs, + RegistryRecord +> { + return struct( + [ + ['key', getKeySerializer()], + ['data', getRegistryDataSerializer()], + ], + { description: 'RegistryRecord' } + ) as Serializer; +} diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts index 82d4af53..b8ac7614 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/delegate.test.ts @@ -1,7 +1,7 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; // import { base58 } from '@metaplex-foundation/umi/serializers'; -import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getPluginHeaderSerializer, getPluginRegistrySerializer, } from '../src'; +import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getDelegateSerializer, getPluginHeaderAccountDataSerializer, getPluginRegistryAccountDataSerializer, } from '../src'; import { createUmi } from './_setup'; test('it initializes the plugin system correctly', async (t) => { @@ -31,10 +31,12 @@ test('it initializes the plugin system correctly', async (t) => { const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); if (pluginData.exists) { - const pluginHeader = getPluginHeaderSerializer().deserialize(pluginData.data, assetData.length); + const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(pluginData.data, assetData.length)[0]; console.log("After Plugins:\n", pluginHeader); - const pluginRegistry = getPluginRegistrySerializer().deserialize(pluginData.data, Number(pluginHeader[0].pluginMapOffset)); - console.log(pluginRegistry); + const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset)); + console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry[0].registry[0].data.offset)); + console.log(delegatePlugin); } else { t.fail("Plugin data not found"); } diff --git a/clients/rust/src/generated/accounts/mod.rs b/clients/rust/src/generated/accounts/mod.rs index 0e5a968c..6f60f76e 100644 --- a/clients/rust/src/generated/accounts/mod.rs +++ b/clients/rust/src/generated/accounts/mod.rs @@ -7,6 +7,10 @@ pub(crate) mod asset; pub(crate) mod hashed_asset; +pub(crate) mod plugin_header; +pub(crate) mod plugin_registry; pub use self::asset::*; pub use self::hashed_asset::*; +pub use self::plugin_header::*; +pub use self::plugin_registry::*; diff --git a/clients/rust/src/generated/accounts/plugin_header.rs b/clients/rust/src/generated/accounts/plugin_header.rs new file mode 100644 index 00000000..f8bdd5aa --- /dev/null +++ b/clients/rust/src/generated/accounts/plugin_header.rs @@ -0,0 +1,38 @@ +//! 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 crate::generated::types::Key; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PluginHeader { + pub key: Key, + pub plugin_registry_offset: u64, +} + +impl PluginHeader { + pub const LEN: usize = 9; + + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for PluginHeader { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} diff --git a/clients/rust/src/generated/accounts/plugin_registry.rs b/clients/rust/src/generated/accounts/plugin_registry.rs new file mode 100644 index 00000000..3f28b1ed --- /dev/null +++ b/clients/rust/src/generated/accounts/plugin_registry.rs @@ -0,0 +1,39 @@ +//! 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 crate::generated::types::ExternalPluginRecord; +use crate::generated::types::Key; +use crate::generated::types::RegistryRecord; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PluginRegistry { + pub key: Key, + pub registry: Vec, + pub external_plugins: Vec, +} + +impl PluginRegistry { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for PluginRegistry { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index 8f2ab99c..3dbe990d 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -25,10 +25,13 @@ pub enum MplAssetError { /// 4 (0x4) - Plugin not found #[error("Plugin not found")] PluginNotFound, - /// 5 (0x5) - Incorrect account + /// 5 (0x5) - Numerical Overflow + #[error("Numerical Overflow")] + NumericalOverflow, + /// 6 (0x6) - Incorrect account #[error("Incorrect account")] IncorrectAccount, - /// 6 (0x6) - Incorrect asset hash + /// 7 (0x7) - Incorrect asset hash #[error("Incorrect asset hash")] IncorrectAssetHash, } diff --git a/clients/rust/src/generated/types/authority.rs b/clients/rust/src/generated/types/authority.rs index 42db0ebc..f8ae9ba3 100644 --- a/clients/rust/src/generated/types/authority.rs +++ b/clients/rust/src/generated/types/authority.rs @@ -14,6 +14,13 @@ use solana_program::pubkey::Pubkey; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Authority { Owner, + Pubkey { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + address: Pubkey, + }, Permanent { #[cfg_attr( feature = "serde", diff --git a/clients/rust/src/generated/types/delegate.rs b/clients/rust/src/generated/types/delegate.rs index 889bbe82..8a51bd3d 100644 --- a/clients/rust/src/generated/types/delegate.rs +++ b/clients/rust/src/generated/types/delegate.rs @@ -5,11 +5,13 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Key; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Delegate { + pub key: Key, pub frozen: bool, } diff --git a/clients/rust/src/generated/types/plugin_registry.rs b/clients/rust/src/generated/types/external_plugin_record.rs similarity index 74% rename from clients/rust/src/generated/types/plugin_registry.rs rename to clients/rust/src/generated/types/external_plugin_record.rs index e9966856..cfad2374 100644 --- a/clients/rust/src/generated/types/plugin_registry.rs +++ b/clients/rust/src/generated/types/external_plugin_record.rs @@ -6,15 +6,13 @@ //! use crate::generated::types::Authority; -use crate::generated::types::Key; use crate::generated::types::RegistryData; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PluginRegistry { - pub key: Key, - pub registry: Vec<(Key, RegistryData)>, - pub external_plugins: Vec<(Authority, RegistryData)>, +pub struct ExternalPluginRecord { + pub authority: Authority, + pub data: RegistryData, } diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index 1da6dc86..956e7fea 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -18,4 +18,5 @@ pub enum Key { HashedCollection, PluginHeader, PluginRegistry, + Delegate, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index f5487ca3..8481e0fc 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -11,13 +11,13 @@ pub(crate) mod compression_proof; pub(crate) mod creator; pub(crate) mod data_state; pub(crate) mod delegate; +pub(crate) mod external_plugin_record; pub(crate) mod extra_accounts; pub(crate) mod key; pub(crate) mod migration_level; pub(crate) mod plugin; -pub(crate) mod plugin_header; -pub(crate) mod plugin_registry; pub(crate) mod registry_data; +pub(crate) mod registry_record; pub(crate) mod royalties; pub use self::authority::*; @@ -26,11 +26,11 @@ pub use self::compression_proof::*; pub use self::creator::*; pub use self::data_state::*; pub use self::delegate::*; +pub use self::external_plugin_record::*; pub use self::extra_accounts::*; pub use self::key::*; pub use self::migration_level::*; pub use self::plugin::*; -pub use self::plugin_header::*; -pub use self::plugin_registry::*; pub use self::registry_data::*; +pub use self::registry_record::*; pub use self::royalties::*; diff --git a/clients/rust/src/generated/types/plugin_header.rs b/clients/rust/src/generated/types/registry_record.rs similarity index 83% rename from clients/rust/src/generated/types/plugin_header.rs rename to clients/rust/src/generated/types/registry_record.rs index 083a35b5..741601f8 100644 --- a/clients/rust/src/generated/types/plugin_header.rs +++ b/clients/rust/src/generated/types/registry_record.rs @@ -6,12 +6,13 @@ //! use crate::generated::types::Key; +use crate::generated::types::RegistryData; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PluginHeader { +pub struct RegistryRecord { pub key: Key, - pub plugin_map_offset: u64, + pub data: RegistryData, } diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index a914c24f..c5f55b0d 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -699,6 +699,36 @@ } ], "accounts": [ + { + "name": "PluginRegistry", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "registry", + "type": { + "vec": { + "defined": "RegistryRecord" + } + } + }, + { + "name": "externalPlugins", + "type": { + "vec": { + "defined": "ExternalPluginRecord" + } + } + } + ] + } + }, { "name": "Asset", "type": { @@ -751,6 +781,24 @@ } ] } + }, + { + "name": "PluginHeader", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "pluginRegistryOffset", + "type": "u64" + } + ] + } } ], "types": [ @@ -775,7 +823,7 @@ } }, { - "name": "PluginRegistry", + "name": "RegistryRecord", "type": { "kind": "struct", "fields": [ @@ -786,33 +834,29 @@ } }, { - "name": "registry", + "name": "data", "type": { - "vec": { - "tuple": [ - { - "defined": "Key" - }, - { - "defined": "RegistryData" - } - ] - } + "defined": "RegistryData" + } + } + ] + } + }, + { + "name": "ExternalPluginRecord", + "type": { + "kind": "struct", + "fields": [ + { + "name": "authority", + "type": { + "defined": "Authority" } }, { - "name": "externalPlugins", + "name": "data", "type": { - "vec": { - "tuple": [ - { - "defined": "Authority" - }, - { - "defined": "RegistryData" - } - ] - } + "defined": "RegistryData" } } ] @@ -839,6 +883,12 @@ "type": { "kind": "struct", "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, { "name": "frozen", "type": "bool" @@ -1030,24 +1080,6 @@ ] } }, - { - "name": "PluginHeader", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "pluginMapOffset", - "type": "u64" - } - ] - } - }, { "name": "Plugin", "type": { @@ -1101,6 +1133,15 @@ { "name": "Owner" }, + { + "name": "Pubkey", + "fields": [ + { + "name": "address", + "type": "publicKey" + } + ] + }, { "name": "Permanent", "fields": [ @@ -1195,6 +1236,9 @@ }, { "name": "PluginRegistry" + }, + { + "name": "Delegate" } ] } @@ -1242,11 +1286,16 @@ }, { "code": 5, + "name": "NumericalOverflow", + "msg": "Numerical Overflow" + }, + { + "code": 6, "name": "IncorrectAccount", "msg": "Incorrect account" }, { - "code": 6, + "code": 7, "name": "IncorrectAssetHash", "msg": "Incorrect asset hash" } diff --git a/programs/mpl-asset/src/plugins/delegate.rs b/programs/mpl-asset/src/plugins/delegate.rs index df980666..e8b18d43 100644 --- a/programs/mpl-asset/src/plugins/delegate.rs +++ b/programs/mpl-asset/src/plugins/delegate.rs @@ -1,11 +1,28 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; use crate::{state::Key, utils::DataBlob}; +#[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct Delegate { - key: Key, // 1 - frozen: bool, // 1 + pub key: Key, // 1 + pub frozen: bool, // 1 +} + +impl Delegate { + pub fn new() -> Self { + Self { + key: Key::Delegate, + frozen: false, + } + } +} + +impl Default for Delegate { + fn default() -> Self { + Self::new() + } } impl DataBlob for Delegate { diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index 635d15e8..27235a3b 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -7,15 +7,12 @@ pub use collection::*; pub use delegate::*; pub use royalties::*; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, -}; +use shank::ShankAccount; pub use utils::*; use borsh::{BorshDeserialize, BorshSerialize}; use crate::{ - error::MplAssetError, state::{Authority, Key}, utils::DataBlob, }; @@ -53,10 +50,24 @@ pub struct RegistryData { #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] -pub struct PluginRegistry { +pub struct RegistryRecord { pub key: Key, - pub registry: Vec<(Key, RegistryData)>, // 4 - pub external_plugins: Vec<(Authority, RegistryData)>, + pub data: RegistryData, +} + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct ExternalPluginRecord { + pub authority: Authority, + pub data: RegistryData, +} + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] +pub struct PluginRegistry { + pub key: Key, // 1 + pub registry: Vec, // 4 + pub external_plugins: Vec, // 4 } // impl PluginRegistry { @@ -82,11 +93,11 @@ impl DataBlob for PluginRegistry { } fn get_initial_size() -> usize { - 4 + 9 } fn get_size(&self) -> usize { - 4 //TODO: Fix this + 9 //TODO: Fix this } } diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 400f2e30..fd059b6b 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -10,7 +10,7 @@ use crate::{ utils::DataBlob, }; -use super::{Plugin, PluginRegistry, RegistryData}; +use super::{Plugin, PluginRegistry, RegistryData, RegistryRecord}; /// Create plugin header and registry if it doesn't exist pub fn create_idempotent<'a>( @@ -76,8 +76,13 @@ pub fn fetch_plugin( // Find the plugin in the registry. let plugin_data = registry .iter() - .find(|(plugin_key, _)| *plugin_key == key) - .map(|(_, data)| data) + .find( + |RegistryRecord { + key: plugin_key, + data: _, + }| *plugin_key == key, + ) + .map(|RegistryRecord { key: _, data }| data) .ok_or(MplAssetError::PluginNotFound)?; // Deserialize the plugin. @@ -96,7 +101,10 @@ pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { let PluginRegistry { registry, .. } = PluginRegistry::load(account, header.plugin_registry_offset)?; - Ok(registry.iter().map(|(key, _)| *key).collect()) + Ok(registry + .iter() + .map(|registry_record| registry_record.key) + .collect()) } /// Add a plugin into the registry @@ -124,16 +132,20 @@ pub fn add_plugin<'a>( Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), Plugin::Inscription => todo!(), }; - - if let Some((_, registry_data)) = plugin_registry - .registry - .iter_mut() - .find(|(search_key, _)| search_key == &key) - { + let authority_bytes = authority.try_to_vec()?; + + if let Some(RegistryRecord { + key: _, + data: registry_data, + }) = plugin_registry.registry.iter_mut().find( + |RegistryRecord { + key: search_key, + data: _, + }| search_key == &key, + ) { + solana_program::msg!("Adding authority to existing plugin"); registry_data.authorities.push(authority.clone()); - let authority_bytes = authority.try_to_vec()?; - let new_size = account .data_len() .checked_add(authority_bytes.len()) @@ -142,35 +154,44 @@ pub fn add_plugin<'a>( plugin_registry.save(account, header.plugin_registry_offset)?; } else { - let authority_bytes = authority.try_to_vec()?; + solana_program::msg!("Adding new plugin"); - let new_size = account - .data_len() - .checked_add(plugin_size) + let old_registry_offset = header.plugin_registry_offset; + let registry_data = RegistryData { + offset: old_registry_offset, + authorities: vec![authority], + }; + + //TODO: There's probably a better way to get the size without having to serialize the registry data. + let size_increase = plugin_size + .checked_add(Key::get_initial_size()) .ok_or(MplAssetError::NumericalOverflow)? - .checked_add(authority_bytes.len()) + .checked_add(registry_data.clone().try_to_vec()?.len()) .ok_or(MplAssetError::NumericalOverflow)?; - let old_registry_offset = header.plugin_registry_offset; let new_registry_offset = header .plugin_registry_offset .checked_add(plugin_size) - .ok_or(MplAssetError::NumericalOverflow)? - .checked_add(authority_bytes.len()) .ok_or(MplAssetError::NumericalOverflow)?; header.plugin_registry_offset = new_registry_offset; - plugin_registry.registry.push(( + + plugin_registry.registry.push(RegistryRecord { key, - RegistryData { - offset: old_registry_offset, - authorities: vec![authority], - }, - )); + data: registry_data.clone(), + }); + + let new_size = account + .data_len() + .checked_add(size_increase) + .ok_or(MplAssetError::NumericalOverflow)?; + solana_program::msg!("Resizing account"); resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; + solana_program::msg!("Saving plugin header"); header.save(account, asset.get_size())?; + solana_program::msg!("Saving plugin"); match plugin { Plugin::Reserved => todo!(), Plugin::Royalties => todo!(), @@ -180,6 +201,7 @@ pub fn add_plugin<'a>( Plugin::Inscription => todo!(), }; + solana_program::msg!("Saving plugin registry"); plugin_registry.save(account, new_registry_offset)?; } diff --git a/programs/mpl-asset/src/processor/delegate.rs b/programs/mpl-asset/src/processor/delegate.rs index ac9c97c6..92070baa 100644 --- a/programs/mpl-asset/src/processor/delegate.rs +++ b/programs/mpl-asset/src/processor/delegate.rs @@ -1,20 +1,45 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; -use crate::{instruction::accounts::DelegateAccounts, plugins::create_idempotent}; +use crate::{ + instruction::accounts::DelegateAccounts, + plugins::{add_plugin, create_idempotent, Delegate, Plugin}, + state::Authority, +}; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct DelegateArgs {} -pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], args: DelegateArgs) -> ProgramResult { +pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], _args: DelegateArgs) -> ProgramResult { let ctx = DelegateAccounts::context(accounts)?; + assert_signer(ctx.accounts.owner)?; + let payer = match ctx.accounts.payer { + Some(payer) => { + assert_signer(payer)?; + payer + } + None => ctx.accounts.owner, + }; + create_idempotent( ctx.accounts.asset_address, ctx.accounts.owner, ctx.accounts.system_program, )?; - Ok(()) + let plugin = Plugin::Delegate(Delegate::new()); + + solana_program::msg!("Add plugin"); + add_plugin( + &plugin, + Authority::Pubkey { + address: *ctx.accounts.delegate.key, + }, + ctx.accounts.asset_address, + payer, + ctx.accounts.system_program, + ) } diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index e85bb684..9faea0f4 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -25,6 +25,7 @@ pub enum DataState { #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub enum Authority { Owner, + Pubkey { address: Pubkey }, Permanent { address: Pubkey }, SameAs { plugin: Plugin }, Collection, @@ -60,6 +61,12 @@ pub enum Key { Delegate, } +impl Key { + pub fn get_initial_size() -> usize { + 1 + } +} + #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub enum MigrationLevel { diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index 7f138c7a..1caf91b8 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -1,8 +1,9 @@ use crate::{state::Key, utils::DataBlob}; use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; #[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct PluginHeader { pub key: Key, pub plugin_registry_offset: usize, // 8