diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index a70010f3..b3cb89c2 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -433,14 +433,14 @@ export class InvalidLogWrapperProgramError extends ProgramError { codeToErrorMap.set(0x1e, InvalidLogWrapperProgramError); nameToErrorMap.set('InvalidLogWrapperProgram', InvalidLogWrapperProgramError); -/** ExternalPluginAdapterNotFound: External PluginExternalPluginAdapter not found */ +/** ExternalPluginAdapterNotFound: External Plugin Adapter not found */ export class ExternalPluginAdapterNotFoundError extends ProgramError { override readonly name: string = 'ExternalPluginAdapterNotFound'; readonly code: number = 0x1f; // 31 constructor(program: Program, cause?: Error) { - super('External PluginExternalPluginAdapter not found', program, cause); + super('External Plugin Adapter not found', program, cause); } } codeToErrorMap.set(0x1f, ExternalPluginAdapterNotFoundError); @@ -449,18 +449,14 @@ nameToErrorMap.set( ExternalPluginAdapterNotFoundError ); -/** ExternalPluginAdapterAlreadyExists: External PluginExternalPluginAdapter already exists */ +/** ExternalPluginAdapterAlreadyExists: External Plugin Adapter already exists */ export class ExternalPluginAdapterAlreadyExistsError extends ProgramError { override readonly name: string = 'ExternalPluginAdapterAlreadyExists'; readonly code: number = 0x20; // 32 constructor(program: Program, cause?: Error) { - super( - 'External PluginExternalPluginAdapter already exists', - program, - cause - ); + super('External Plugin Adapter already exists', program, cause); } } codeToErrorMap.set(0x20, ExternalPluginAdapterAlreadyExistsError); @@ -608,6 +604,78 @@ export class InvalidPluginOperationError extends ProgramError { codeToErrorMap.set(0x29, InvalidPluginOperationError); nameToErrorMap.set('InvalidPluginOperation', InvalidPluginOperationError); +/** TwoDataSources: Two data sources provided, only one is allowed */ +export class TwoDataSourcesError extends ProgramError { + override readonly name: string = 'TwoDataSources'; + + readonly code: number = 0x2a; // 42 + + constructor(program: Program, cause?: Error) { + super('Two data sources provided, only one is allowed', program, cause); + } +} +codeToErrorMap.set(0x2a, TwoDataSourcesError); +nameToErrorMap.set('TwoDataSources', TwoDataSourcesError); + +/** UnsupportedOperation: External Plugin does not support this operation */ +export class UnsupportedOperationError extends ProgramError { + override readonly name: string = 'UnsupportedOperation'; + + readonly code: number = 0x2b; // 43 + + constructor(program: Program, cause?: Error) { + super('External Plugin does not support this operation', program, cause); + } +} +codeToErrorMap.set(0x2b, UnsupportedOperationError); +nameToErrorMap.set('UnsupportedOperation', UnsupportedOperationError); + +/** NoDataSources: No data sources provided, one is required */ +export class NoDataSourcesError extends ProgramError { + override readonly name: string = 'NoDataSources'; + + readonly code: number = 0x2c; // 44 + + constructor(program: Program, cause?: Error) { + super('No data sources provided, one is required', program, cause); + } +} +codeToErrorMap.set(0x2c, NoDataSourcesError); +nameToErrorMap.set('NoDataSources', NoDataSourcesError); + +/** InvalidPluginAdapterTarget: This plugin adapter cannot be added to an Asset */ +export class InvalidPluginAdapterTargetError extends ProgramError { + override readonly name: string = 'InvalidPluginAdapterTarget'; + + readonly code: number = 0x2d; // 45 + + constructor(program: Program, cause?: Error) { + super('This plugin adapter cannot be added to an Asset', program, cause); + } +} +codeToErrorMap.set(0x2d, InvalidPluginAdapterTargetError); +nameToErrorMap.set( + 'InvalidPluginAdapterTarget', + InvalidPluginAdapterTargetError +); + +/** CannotAddDataSection: Cannot add a Data Section without a linked external plugin */ +export class CannotAddDataSectionError extends ProgramError { + override readonly name: string = 'CannotAddDataSection'; + + readonly code: number = 0x2e; // 46 + + constructor(program: Program, cause?: Error) { + super( + 'Cannot add a Data Section without a linked external plugin', + program, + cause + ); + } +} +codeToErrorMap.set(0x2e, CannotAddDataSectionError); +nameToErrorMap.set('CannotAddDataSection', CannotAddDataSectionError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts b/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts index 1397b69a..589d28a1 100644 --- a/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts +++ b/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts @@ -8,6 +8,8 @@ import { Context, + Option, + OptionOrNullable, Pda, PublicKey, Signer, @@ -18,6 +20,7 @@ import { Serializer, bytes, mapSerializer, + option, struct, u32, u8, @@ -39,8 +42,10 @@ export type WriteCollectionExternalPluginAdapterDataV1InstructionAccounts = { collection: PublicKey | Pda; /** The account paying for the storage fees */ payer?: Signer; - /** The Data Authority of the External PluginExternalPluginAdapter */ + /** The Data Authority of the External Plugin Adapter */ authority?: Signer; + /** The buffer to write to the external plugin */ + buffer?: PublicKey | Pda; /** The system program */ systemProgram?: PublicKey | Pda; /** The SPL Noop Program */ @@ -51,12 +56,12 @@ export type WriteCollectionExternalPluginAdapterDataV1InstructionAccounts = { export type WriteCollectionExternalPluginAdapterDataV1InstructionData = { discriminator: number; key: BaseExternalPluginAdapterKey; - data: Uint8Array; + data: Option; }; export type WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs = { key: BaseExternalPluginAdapterKeyArgs; - data: Uint8Array; + data: OptionOrNullable; }; export function getWriteCollectionExternalPluginAdapterDataV1InstructionDataSerializer(): Serializer< @@ -72,7 +77,7 @@ export function getWriteCollectionExternalPluginAdapterDataV1InstructionDataSeri [ ['discriminator', u8()], ['key', getBaseExternalPluginAdapterKeySerializer()], - ['data', bytes({ size: u32() })], + ['data', option(bytes({ size: u32() }))], ], { description: @@ -119,13 +124,18 @@ export function writeCollectionExternalPluginAdapterDataV1( isWritable: false as boolean, value: input.authority ?? null, }, - systemProgram: { + buffer: { index: 3, isWritable: false as boolean, + value: input.buffer ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, value: input.systemProgram ?? null, }, logWrapper: { - index: 4, + index: 5, isWritable: false as boolean, value: input.logWrapper ?? null, }, diff --git a/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts b/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts index 78e639ba..226ba20f 100644 --- a/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts +++ b/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts @@ -8,6 +8,8 @@ import { Context, + Option, + OptionOrNullable, Pda, PublicKey, Signer, @@ -18,6 +20,7 @@ import { Serializer, bytes, mapSerializer, + option, struct, u32, u8, @@ -41,8 +44,10 @@ export type WriteExternalPluginAdapterDataV1InstructionAccounts = { collection?: PublicKey | Pda; /** The account paying for the storage fees */ payer?: Signer; - /** The Data Authority of the External PluginExternalPluginAdapter */ + /** The Data Authority of the External Plugin Adapter */ authority?: Signer; + /** The buffer to write to the external plugin */ + buffer?: PublicKey | Pda; /** The system program */ systemProgram?: PublicKey | Pda; /** The SPL Noop Program */ @@ -53,12 +58,12 @@ export type WriteExternalPluginAdapterDataV1InstructionAccounts = { export type WriteExternalPluginAdapterDataV1InstructionData = { discriminator: number; key: BaseExternalPluginAdapterKey; - data: Uint8Array; + data: Option; }; export type WriteExternalPluginAdapterDataV1InstructionDataArgs = { key: BaseExternalPluginAdapterKeyArgs; - data: Uint8Array; + data: OptionOrNullable; }; export function getWriteExternalPluginAdapterDataV1InstructionDataSerializer(): Serializer< @@ -74,7 +79,7 @@ export function getWriteExternalPluginAdapterDataV1InstructionDataSerializer(): [ ['discriminator', u8()], ['key', getBaseExternalPluginAdapterKeySerializer()], - ['data', bytes({ size: u32() })], + ['data', option(bytes({ size: u32() }))], ], { description: 'WriteExternalPluginAdapterDataV1InstructionData' } ), @@ -123,13 +128,18 @@ export function writeExternalPluginAdapterDataV1( isWritable: false as boolean, value: input.authority ?? null, }, - systemProgram: { + buffer: { index: 4, isWritable: false as boolean, + value: input.buffer ?? null, + }, + systemProgram: { + index: 5, + isWritable: false as boolean, value: input.systemProgram ?? null, }, logWrapper: { - index: 5, + index: 6, isWritable: false as boolean, value: input.logWrapper ?? null, }, diff --git a/clients/js/src/generated/types/baseDataStore.ts b/clients/js/src/generated/types/baseAppData.ts similarity index 74% rename from clients/js/src/generated/types/baseDataStore.ts rename to clients/js/src/generated/types/baseAppData.ts index e83eff4d..5d80d6b0 100644 --- a/clients/js/src/generated/types/baseDataStore.ts +++ b/clients/js/src/generated/types/baseAppData.ts @@ -16,25 +16,25 @@ import { getExternalPluginAdapterSchemaSerializer, } from '.'; -export type BaseDataStore = { +export type BaseAppData = { dataAuthority: BasePluginAuthority; schema: ExternalPluginAdapterSchema; }; -export type BaseDataStoreArgs = { +export type BaseAppDataArgs = { dataAuthority: BasePluginAuthorityArgs; schema: ExternalPluginAdapterSchemaArgs; }; -export function getBaseDataStoreSerializer(): Serializer< - BaseDataStoreArgs, - BaseDataStore +export function getBaseAppDataSerializer(): Serializer< + BaseAppDataArgs, + BaseAppData > { - return struct( + return struct( [ ['dataAuthority', getBasePluginAuthoritySerializer()], ['schema', getExternalPluginAdapterSchemaSerializer()], ], - { description: 'BaseDataStore' } - ) as Serializer; + { description: 'BaseAppData' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/baseDataStoreInitInfo.ts b/clients/js/src/generated/types/baseAppDataInitInfo.ts similarity index 76% rename from clients/js/src/generated/types/baseDataStoreInitInfo.ts rename to clients/js/src/generated/types/baseAppDataInitInfo.ts index b23c72f6..30374e1f 100644 --- a/clients/js/src/generated/types/baseDataStoreInitInfo.ts +++ b/clients/js/src/generated/types/baseAppDataInitInfo.ts @@ -21,28 +21,28 @@ import { getExternalPluginAdapterSchemaSerializer, } from '.'; -export type BaseDataStoreInitInfo = { +export type BaseAppDataInitInfo = { dataAuthority: BasePluginAuthority; initPluginAuthority: Option; schema: Option; }; -export type BaseDataStoreInitInfoArgs = { +export type BaseAppDataInitInfoArgs = { dataAuthority: BasePluginAuthorityArgs; initPluginAuthority: OptionOrNullable; schema: OptionOrNullable; }; -export function getBaseDataStoreInitInfoSerializer(): Serializer< - BaseDataStoreInitInfoArgs, - BaseDataStoreInitInfo +export function getBaseAppDataInitInfoSerializer(): Serializer< + BaseAppDataInitInfoArgs, + BaseAppDataInitInfo > { - return struct( + return struct( [ ['dataAuthority', getBasePluginAuthoritySerializer()], ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], ['schema', option(getExternalPluginAdapterSchemaSerializer())], ], - { description: 'BaseDataStoreInitInfo' } - ) as Serializer; + { description: 'BaseAppDataInitInfo' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/baseDataStoreUpdateInfo.ts b/clients/js/src/generated/types/baseAppDataUpdateInfo.ts similarity index 65% rename from clients/js/src/generated/types/baseDataStoreUpdateInfo.ts rename to clients/js/src/generated/types/baseAppDataUpdateInfo.ts index 53b65c4c..9d42e1f7 100644 --- a/clients/js/src/generated/types/baseDataStoreUpdateInfo.ts +++ b/clients/js/src/generated/types/baseAppDataUpdateInfo.ts @@ -18,20 +18,20 @@ import { getExternalPluginAdapterSchemaSerializer, } from '.'; -export type BaseDataStoreUpdateInfo = { +export type BaseAppDataUpdateInfo = { schema: Option; }; -export type BaseDataStoreUpdateInfoArgs = { +export type BaseAppDataUpdateInfoArgs = { schema: OptionOrNullable; }; -export function getBaseDataStoreUpdateInfoSerializer(): Serializer< - BaseDataStoreUpdateInfoArgs, - BaseDataStoreUpdateInfo +export function getBaseAppDataUpdateInfoSerializer(): Serializer< + BaseAppDataUpdateInfoArgs, + BaseAppDataUpdateInfo > { - return struct( + return struct( [['schema', option(getExternalPluginAdapterSchemaSerializer())]], - { description: 'BaseDataStoreUpdateInfo' } - ) as Serializer; + { description: 'BaseAppDataUpdateInfo' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/baseDataSection.ts b/clients/js/src/generated/types/baseDataSection.ts new file mode 100644 index 00000000..59cdfbbd --- /dev/null +++ b/clients/js/src/generated/types/baseDataSection.ts @@ -0,0 +1,40 @@ +/** + * 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 { + BaseLinkedDataKey, + BaseLinkedDataKeyArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBaseLinkedDataKeySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseDataSection = { + parentKey: BaseLinkedDataKey; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseDataSectionArgs = { + parentKey: BaseLinkedDataKeyArgs; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseDataSectionSerializer(): Serializer< + BaseDataSectionArgs, + BaseDataSection +> { + return struct( + [ + ['parentKey', getBaseLinkedDataKeySerializer()], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseDataSection' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseDataSectionInitInfo.ts b/clients/js/src/generated/types/baseDataSectionInitInfo.ts new file mode 100644 index 00000000..ed1d30d3 --- /dev/null +++ b/clients/js/src/generated/types/baseDataSectionInitInfo.ts @@ -0,0 +1,40 @@ +/** + * 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 { + BaseLinkedDataKey, + BaseLinkedDataKeyArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBaseLinkedDataKeySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseDataSectionInitInfo = { + parentKey: BaseLinkedDataKey; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseDataSectionInitInfoArgs = { + parentKey: BaseLinkedDataKeyArgs; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseDataSectionInitInfoSerializer(): Serializer< + BaseDataSectionInitInfoArgs, + BaseDataSectionInitInfo +> { + return struct( + [ + ['parentKey', getBaseLinkedDataKeySerializer()], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseDataSectionInitInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseDataSectionUpdateInfo.ts b/clients/js/src/generated/types/baseDataSectionUpdateInfo.ts new file mode 100644 index 00000000..b1abd23e --- /dev/null +++ b/clients/js/src/generated/types/baseDataSectionUpdateInfo.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 BaseDataSectionUpdateInfo = {}; + +export type BaseDataSectionUpdateInfoArgs = BaseDataSectionUpdateInfo; + +export function getBaseDataSectionUpdateInfoSerializer(): Serializer< + BaseDataSectionUpdateInfoArgs, + BaseDataSectionUpdateInfo +> { + return struct([], { + description: 'BaseDataSectionUpdateInfo', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts b/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts index 2649b73a..c65b82af 100644 --- a/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts +++ b/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts @@ -15,26 +15,44 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { - BaseDataStoreInitInfo, - BaseDataStoreInitInfoArgs, + BaseAppDataInitInfo, + BaseAppDataInitInfoArgs, + BaseDataSectionInitInfo, + BaseDataSectionInitInfoArgs, BaseLifecycleHookInitInfo, BaseLifecycleHookInitInfoArgs, + BaseLinkedAppDataInitInfo, + BaseLinkedAppDataInitInfoArgs, + BaseLinkedLifecycleHookInitInfo, + BaseLinkedLifecycleHookInitInfoArgs, BaseOracleInitInfo, BaseOracleInitInfoArgs, - getBaseDataStoreInitInfoSerializer, + getBaseAppDataInitInfoSerializer, + getBaseDataSectionInitInfoSerializer, getBaseLifecycleHookInitInfoSerializer, + getBaseLinkedAppDataInitInfoSerializer, + getBaseLinkedLifecycleHookInitInfoSerializer, getBaseOracleInitInfoSerializer, } from '.'; export type BaseExternalPluginAdapterInitInfo = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookInitInfo] } | { __kind: 'Oracle'; fields: [BaseOracleInitInfo] } - | { __kind: 'DataStore'; fields: [BaseDataStoreInitInfo] }; + | { __kind: 'AppData'; fields: [BaseAppDataInitInfo] } + | { __kind: 'LinkedLifecycleHook'; fields: [BaseLinkedLifecycleHookInitInfo] } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppDataInitInfo] } + | { __kind: 'DataSection'; fields: [BaseDataSectionInitInfo] }; export type BaseExternalPluginAdapterInitInfoArgs = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookInitInfoArgs] } | { __kind: 'Oracle'; fields: [BaseOracleInitInfoArgs] } - | { __kind: 'DataStore'; fields: [BaseDataStoreInitInfoArgs] }; + | { __kind: 'AppData'; fields: [BaseAppDataInitInfoArgs] } + | { + __kind: 'LinkedLifecycleHook'; + fields: [BaseLinkedLifecycleHookInitInfoArgs]; + } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppDataInitInfoArgs] } + | { __kind: 'DataSection'; fields: [BaseDataSectionInitInfoArgs] }; export function getBaseExternalPluginAdapterInitInfoSerializer(): Serializer< BaseExternalPluginAdapterInitInfoArgs, @@ -58,10 +76,39 @@ export function getBaseExternalPluginAdapterInitInfoSerializer(): Serializer< >([['fields', tuple([getBaseOracleInitInfoSerializer()])]]), ], [ - 'DataStore', + 'AppData', struct< - GetDataEnumKindContent - >([['fields', tuple([getBaseDataStoreInitInfoSerializer()])]]), + GetDataEnumKindContent + >([['fields', tuple([getBaseAppDataInitInfoSerializer()])]]), + ], + [ + 'LinkedLifecycleHook', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfo, + 'LinkedLifecycleHook' + > + >([ + ['fields', tuple([getBaseLinkedLifecycleHookInitInfoSerializer()])], + ]), + ], + [ + 'LinkedAppData', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfo, + 'LinkedAppData' + > + >([['fields', tuple([getBaseLinkedAppDataInitInfoSerializer()])]]), + ], + [ + 'DataSection', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfo, + 'DataSection' + > + >([['fields', tuple([getBaseDataSectionInitInfoSerializer()])]]), ], ], { description: 'BaseExternalPluginAdapterInitInfo' } @@ -87,12 +134,36 @@ export function baseExternalPluginAdapterInitInfo( >['fields'] ): GetDataEnumKind; export function baseExternalPluginAdapterInitInfo( - kind: 'DataStore', + kind: 'AppData', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'AppData' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterInitInfo( + kind: 'LinkedLifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'LinkedLifecycleHook' + >['fields'] +): GetDataEnumKind< + BaseExternalPluginAdapterInitInfoArgs, + 'LinkedLifecycleHook' +>; +export function baseExternalPluginAdapterInitInfo( + kind: 'LinkedAppData', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'LinkedAppData' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterInitInfo( + kind: 'DataSection', data: GetDataEnumKindContent< BaseExternalPluginAdapterInitInfoArgs, - 'DataStore' + 'DataSection' >['fields'] -): GetDataEnumKind; +): GetDataEnumKind; export function baseExternalPluginAdapterInitInfo< K extends BaseExternalPluginAdapterInitInfoArgs['__kind'], >( diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts b/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts index 1a57e653..9aedf063 100644 --- a/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts +++ b/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts @@ -17,20 +17,29 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { + BaseLinkedDataKey, + BaseLinkedDataKeyArgs, BasePluginAuthority, BasePluginAuthorityArgs, + getBaseLinkedDataKeySerializer, getBasePluginAuthoritySerializer, } from '.'; export type BaseExternalPluginAdapterKey = | { __kind: 'LifecycleHook'; fields: [PublicKey] } | { __kind: 'Oracle'; fields: [PublicKey] } - | { __kind: 'DataStore'; fields: [BasePluginAuthority] }; + | { __kind: 'AppData'; fields: [BasePluginAuthority] } + | { __kind: 'LinkedLifecycleHook'; fields: [PublicKey] } + | { __kind: 'LinkedAppData'; fields: [BasePluginAuthority] } + | { __kind: 'DataSection'; fields: [BaseLinkedDataKey] }; export type BaseExternalPluginAdapterKeyArgs = | { __kind: 'LifecycleHook'; fields: [PublicKey] } | { __kind: 'Oracle'; fields: [PublicKey] } - | { __kind: 'DataStore'; fields: [BasePluginAuthorityArgs] }; + | { __kind: 'AppData'; fields: [BasePluginAuthorityArgs] } + | { __kind: 'LinkedLifecycleHook'; fields: [PublicKey] } + | { __kind: 'LinkedAppData'; fields: [BasePluginAuthorityArgs] } + | { __kind: 'DataSection'; fields: [BaseLinkedDataKeyArgs] }; export function getBaseExternalPluginAdapterKeySerializer(): Serializer< BaseExternalPluginAdapterKeyArgs, @@ -51,11 +60,32 @@ export function getBaseExternalPluginAdapterKeySerializer(): Serializer< ]), ], [ - 'DataStore', + 'AppData', + struct>( + [['fields', tuple([getBasePluginAuthoritySerializer()])]] + ), + ], + [ + 'LinkedLifecycleHook', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterKey, + 'LinkedLifecycleHook' + > + >([['fields', tuple([publicKeySerializer()])]]), + ], + [ + 'LinkedAppData', struct< - GetDataEnumKindContent + GetDataEnumKindContent >([['fields', tuple([getBasePluginAuthoritySerializer()])]]), ], + [ + 'DataSection', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseLinkedDataKeySerializer()])]]), + ], ], { description: 'BaseExternalPluginAdapterKey' } ) as Serializer< @@ -80,12 +110,33 @@ export function baseExternalPluginAdapterKey( >['fields'] ): GetDataEnumKind; export function baseExternalPluginAdapterKey( - kind: 'DataStore', + kind: 'AppData', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'AppData' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey( + kind: 'LinkedLifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'LinkedLifecycleHook' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey( + kind: 'LinkedAppData', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'LinkedAppData' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey( + kind: 'DataSection', data: GetDataEnumKindContent< BaseExternalPluginAdapterKeyArgs, - 'DataStore' + 'DataSection' >['fields'] -): GetDataEnumKind; +): GetDataEnumKind; export function baseExternalPluginAdapterKey< K extends BaseExternalPluginAdapterKeyArgs['__kind'], >( diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts b/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts index 4fc19360..99ee7553 100644 --- a/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts +++ b/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts @@ -15,26 +15,42 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { - BaseDataStoreUpdateInfo, - BaseDataStoreUpdateInfoArgs, + BaseAppDataUpdateInfo, + BaseAppDataUpdateInfoArgs, BaseLifecycleHookUpdateInfo, BaseLifecycleHookUpdateInfoArgs, + BaseLinkedAppDataUpdateInfo, + BaseLinkedAppDataUpdateInfoArgs, + BaseLinkedLifecycleHookUpdateInfo, + BaseLinkedLifecycleHookUpdateInfoArgs, BaseOracleUpdateInfo, BaseOracleUpdateInfoArgs, - getBaseDataStoreUpdateInfoSerializer, + getBaseAppDataUpdateInfoSerializer, getBaseLifecycleHookUpdateInfoSerializer, + getBaseLinkedAppDataUpdateInfoSerializer, + getBaseLinkedLifecycleHookUpdateInfoSerializer, getBaseOracleUpdateInfoSerializer, } from '.'; export type BaseExternalPluginAdapterUpdateInfo = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookUpdateInfo] } | { __kind: 'Oracle'; fields: [BaseOracleUpdateInfo] } - | { __kind: 'DataStore'; fields: [BaseDataStoreUpdateInfo] }; + | { __kind: 'AppData'; fields: [BaseAppDataUpdateInfo] } + | { + __kind: 'LinkedLifecycleHook'; + fields: [BaseLinkedLifecycleHookUpdateInfo]; + } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppDataUpdateInfo] }; export type BaseExternalPluginAdapterUpdateInfoArgs = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookUpdateInfoArgs] } | { __kind: 'Oracle'; fields: [BaseOracleUpdateInfoArgs] } - | { __kind: 'DataStore'; fields: [BaseDataStoreUpdateInfoArgs] }; + | { __kind: 'AppData'; fields: [BaseAppDataUpdateInfoArgs] } + | { + __kind: 'LinkedLifecycleHook'; + fields: [BaseLinkedLifecycleHookUpdateInfoArgs]; + } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppDataUpdateInfoArgs] }; export function getBaseExternalPluginAdapterUpdateInfoSerializer(): Serializer< BaseExternalPluginAdapterUpdateInfoArgs, @@ -58,13 +74,30 @@ export function getBaseExternalPluginAdapterUpdateInfoSerializer(): Serializer< >([['fields', tuple([getBaseOracleUpdateInfoSerializer()])]]), ], [ - 'DataStore', + 'AppData', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseAppDataUpdateInfoSerializer()])]]), + ], + [ + 'LinkedLifecycleHook', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfo, + 'LinkedLifecycleHook' + > + >([ + ['fields', tuple([getBaseLinkedLifecycleHookUpdateInfoSerializer()])], + ]), + ], + [ + 'LinkedAppData', struct< GetDataEnumKindContent< BaseExternalPluginAdapterUpdateInfo, - 'DataStore' + 'LinkedAppData' > - >([['fields', tuple([getBaseDataStoreUpdateInfoSerializer()])]]), + >([['fields', tuple([getBaseLinkedAppDataUpdateInfoSerializer()])]]), ], ], { description: 'BaseExternalPluginAdapterUpdateInfo' } @@ -90,12 +123,29 @@ export function baseExternalPluginAdapterUpdateInfo( >['fields'] ): GetDataEnumKind; export function baseExternalPluginAdapterUpdateInfo( - kind: 'DataStore', + kind: 'AppData', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfoArgs, + 'AppData' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterUpdateInfo( + kind: 'LinkedLifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfoArgs, + 'LinkedLifecycleHook' + >['fields'] +): GetDataEnumKind< + BaseExternalPluginAdapterUpdateInfoArgs, + 'LinkedLifecycleHook' +>; +export function baseExternalPluginAdapterUpdateInfo( + kind: 'LinkedAppData', data: GetDataEnumKindContent< BaseExternalPluginAdapterUpdateInfoArgs, - 'DataStore' + 'LinkedAppData' >['fields'] -): GetDataEnumKind; +): GetDataEnumKind; export function baseExternalPluginAdapterUpdateInfo< K extends BaseExternalPluginAdapterUpdateInfoArgs['__kind'], >( diff --git a/clients/js/src/generated/types/baseLinkedAppData.ts b/clients/js/src/generated/types/baseLinkedAppData.ts new file mode 100644 index 00000000..22c78b25 --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedAppData.ts @@ -0,0 +1,40 @@ +/** + * 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 { + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseLinkedAppData = { + dataAuthority: BasePluginAuthority; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseLinkedAppDataArgs = { + dataAuthority: BasePluginAuthorityArgs; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseLinkedAppDataSerializer(): Serializer< + BaseLinkedAppDataArgs, + BaseLinkedAppData +> { + return struct( + [ + ['dataAuthority', getBasePluginAuthoritySerializer()], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseLinkedAppData' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLinkedAppDataInitInfo.ts b/clients/js/src/generated/types/baseLinkedAppDataInitInfo.ts new file mode 100644 index 00000000..42993060 --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedAppDataInitInfo.ts @@ -0,0 +1,48 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseLinkedAppDataInitInfo = { + dataAuthority: BasePluginAuthority; + initPluginAuthority: Option; + schema: Option; +}; + +export type BaseLinkedAppDataInitInfoArgs = { + dataAuthority: BasePluginAuthorityArgs; + initPluginAuthority: OptionOrNullable; + schema: OptionOrNullable; +}; + +export function getBaseLinkedAppDataInitInfoSerializer(): Serializer< + BaseLinkedAppDataInitInfoArgs, + BaseLinkedAppDataInitInfo +> { + return struct( + [ + ['dataAuthority', getBasePluginAuthoritySerializer()], + ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseLinkedAppDataInitInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLinkedAppDataUpdateInfo.ts b/clients/js/src/generated/types/baseLinkedAppDataUpdateInfo.ts new file mode 100644 index 00000000..cf987f8e --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedAppDataUpdateInfo.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 { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseLinkedAppDataUpdateInfo = { + schema: Option; +}; + +export type BaseLinkedAppDataUpdateInfoArgs = { + schema: OptionOrNullable; +}; + +export function getBaseLinkedAppDataUpdateInfoSerializer(): Serializer< + BaseLinkedAppDataUpdateInfoArgs, + BaseLinkedAppDataUpdateInfo +> { + return struct( + [['schema', option(getExternalPluginAdapterSchemaSerializer())]], + { description: 'BaseLinkedAppDataUpdateInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLinkedDataKey.ts b/clients/js/src/generated/types/baseLinkedDataKey.ts new file mode 100644 index 00000000..80217cb1 --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedDataKey.ts @@ -0,0 +1,81 @@ +/** + * 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 { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + getBasePluginAuthoritySerializer, +} from '.'; + +export type BaseLinkedDataKey = + | { __kind: 'LinkedLifecycleHook'; fields: [PublicKey] } + | { __kind: 'LinkedAppData'; fields: [BasePluginAuthority] }; + +export type BaseLinkedDataKeyArgs = + | { __kind: 'LinkedLifecycleHook'; fields: [PublicKey] } + | { __kind: 'LinkedAppData'; fields: [BasePluginAuthorityArgs] }; + +export function getBaseLinkedDataKeySerializer(): Serializer< + BaseLinkedDataKeyArgs, + BaseLinkedDataKey +> { + return dataEnum( + [ + [ + 'LinkedLifecycleHook', + struct< + GetDataEnumKindContent + >([['fields', tuple([publicKeySerializer()])]]), + ], + [ + 'LinkedAppData', + struct>([ + ['fields', tuple([getBasePluginAuthoritySerializer()])], + ]), + ], + ], + { description: 'BaseLinkedDataKey' } + ) as Serializer; +} + +// Data Enum Helpers. +export function baseLinkedDataKey( + kind: 'LinkedLifecycleHook', + data: GetDataEnumKindContent< + BaseLinkedDataKeyArgs, + 'LinkedLifecycleHook' + >['fields'] +): GetDataEnumKind; +export function baseLinkedDataKey( + kind: 'LinkedAppData', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseLinkedDataKey( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseLinkedDataKey( + kind: K, + value: BaseLinkedDataKey +): value is BaseLinkedDataKey & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseLinkedLifecycleHook.ts b/clients/js/src/generated/types/baseLinkedLifecycleHook.ts new file mode 100644 index 00000000..b84a133f --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedLifecycleHook.ts @@ -0,0 +1,56 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBaseExtraAccountSerializer, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseLinkedLifecycleHook = { + hookedProgram: PublicKey; + extraAccounts: Option>; + dataAuthority: Option; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseLinkedLifecycleHookArgs = { + hookedProgram: PublicKey; + extraAccounts: OptionOrNullable>; + dataAuthority: OptionOrNullable; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseLinkedLifecycleHookSerializer(): Serializer< + BaseLinkedLifecycleHookArgs, + BaseLinkedLifecycleHook +> { + return struct( + [ + ['hookedProgram', publicKeySerializer()], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['dataAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseLinkedLifecycleHook' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLinkedLifecycleHookInitInfo.ts b/clients/js/src/generated/types/baseLinkedLifecycleHookInitInfo.ts new file mode 100644 index 00000000..9b3ae621 --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedLifecycleHookInitInfo.ts @@ -0,0 +1,80 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getBasePluginAuthoritySerializer, + getExternalCheckResultSerializer, + getExternalPluginAdapterSchemaSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseLinkedLifecycleHookInitInfo = { + hookedProgram: PublicKey; + initPluginAuthority: Option; + lifecycleChecks: Array<[HookableLifecycleEvent, ExternalCheckResult]>; + extraAccounts: Option>; + dataAuthority: Option; + schema: Option; +}; + +export type BaseLinkedLifecycleHookInitInfoArgs = { + hookedProgram: PublicKey; + initPluginAuthority: OptionOrNullable; + lifecycleChecks: Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]>; + extraAccounts: OptionOrNullable>; + dataAuthority: OptionOrNullable; + schema: OptionOrNullable; +}; + +export function getBaseLinkedLifecycleHookInitInfoSerializer(): Serializer< + BaseLinkedLifecycleHookInitInfoArgs, + BaseLinkedLifecycleHookInitInfo +> { + return struct( + [ + ['hookedProgram', publicKeySerializer()], + ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], + [ + 'lifecycleChecks', + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ), + ], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['dataAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseLinkedLifecycleHookInitInfo' } + ) as Serializer< + BaseLinkedLifecycleHookInitInfoArgs, + BaseLinkedLifecycleHookInitInfo + >; +} diff --git a/clients/js/src/generated/types/baseLinkedLifecycleHookUpdateInfo.ts b/clients/js/src/generated/types/baseLinkedLifecycleHookUpdateInfo.ts new file mode 100644 index 00000000..2abd14d3 --- /dev/null +++ b/clients/js/src/generated/types/baseLinkedLifecycleHookUpdateInfo.ts @@ -0,0 +1,71 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getExternalCheckResultSerializer, + getExternalPluginAdapterSchemaSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseLinkedLifecycleHookUpdateInfo = { + lifecycleChecks: Option>; + extraAccounts: Option>; + schema: Option; +}; + +export type BaseLinkedLifecycleHookUpdateInfoArgs = { + lifecycleChecks: OptionOrNullable< + Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]> + >; + extraAccounts: OptionOrNullable>; + schema: OptionOrNullable; +}; + +export function getBaseLinkedLifecycleHookUpdateInfoSerializer(): Serializer< + BaseLinkedLifecycleHookUpdateInfoArgs, + BaseLinkedLifecycleHookUpdateInfo +> { + return struct( + [ + [ + 'lifecycleChecks', + option( + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ) + ), + ], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseLinkedLifecycleHookUpdateInfo' } + ) as Serializer< + BaseLinkedLifecycleHookUpdateInfoArgs, + BaseLinkedLifecycleHookUpdateInfo + >; +} diff --git a/clients/js/src/generated/types/externalPluginAdapter.ts b/clients/js/src/generated/types/externalPluginAdapter.ts index b50617df..260f2e09 100644 --- a/clients/js/src/generated/types/externalPluginAdapter.ts +++ b/clients/js/src/generated/types/externalPluginAdapter.ts @@ -15,26 +15,41 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { - BaseDataStore, - BaseDataStoreArgs, + BaseAppData, + BaseAppDataArgs, + BaseDataSection, + BaseDataSectionArgs, BaseLifecycleHook, BaseLifecycleHookArgs, + BaseLinkedAppData, + BaseLinkedAppDataArgs, + BaseLinkedLifecycleHook, + BaseLinkedLifecycleHookArgs, BaseOracle, BaseOracleArgs, - getBaseDataStoreSerializer, + getBaseAppDataSerializer, + getBaseDataSectionSerializer, getBaseLifecycleHookSerializer, + getBaseLinkedAppDataSerializer, + getBaseLinkedLifecycleHookSerializer, getBaseOracleSerializer, } from '.'; export type ExternalPluginAdapter = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHook] } | { __kind: 'Oracle'; fields: [BaseOracle] } - | { __kind: 'DataStore'; fields: [BaseDataStore] }; + | { __kind: 'AppData'; fields: [BaseAppData] } + | { __kind: 'LinkedLifecycleHook'; fields: [BaseLinkedLifecycleHook] } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppData] } + | { __kind: 'DataSection'; fields: [BaseDataSection] }; export type ExternalPluginAdapterArgs = | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookArgs] } | { __kind: 'Oracle'; fields: [BaseOracleArgs] } - | { __kind: 'DataStore'; fields: [BaseDataStoreArgs] }; + | { __kind: 'AppData'; fields: [BaseAppDataArgs] } + | { __kind: 'LinkedLifecycleHook'; fields: [BaseLinkedLifecycleHookArgs] } + | { __kind: 'LinkedAppData'; fields: [BaseLinkedAppDataArgs] } + | { __kind: 'DataSection'; fields: [BaseDataSectionArgs] }; export function getExternalPluginAdapterSerializer(): Serializer< ExternalPluginAdapterArgs, @@ -55,9 +70,27 @@ export function getExternalPluginAdapterSerializer(): Serializer< ]), ], [ - 'DataStore', - struct>([ - ['fields', tuple([getBaseDataStoreSerializer()])], + 'AppData', + struct>([ + ['fields', tuple([getBaseAppDataSerializer()])], + ]), + ], + [ + 'LinkedLifecycleHook', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseLinkedLifecycleHookSerializer()])]]), + ], + [ + 'LinkedAppData', + struct>([ + ['fields', tuple([getBaseLinkedAppDataSerializer()])], + ]), + ], + [ + 'DataSection', + struct>([ + ['fields', tuple([getBaseDataSectionSerializer()])], ]), ], ], @@ -78,9 +111,30 @@ export function externalPluginAdapter( data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; export function externalPluginAdapter( - kind: 'DataStore', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; + kind: 'AppData', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function externalPluginAdapter( + kind: 'LinkedLifecycleHook', + data: GetDataEnumKindContent< + ExternalPluginAdapterArgs, + 'LinkedLifecycleHook' + >['fields'] +): GetDataEnumKind; +export function externalPluginAdapter( + kind: 'LinkedAppData', + data: GetDataEnumKindContent< + ExternalPluginAdapterArgs, + 'LinkedAppData' + >['fields'] +): GetDataEnumKind; +export function externalPluginAdapter( + kind: 'DataSection', + data: GetDataEnumKindContent< + ExternalPluginAdapterArgs, + 'DataSection' + >['fields'] +): GetDataEnumKind; export function externalPluginAdapter< K extends ExternalPluginAdapterArgs['__kind'], >(kind: K, data?: any): Extract { diff --git a/clients/js/src/generated/types/externalPluginAdapterType.ts b/clients/js/src/generated/types/externalPluginAdapterType.ts index a0141f37..dd992d55 100644 --- a/clients/js/src/generated/types/externalPluginAdapterType.ts +++ b/clients/js/src/generated/types/externalPluginAdapterType.ts @@ -11,7 +11,10 @@ import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; export enum ExternalPluginAdapterType { LifecycleHook, Oracle, - DataStore, + AppData, + LinkedLifecycleHook, + LinkedAppData, + DataSection, } export type ExternalPluginAdapterTypeArgs = ExternalPluginAdapterType; diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index c744b4f0..86c9b374 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -11,9 +11,12 @@ export * from './attribute'; export * from './attributes'; export * from './autograph'; export * from './autographSignature'; -export * from './baseDataStore'; -export * from './baseDataStoreInitInfo'; -export * from './baseDataStoreUpdateInfo'; +export * from './baseAppData'; +export * from './baseAppDataInitInfo'; +export * from './baseAppDataUpdateInfo'; +export * from './baseDataSection'; +export * from './baseDataSectionInitInfo'; +export * from './baseDataSectionUpdateInfo'; export * from './baseExternalPluginAdapterInitInfo'; export * from './baseExternalPluginAdapterKey'; export * from './baseExternalPluginAdapterUpdateInfo'; @@ -21,6 +24,13 @@ export * from './baseExtraAccount'; export * from './baseLifecycleHook'; export * from './baseLifecycleHookInitInfo'; export * from './baseLifecycleHookUpdateInfo'; +export * from './baseLinkedAppData'; +export * from './baseLinkedAppDataInitInfo'; +export * from './baseLinkedAppDataUpdateInfo'; +export * from './baseLinkedDataKey'; +export * from './baseLinkedLifecycleHook'; +export * from './baseLinkedLifecycleHookInitInfo'; +export * from './baseLinkedLifecycleHookUpdateInfo'; export * from './baseMasterEdition'; export * from './baseOracle'; export * from './baseOracleInitInfo'; diff --git a/clients/js/src/helpers/state.ts b/clients/js/src/helpers/state.ts index f522ac9c..3389c9d2 100644 --- a/clients/js/src/helpers/state.ts +++ b/clients/js/src/helpers/state.ts @@ -1,12 +1,17 @@ import { PublicKey, publicKey } from '@metaplex-foundation/umi'; +import { LinkedLifecycleHookPlugin } from '../plugins/linkedLifecycleHook'; import { AssetV1, CollectionV1 } from '../generated'; -import { ExternalPluginAdaptersList } from '../plugins'; -import { OracleInitInfoArgs, OraclePlugin } from '../plugins/oracle'; -import { DataStoreInitInfoArgs, DataStorePlugin } from '../plugins/dataStore'; import { - LifecycleHookInitInfoArgs, - LifecycleHookPlugin, -} from '../plugins/lifecycleHook'; + comparePluginAuthorities, + ExternalPluginAdapters, + ExternalPluginAdaptersList, + PluginAuthority, +} from '../plugins'; +import { OraclePlugin } from '../plugins/oracle'; +import { AppDataPlugin } from '../plugins/appData'; +import { LifecycleHookPlugin } from '../plugins/lifecycleHook'; +import { DataSectionPlugin } from '../plugins/dataSection'; +import { LinkedAppDataPlugin } from '../plugins/linkedAppData'; /** * Find the collection address for the given asset if it is part of a collection. @@ -23,28 +28,37 @@ export function collectionAddress(asset: AssetV1): PublicKey | undefined { const externalPluginAdapterKeys: (keyof ExternalPluginAdaptersList)[] = [ 'oracles', - 'dataStores', + 'appDatas', 'lifecycleHooks', + 'dataSections', + 'linkedAppDatas', ]; export const getExternalPluginAdapterKeyAsString = ( plugin: - | OraclePlugin - | DataStorePlugin - | LifecycleHookPlugin - | OracleInitInfoArgs - | LifecycleHookInitInfoArgs - | DataStoreInitInfoArgs -) => { + | Pick + | Pick + | Pick + | Pick + | Pick + | Pick +): string => { switch (plugin.type) { case 'Oracle': return `${plugin.type}-${plugin.baseAddress}`; - case 'DataStore': + case 'AppData': return `${plugin.type}-${plugin.dataAuthority.type}${ plugin.dataAuthority.address ? `-${plugin.dataAuthority.address}` : '' }`; case 'LifecycleHook': - default: return `${plugin.type}-${plugin.hookedProgram}`; + case 'LinkedAppData': + return `${plugin.type}-${plugin.dataAuthority.type}${ + plugin.dataAuthority.address ? `-${plugin.dataAuthority.address}` : '' + }`; + case 'DataSection': + return `${plugin.type}-${getExternalPluginAdapterKeyAsString(plugin.parentKey)}`; + default: + throw new Error('Unknown ExternalPluginAdapter type'); } }; @@ -61,20 +75,16 @@ export const deriveExternalPluginAdapters = ( if (asset[key] || collection[key]) { externalPluginAdapters[key] = []; } - asset[key]?.forEach( - (plugin: OraclePlugin | DataStorePlugin | LifecycleHookPlugin) => { - set.add(getExternalPluginAdapterKeyAsString(plugin)); - externalPluginAdapters[key]?.push(plugin as any); - } - ); + asset[key]?.forEach((plugin: ExternalPluginAdapters) => { + set.add(getExternalPluginAdapterKeyAsString(plugin)); + externalPluginAdapters[key]?.push(plugin as any); + }); - collection[key]?.forEach( - (plugin: OraclePlugin | DataStorePlugin | LifecycleHookPlugin) => { - if (!set.has(getExternalPluginAdapterKeyAsString(plugin))) { - externalPluginAdapters[key]?.push(plugin as any); - } + collection[key]?.forEach((plugin: ExternalPluginAdapters) => { + if (!set.has(getExternalPluginAdapterKeyAsString(plugin))) { + externalPluginAdapters[key]?.push(plugin as any); } - ); + }); }); return externalPluginAdapters; @@ -97,10 +107,31 @@ export function deriveAssetPlugins( collection ); + // for every data section, find a matching linked plugin and inject the data for convenience + externalPluginAdapters.dataSections?.forEach((dataSection) => { + let appData; + let dataAuth: PluginAuthority; + switch (dataSection.parentKey.type) { + case 'LinkedAppData': + dataAuth = dataSection.parentKey.dataAuthority; + appData = externalPluginAdapters.linkedAppDatas?.find((plugin) => + comparePluginAuthorities(dataAuth, plugin.dataAuthority) + ); + if (appData) { + appData.data = dataSection.data; + } + break; + case 'LinkedLifecycleHook': + default: + throw new Error('LinkedLifecycleHook currently unsupported'); + } + }); + return { ...{ ...collection, - masterEdition: undefined, // master edition can only be on the collection + // the following plugins can only be on the collection + masterEdition: undefined, }, ...asset, ...externalPluginAdapters, diff --git a/clients/js/src/hooked/pluginRegistryV1Data.ts b/clients/js/src/hooked/pluginRegistryV1Data.ts index 00ee11e1..fc57926b 100644 --- a/clients/js/src/hooked/pluginRegistryV1Data.ts +++ b/clients/js/src/hooked/pluginRegistryV1Data.ts @@ -103,7 +103,7 @@ export function getAdapterRegistryRecordSerializer(): Serializer< offset = 0 ): [ExternalRegistryRecordWithUnknown, number] => { let [pluginType, pluginTypeOffset, isUnknown] = [ - ExternalPluginAdapterType.DataStore, + ExternalPluginAdapterType.AppData, offset + 1, true, ]; diff --git a/clients/js/src/instructions/create.ts b/clients/js/src/instructions/create.ts index e3099ac3..80204483 100644 --- a/clients/js/src/instructions/create.ts +++ b/clients/js/src/instructions/create.ts @@ -59,8 +59,8 @@ export const create = ( type: 'Oracle', }); break; - case 'DataStore': - // Do nothing, datastore has no extra accounts + case 'AppData': + // Do nothing, App Data has no extra accounts break; case 'LifecycleHook': assetExternalPluginAdapters.lifecycleHooks?.push({ diff --git a/clients/js/src/instructions/index.ts b/clients/js/src/instructions/index.ts index 49825646..d1d747ba 100644 --- a/clients/js/src/instructions/index.ts +++ b/clients/js/src/instructions/index.ts @@ -11,3 +11,4 @@ export * from './updatePlugin'; export * from './approvePluginAuthority'; export * from './revokePluginAuthority'; export * from './collection'; +export * from './writeData'; diff --git a/clients/js/src/instructions/writeData.ts b/clients/js/src/instructions/writeData.ts new file mode 100644 index 00000000..9793d189 --- /dev/null +++ b/clients/js/src/instructions/writeData.ts @@ -0,0 +1,28 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + writeExternalPluginAdapterDataV1, + WriteExternalPluginAdapterDataV1InstructionArgs, + WriteExternalPluginAdapterDataV1InstructionAccounts, +} from '../generated'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../plugins'; + +export type WriteDataArgs = Omit< + WriteExternalPluginAdapterDataV1InstructionArgs, + 'key' +> & { + key: ExternalPluginAdapterKey; +}; + +export const writeData = ( + context: Pick, + args: WriteDataArgs & WriteExternalPluginAdapterDataV1InstructionAccounts +) => { + const { key, ...rest } = args; + return writeExternalPluginAdapterDataV1(context, { + ...rest, + key: externalPluginAdapterKeyToBase(key), + }); +}; diff --git a/clients/js/src/plugins/dataStore.ts b/clients/js/src/plugins/appData.ts similarity index 58% rename from clients/js/src/plugins/dataStore.ts rename to clients/js/src/plugins/appData.ts index 04aa1ca0..fb17b469 100644 --- a/clients/js/src/plugins/dataStore.ts +++ b/clients/js/src/plugins/appData.ts @@ -1,7 +1,7 @@ import { - BaseDataStore, - BaseDataStoreInitInfoArgs, - BaseDataStoreUpdateInfoArgs, + BaseAppData, + BaseAppDataInitInfoArgs, + BaseAppDataUpdateInfoArgs, ExternalPluginAdapterSchema, ExternalRegistryRecord, } from '../generated'; @@ -16,39 +16,39 @@ import { pluginAuthorityToBase, } from './pluginAuthority'; -export type DataStore = Omit & { +export type AppData = Omit & { dataAuthority: PluginAuthority; data?: any; }; -export type DataStorePlugin = BaseExternalPluginAdapter & - DataStore & { - type: 'DataStore'; +export type AppDataPlugin = BaseExternalPluginAdapter & + AppData & { + type: 'AppData'; dataAuthority: PluginAuthority; }; -export type DataStoreInitInfoArgs = Omit< - BaseDataStoreInitInfoArgs, +export type AppDataInitInfoArgs = Omit< + BaseAppDataInitInfoArgs, 'initPluginAuthority' | 'lifecycleChecks' | 'dataAuthority' > & { - type: 'DataStore'; + type: 'AppData'; initPluginAuthority?: PluginAuthority; lifecycleChecks?: LifecycleChecks; schema?: ExternalPluginAdapterSchema; dataAuthority: PluginAuthority; }; -export type DataStoreUpdateInfoArgs = Omit< - BaseDataStoreUpdateInfoArgs, +export type AppDataUpdateInfoArgs = Omit< + BaseAppDataUpdateInfoArgs, 'schema' > & { key: ExternalPluginAdapterKey; schema?: ExternalPluginAdapterSchema; }; -export function dataStoreInitInfoArgsToBase( - d: DataStoreInitInfoArgs -): BaseDataStoreInitInfoArgs { +export function appDataInitInfoArgsToBase( + d: AppDataInitInfoArgs +): BaseAppDataInitInfoArgs { return { dataAuthority: pluginAuthorityToBase(d.dataAuthority), initPluginAuthority: d.initPluginAuthority @@ -58,19 +58,19 @@ export function dataStoreInitInfoArgsToBase( }; } -export function dataStoreUpdateInfoArgsToBase( - d: DataStoreUpdateInfoArgs -): BaseDataStoreUpdateInfoArgs { +export function appDataUpdateInfoArgsToBase( + d: AppDataUpdateInfoArgs +): BaseAppDataUpdateInfoArgs { return { schema: d.schema ? d.schema : null, }; } -export function dataStoreFromBase( - s: BaseDataStore, +export function appDataFromBase( + s: BaseAppData, r: ExternalRegistryRecord, account: Uint8Array -): DataStore { +): AppData { return { ...s, dataAuthority: pluginAuthorityFromBase(s.dataAuthority), @@ -78,16 +78,16 @@ export function dataStoreFromBase( }; } -export const dataStoreManifest: ExternalPluginAdapterManifest< - DataStore, - BaseDataStore, - DataStoreInitInfoArgs, - BaseDataStoreInitInfoArgs, - DataStoreUpdateInfoArgs, - BaseDataStoreUpdateInfoArgs +export const appDataManifest: ExternalPluginAdapterManifest< + AppData, + BaseAppData, + AppDataInitInfoArgs, + BaseAppDataInitInfoArgs, + AppDataUpdateInfoArgs, + BaseAppDataUpdateInfoArgs > = { - type: 'DataStore', - fromBase: dataStoreFromBase, - initToBase: dataStoreInitInfoArgsToBase, - updateToBase: dataStoreUpdateInfoArgsToBase, + type: 'AppData', + fromBase: appDataFromBase, + initToBase: appDataInitInfoArgsToBase, + updateToBase: appDataUpdateInfoArgsToBase, }; diff --git a/clients/js/src/plugins/dataSection.ts b/clients/js/src/plugins/dataSection.ts new file mode 100644 index 00000000..327b4055 --- /dev/null +++ b/clients/js/src/plugins/dataSection.ts @@ -0,0 +1,88 @@ +import { + BaseDataSection, + BaseDataSectionInitInfoArgs, + BaseDataSectionUpdateInfoArgs, + ExternalRegistryRecord, +} from '../generated'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { parseExternalPluginAdapterData } from './lib'; +import { + LinkedDataKey, + linkedDataKeyFromBase, + linkedDataKeyToBase, +} from './linkedDataKey'; +import { PluginAuthority, pluginAuthorityFromBase } from './pluginAuthority'; + +export type DataSection = Omit< + BaseDataSection, + 'dataAuthority' | 'parentKey' +> & { + dataAuthority?: PluginAuthority; + parentKey: LinkedDataKey; + data?: any; +}; + +export type DataSectionPlugin = BaseExternalPluginAdapter & + DataSection & { + type: 'DataSection'; + }; + +export type DataSectionInitInfoArgs = Omit< + BaseDataSectionInitInfoArgs, + 'parentKey' +> & { + type: 'DataSection'; + parentKey: LinkedDataKey; +}; + +export type DataSectionUpdateInfoArgs = BaseDataSectionUpdateInfoArgs & { + key: ExternalPluginAdapterKey; +}; + +export function dataSectionInitInfoArgsToBase( + d: DataSectionInitInfoArgs +): BaseDataSectionInitInfoArgs { + return { + parentKey: linkedDataKeyToBase(d.parentKey), + schema: d.schema, + }; +} + +export function dataSectionUpdateInfoArgsToBase( + d: DataSectionUpdateInfoArgs +): BaseDataSectionUpdateInfoArgs { + // You can't update the data section directly + return {}; +} + +export function dataSectionFromBase( + s: BaseDataSection, + r: ExternalRegistryRecord, + account: Uint8Array +): DataSection { + return { + ...s, + parentKey: linkedDataKeyFromBase(s.parentKey), + dataAuthority: + s.parentKey.__kind !== 'LinkedLifecycleHook' + ? pluginAuthorityFromBase(s.parentKey.fields[0]) + : undefined, + data: parseExternalPluginAdapterData(s, r, account), + }; +} + +export const dataSectionManifest: ExternalPluginAdapterManifest< + DataSection, + BaseDataSection, + DataSectionInitInfoArgs, + BaseDataSectionInitInfoArgs, + DataSectionUpdateInfoArgs, + BaseDataSectionUpdateInfoArgs +> = { + type: 'DataSection', + fromBase: dataSectionFromBase, + initToBase: dataSectionInitInfoArgsToBase, + updateToBase: dataSectionUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/externalPluginAdapterKey.ts b/clients/js/src/plugins/externalPluginAdapterKey.ts index e86610b0..8b89113b 100644 --- a/clients/js/src/plugins/externalPluginAdapterKey.ts +++ b/clients/js/src/plugins/externalPluginAdapterKey.ts @@ -1,38 +1,57 @@ import { PublicKey } from '@metaplex-foundation/umi'; import { BaseExternalPluginAdapterKey } from '../generated'; import { PluginAuthority, pluginAuthorityToBase } from './pluginAuthority'; +import { LinkedDataKey, linkedDataKeyToBase } from './linkedDataKey'; export type ExternalPluginAdapterKey = + | { + type: 'LifecycleHook'; + hookedProgram: PublicKey; + } | { type: 'Oracle'; baseAddress: PublicKey; } | { - type: 'DataStore'; + type: 'AppData'; dataAuthority: PluginAuthority; } | { - type: 'LifecycleHook'; - hookedProgram: PublicKey; - }; + type: 'LinkedLifecycleHook'; + dataAuthority: PublicKey; + } + | { + type: 'LinkedAppData'; + dataAuthority: PluginAuthority; + } + | { type: 'DataSection'; parentKey: LinkedDataKey }; export function externalPluginAdapterKeyToBase( e: ExternalPluginAdapterKey ): BaseExternalPluginAdapterKey { - if (e.type === 'Oracle') { - return { - __kind: 'Oracle', - fields: [e.baseAddress], - }; - } - if (e.type === 'DataStore') { - return { - __kind: 'DataStore', - fields: [pluginAuthorityToBase(e.dataAuthority)], - }; + switch (e.type) { + case 'Oracle': + return { + __kind: e.type, + fields: [e.baseAddress], + }; + case 'AppData': + case 'LinkedAppData': + return { + __kind: e.type, + fields: [pluginAuthorityToBase(e.dataAuthority)], + }; + case 'LifecycleHook': + return { + __kind: e.type, + fields: [e.hookedProgram], + }; + case 'DataSection': + return { + __kind: e.type, + fields: [linkedDataKeyToBase(e.parentKey)], + }; + default: + throw new Error('Unknown ExternalPluginAdapterKey type'); } - return { - __kind: 'LifecycleHook', - fields: [e.hookedProgram], - }; } diff --git a/clients/js/src/plugins/externalPluginAdapters.ts b/clients/js/src/plugins/externalPluginAdapters.ts index d5dcaef0..b276055d 100644 --- a/clients/js/src/plugins/externalPluginAdapters.ts +++ b/clients/js/src/plugins/externalPluginAdapters.ts @@ -16,12 +16,12 @@ import { } from '../generated'; import { - dataStoreFromBase, - DataStoreInitInfoArgs, - dataStoreManifest, - DataStorePlugin, - DataStoreUpdateInfoArgs, -} from './dataStore'; + appDataFromBase, + AppDataInitInfoArgs, + appDataManifest, + AppDataPlugin, + AppDataUpdateInfoArgs, +} from './appData'; import { LifecycleChecksContainer, lifecycleChecksFromBase, @@ -36,44 +36,92 @@ import { } from './oracle'; import { BasePlugin } from './types'; import { extraAccountToAccountMeta } from './extraAccount'; +import { + linkedAppDataFromBase, + LinkedAppDataInitInfoArgs, + linkedAppDataManifest, + LinkedAppDataPlugin, + LinkedAppDataUpdateInfoArgs, +} from './linkedAppData'; +import { + dataSectionFromBase, + dataSectionManifest, + DataSectionPlugin, +} from './dataSection'; +import { + LinkedLifecycleHookInitInfoArgs, + LinkedLifecycleHookPlugin, + LinkedLifecycleHookUpdateInfoArgs, + linkedLifecycleHookFromBase, + linkedLifecycleHookManifest, +} from './linkedLifecycleHook'; export type ExternalPluginAdapterTypeString = BaseExternalPluginAdapterKey['__kind']; export type BaseExternalPluginAdapter = BasePlugin & LifecycleChecksContainer; +export type ExternalPluginAdapters = + | LifecycleHookPlugin + | OraclePlugin + | AppDataPlugin + | LinkedLifecycleHookPlugin + | LinkedAppDataPlugin + | DataSectionPlugin; + export type ExternalPluginAdaptersList = { - oracles?: OraclePlugin[]; - dataStores?: DataStorePlugin[]; lifecycleHooks?: LifecycleHookPlugin[]; + oracles?: OraclePlugin[]; + appDatas?: AppDataPlugin[]; + linkedLifecycleHooks?: LinkedLifecycleHookPlugin[]; + linkedAppDatas?: LinkedAppDataPlugin[]; + dataSections?: DataSectionPlugin[]; }; export type ExternalPluginAdapterInitInfoArgs = + | ({ + type: 'LifecycleHook'; + } & LifecycleHookInitInfoArgs) | ({ type: 'Oracle'; } & OracleInitInfoArgs) | ({ - type: 'LifecycleHook'; - } & LifecycleHookInitInfoArgs) + type: 'AppData'; + } & AppDataInitInfoArgs) + | ({ + type: 'LinkedLifecycleHook'; + } & LinkedLifecycleHookInitInfoArgs) | ({ - type: 'DataStore'; - } & DataStoreInitInfoArgs); + type: 'LinkedAppData'; + } & LinkedAppDataInitInfoArgs) + | ({ + type: 'DataSection'; + } & AppDataInitInfoArgs); export type ExternalPluginAdapterUpdateInfoArgs = + | ({ + type: 'LifecycleHook'; + } & LifecycleHookUpdateInfoArgs) | ({ type: 'Oracle'; } & OracleUpdateInfoArgs) | ({ - type: 'LifecycleHook'; - } & LifecycleHookUpdateInfoArgs) + type: 'AppData'; + } & AppDataUpdateInfoArgs) | ({ - type: 'DataStore'; - } & DataStoreUpdateInfoArgs); + type: 'LinkedLifecycleHook'; + } & LinkedLifecycleHookUpdateInfoArgs) + | ({ + type: 'LinkedAppData'; + } & LinkedAppDataUpdateInfoArgs); export const externalPluginAdapterManifests = { - Oracle: oracleManifest, - DataStore: dataStoreManifest, LifecycleHook: lifecycleHookManifest, + Oracle: oracleManifest, + AppData: appDataManifest, + LinkedLifecycleHook: linkedLifecycleHookManifest, + LinkedAppData: linkedAppDataManifest, + DataSection: dataSectionManifest, }; export type ExternalPluginAdapterData = { @@ -102,7 +150,29 @@ export function externalRegistryRecordsToExternalPluginAdapterList( offset: record.offset, }; - if (deserializedPlugin.__kind === 'Oracle') { + if (deserializedPlugin.__kind === 'LifecycleHook') { + if (!result.lifecycleHooks) { + result.lifecycleHooks = []; + } + result.lifecycleHooks.push({ + type: 'LifecycleHook', + ...mappedPlugin, + ...lifecycleHookFromBase( + deserializedPlugin.fields[0], + record, + accountData + ), + }); + } else if (deserializedPlugin.__kind === 'AppData') { + if (!result.appDatas) { + result.appDatas = []; + } + result.appDatas.push({ + type: 'AppData', + ...mappedPlugin, + ...appDataFromBase(deserializedPlugin.fields[0], record, accountData), + }); + } else if (deserializedPlugin.__kind === 'Oracle') { if (!result.oracles) { result.oracles = []; } @@ -112,23 +182,40 @@ export function externalRegistryRecordsToExternalPluginAdapterList( ...mappedPlugin, ...oracleFromBase(deserializedPlugin.fields[0], record, accountData), }); - } else if (deserializedPlugin.__kind === 'DataStore') { - if (!result.dataStores) { - result.dataStores = []; + } else if (deserializedPlugin.__kind === 'LinkedLifecycleHook') { + if (!result.linkedLifecycleHooks) { + result.linkedLifecycleHooks = []; } - result.dataStores.push({ - type: 'DataStore', + result.linkedLifecycleHooks.push({ + type: 'LinkedLifecycleHook', ...mappedPlugin, - ...dataStoreFromBase(deserializedPlugin.fields[0], record, accountData), + ...linkedLifecycleHookFromBase( + deserializedPlugin.fields[0], + record, + accountData + ), }); - } else if (deserializedPlugin.__kind === 'LifecycleHook') { - if (!result.lifecycleHooks) { - result.lifecycleHooks = []; + } else if (deserializedPlugin.__kind === 'LinkedAppData') { + if (!result.linkedAppDatas) { + result.linkedAppDatas = []; } - result.lifecycleHooks.push({ - type: 'LifecycleHook', + result.linkedAppDatas.push({ + type: 'LinkedAppData', ...mappedPlugin, - ...lifecycleHookFromBase( + ...linkedAppDataFromBase( + deserializedPlugin.fields[0], + record, + accountData + ), + }); + } else if (deserializedPlugin.__kind === 'DataSection') { + if (!result.dataSections) { + result.dataSections = []; + } + result.dataSections.push({ + type: 'DataSection', + ...mappedPlugin, + ...dataSectionFromBase( deserializedPlugin.fields[0], record, accountData @@ -142,9 +229,12 @@ export function externalRegistryRecordsToExternalPluginAdapterList( export const isExternalPluginAdapterType = (plugin: { type: string }) => { if ( - plugin.type === 'Oracle' || plugin.type === 'LifecycleHook' || - plugin.type === 'DataStore' + plugin.type === 'Oracle' || + plugin.type === 'AppData' || + plugin.type === 'LinkedLifecycleHook' || + plugin.type === 'DataSection' || + plugin.type === 'LinkedAppData' ) { return true; } @@ -224,5 +314,24 @@ export const findExtraAccounts = ( } }); + externalPluginAdapters.linkedLifecycleHooks?.forEach((hook) => { + if (hook.lifecycleChecks?.[lifecycle]) { + accounts.push({ + pubkey: hook.hookedProgram, + isSigner: false, + isWritable: false, + }); + + hook.extraAccounts?.forEach((extra) => { + accounts.push( + extraAccountToAccountMeta(context, extra, { + ...inputs, + program: hook.hookedProgram, + }) + ); + }); + } + }); + return accounts; }; diff --git a/clients/js/src/plugins/index.ts b/clients/js/src/plugins/index.ts index 9bff9abb..29777a81 100644 --- a/clients/js/src/plugins/index.ts +++ b/clients/js/src/plugins/index.ts @@ -1,6 +1,6 @@ export * from './royalties'; export * from './lib'; -export * from './dataStore'; +export * from './appData'; export * from './lifecycleChecks'; export * from './lifecycleHook'; export * from './oracle'; @@ -13,4 +13,8 @@ export * from './updateAuthority'; export * from './seed'; export * from './extraAccount'; export * from './validationResultsOffset'; +export * from './linkedLifecycleHook'; +export * from './linkedAppData'; +export * from './dataSection'; +export * from './linkedDataKey'; export * from './masterEdition'; diff --git a/clients/js/src/plugins/lib.ts b/clients/js/src/plugins/lib.ts index 14dd4798..e8345249 100644 --- a/clients/js/src/plugins/lib.ts +++ b/clients/js/src/plugins/lib.ts @@ -1,4 +1,4 @@ -import { none, Option, some } from '@metaplex-foundation/umi'; +import { isSome, none, Option, some } from '@metaplex-foundation/umi'; import { Key, @@ -215,21 +215,32 @@ export function parseExternalPluginAdapterData( account: Uint8Array ): any { let data; - const dataSlice = account.slice( - Number(record.dataOffset), - Number(record.dataOffset) + Number(record.dataLen) - ); - - if (plugin.schema === ExternalPluginAdapterSchema.Binary) { - data = dataSlice; - } else if (plugin.schema === ExternalPluginAdapterSchema.Json) { - data = JSON.parse(new TextDecoder().decode(dataSlice)); - } else if (plugin.schema === ExternalPluginAdapterSchema.MsgPack) { - // eslint-disable-next-line no-console - console.warn( - 'MsgPack schema currently not supported, falling back to binary' + if (isSome(record.dataOffset) && isSome(record.dataLen)) { + const dataSlice = account.slice( + Number(record.dataOffset.value), + Number(record.dataOffset.value) + Number(record.dataLen.value) ); - data = dataSlice; + + if (plugin.schema === ExternalPluginAdapterSchema.Binary) { + data = dataSlice; + } else if (plugin.schema === ExternalPluginAdapterSchema.Json) { + // if data is empty, the data slice is uninitialized and should be ignored + if (dataSlice.length !== 0) { + try { + data = JSON.parse(new TextDecoder().decode(dataSlice)); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('Invalid JSON in external plugin data', e.message); + } + } + } else if (plugin.schema === ExternalPluginAdapterSchema.MsgPack) { + // eslint-disable-next-line no-console + console.warn( + 'MsgPack schema currently not supported, falling back to binary' + ); + data = dataSlice; + } + return data; } - return data; + throw new Error('Invalid DataStore, missing dataOffset or dataLen'); } diff --git a/clients/js/src/plugins/linkedAppData.ts b/clients/js/src/plugins/linkedAppData.ts new file mode 100644 index 00000000..df7e5c1a --- /dev/null +++ b/clients/js/src/plugins/linkedAppData.ts @@ -0,0 +1,92 @@ +import { + BaseLinkedAppData, + BaseLinkedAppDataInitInfoArgs, + BaseLinkedAppDataUpdateInfoArgs, + ExternalPluginAdapterSchema, + ExternalRegistryRecord, +} from '../generated'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { LifecycleChecks } from './lifecycleChecks'; +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; + +export type LinkedAppData = Omit & { + dataAuthority: PluginAuthority; + data?: any; +}; + +export type LinkedAppDataPlugin = BaseExternalPluginAdapter & + LinkedAppData & { + type: 'LinkedAppData'; + dataAuthority: PluginAuthority; + }; + +export type LinkedAppDataInitInfoArgs = Omit< + BaseLinkedAppDataInitInfoArgs, + 'initPluginAuthority' | 'lifecycleChecks' | 'dataAuthority' +> & { + type: 'LinkedAppData'; + initPluginAuthority?: PluginAuthority; + lifecycleChecks?: LifecycleChecks; + schema?: ExternalPluginAdapterSchema; + dataAuthority: PluginAuthority; +}; + +export type LinkedAppDataUpdateInfoArgs = Omit< + BaseLinkedAppDataUpdateInfoArgs, + 'schema' +> & { + key: ExternalPluginAdapterKey; + schema?: ExternalPluginAdapterSchema; +}; + +export function linkedAppDataInitInfoArgsToBase( + d: LinkedAppDataInitInfoArgs +): BaseLinkedAppDataInitInfoArgs { + return { + dataAuthority: pluginAuthorityToBase(d.dataAuthority), + initPluginAuthority: d.initPluginAuthority + ? pluginAuthorityToBase(d.initPluginAuthority) + : null, + schema: d.schema ? d.schema : null, + }; +} + +export function linkedAppDataUpdateInfoArgsToBase( + d: LinkedAppDataUpdateInfoArgs +): BaseLinkedAppDataUpdateInfoArgs { + return { + schema: d.schema ? d.schema : null, + }; +} + +export function linkedAppDataFromBase( + s: BaseLinkedAppData, + r: ExternalRegistryRecord, + account: Uint8Array +): LinkedAppData { + return { + ...s, + dataAuthority: pluginAuthorityFromBase(s.dataAuthority), + // plugin has no data but injected in the derivation of the asset + }; +} + +export const linkedAppDataManifest: ExternalPluginAdapterManifest< + LinkedAppData, + BaseLinkedAppData, + LinkedAppDataInitInfoArgs, + BaseLinkedAppDataInitInfoArgs, + LinkedAppDataUpdateInfoArgs, + BaseLinkedAppDataUpdateInfoArgs +> = { + type: 'LinkedAppData', + fromBase: linkedAppDataFromBase, + initToBase: linkedAppDataInitInfoArgsToBase, + updateToBase: linkedAppDataUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/linkedDataKey.ts b/clients/js/src/plugins/linkedDataKey.ts new file mode 100644 index 00000000..5cafe496 --- /dev/null +++ b/clients/js/src/plugins/linkedDataKey.ts @@ -0,0 +1,52 @@ +import { PublicKey } from '@metaplex-foundation/umi'; + +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; +import { BaseLinkedDataKey } from '../generated'; + +export type LinkedDataKey = + | { + type: 'LinkedLifecycleHook'; + hookedProgram: PublicKey; + } + | { + type: 'LinkedAppData'; + dataAuthority: PluginAuthority; + }; + +export function linkedDataKeyToBase(e: LinkedDataKey): BaseLinkedDataKey { + switch (e.type) { + case 'LinkedLifecycleHook': + return { + __kind: e.type, + fields: [e.hookedProgram], + }; + case 'LinkedAppData': + return { + __kind: e.type, + fields: [pluginAuthorityToBase(e.dataAuthority)], + }; + default: + throw new Error('Unknown LinkedDataKey type'); + } +} + +export function linkedDataKeyFromBase(e: BaseLinkedDataKey): LinkedDataKey { + switch (e.__kind) { + case 'LinkedLifecycleHook': + return { + type: e.__kind, + hookedProgram: e.fields[0], + }; + case 'LinkedAppData': + return { + type: e.__kind, + dataAuthority: pluginAuthorityFromBase(e.fields[0]), + }; + default: + throw new Error('Unknown LinkedDataKey type'); + } +} diff --git a/clients/js/src/plugins/linkedLifecycleHook.ts b/clients/js/src/plugins/linkedLifecycleHook.ts new file mode 100644 index 00000000..a0846bde --- /dev/null +++ b/clients/js/src/plugins/linkedLifecycleHook.ts @@ -0,0 +1,129 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { + ExtraAccount, + extraAccountFromBase, + extraAccountToBase, +} from './extraAccount'; +import { + BaseLinkedLifecycleHook, + BaseLinkedLifecycleHookInitInfoArgs, + BaseLinkedLifecycleHookUpdateInfoArgs, + ExternalPluginAdapterSchema, + ExternalRegistryRecord, +} from '../generated'; +import { LifecycleChecks, lifecycleChecksToBase } from './lifecycleChecks'; +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +// import { parseExternalPluginAdapterData } from './lib'; + +export type LinkedLifecycleHook = Omit< + BaseLinkedLifecycleHook, + 'extraAccounts' | 'dataAuthority' +> & { + extraAccounts?: Array; + dataAuthority?: PluginAuthority; + data?: any; +}; + +export type LinkedLifecycleHookPlugin = BaseExternalPluginAdapter & + LinkedLifecycleHook & { + type: 'LinkedLifecycleHook'; + hookedProgram: PublicKey; + }; + +export type LinkedLifecycleHookInitInfoArgs = Omit< + BaseLinkedLifecycleHookInitInfoArgs, + | 'initPluginAuthority' + | 'lifecycleChecks' + | 'schema' + | 'extraAccounts' + | 'dataAuthority' +> & { + type: 'LinkedLifecycleHook'; + initPluginAuthority?: PluginAuthority; + lifecycleChecks: LifecycleChecks; + schema?: ExternalPluginAdapterSchema; + extraAccounts?: Array; + dataAuthority?: PluginAuthority; +}; + +export type LinkedLifecycleHookUpdateInfoArgs = Omit< + BaseLinkedLifecycleHookUpdateInfoArgs, + 'lifecycleChecks' | 'extraAccounts' | 'schema' +> & { + key: ExternalPluginAdapterKey; + lifecycleChecks?: LifecycleChecks; + extraAccounts?: Array; + schema?: ExternalPluginAdapterSchema; +}; + +export function linkedLifecycleHookInitInfoArgsToBase( + l: LinkedLifecycleHookInitInfoArgs +): BaseLinkedLifecycleHookInitInfoArgs { + return { + extraAccounts: l.extraAccounts + ? l.extraAccounts.map(extraAccountToBase) + : null, + hookedProgram: l.hookedProgram, + initPluginAuthority: l.initPluginAuthority + ? pluginAuthorityToBase(l.initPluginAuthority) + : null, + lifecycleChecks: lifecycleChecksToBase(l.lifecycleChecks), + schema: l.schema ? l.schema : null, + dataAuthority: l.dataAuthority + ? pluginAuthorityToBase(l.dataAuthority) + : null, + }; +} + +export function linkedLifecycleHookUpdateInfoArgsToBase( + l: LinkedLifecycleHookUpdateInfoArgs +): BaseLinkedLifecycleHookUpdateInfoArgs { + return { + lifecycleChecks: l.lifecycleChecks + ? lifecycleChecksToBase(l.lifecycleChecks) + : null, + extraAccounts: l.extraAccounts + ? l.extraAccounts.map(extraAccountToBase) + : null, + schema: l.schema ? l.schema : null, + }; +} + +export function linkedLifecycleHookFromBase( + s: BaseLinkedLifecycleHook, + r: ExternalRegistryRecord, + account: Uint8Array +): LinkedLifecycleHook { + return { + ...s, + extraAccounts: + s.extraAccounts.__option === 'Some' + ? s.extraAccounts.value.map(extraAccountFromBase) + : undefined, + dataAuthority: + s.dataAuthority.__option === 'Some' + ? pluginAuthorityFromBase(s.dataAuthority.value) + : undefined, + }; +} + +export const linkedLifecycleHookManifest: ExternalPluginAdapterManifest< + LinkedLifecycleHook, + BaseLinkedLifecycleHook, + LinkedLifecycleHookInitInfoArgs, + BaseLinkedLifecycleHookInitInfoArgs, + LinkedLifecycleHookUpdateInfoArgs, + BaseLinkedLifecycleHookUpdateInfoArgs +> = { + type: 'LinkedLifecycleHook', + fromBase: linkedLifecycleHookFromBase, + initToBase: linkedLifecycleHookInitInfoArgsToBase, + updateToBase: linkedLifecycleHookUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/pluginAuthority.ts b/clients/js/src/plugins/pluginAuthority.ts index b0a4f0b3..e75e2365 100644 --- a/clients/js/src/plugins/pluginAuthority.ts +++ b/clients/js/src/plugins/pluginAuthority.ts @@ -28,3 +28,13 @@ export function pluginAuthorityFromBase( address: (authority as any).address, }; } +export function comparePluginAuthorities( + a: PluginAuthority, + b: PluginAuthority +): boolean { + if (a.type !== b.type) { + return false; + } + + return a.address === b.address; +} diff --git a/clients/js/test/_setupRaw.ts b/clients/js/test/_setupRaw.ts index 685ba375..bc2f696e 100644 --- a/clients/js/test/_setupRaw.ts +++ b/clients/js/test/_setupRaw.ts @@ -24,6 +24,7 @@ import { ExternalPluginAdaptersList, AssetPluginsList, CollectionPluginsList, + fetchAsset, } from '../src'; export const createUmi = async () => (await basecreateUmi()).use(mplCore()); @@ -38,7 +39,6 @@ export type CreateAssetHelperArgs = { authority?: Signer; updateAuthority?: PublicKey | Signer; collection?: PublicKey; - // TODO use PluginList type here plugins?: PluginAuthorityPairArgs[]; }; @@ -76,8 +76,6 @@ export const createAsset = async ( authority: input.authority, }).sendAndConfirm(umi); - // console.log("Creating with:", input.plugins, "cost", (await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); - return fetchAssetV1(umi, publicKey(asset)); }; @@ -87,7 +85,6 @@ export type CreateCollectionHelperArgs = { name?: string; uri?: string; updateAuthority?: PublicKey | Signer; - // TODO use CollectionPluginList type here plugins?: PluginAuthorityPairArgs[]; }; @@ -147,11 +144,16 @@ export const assertAsset = async ( name?: string | RegExp; uri?: string | RegExp; } & AssetPluginsList & - ExternalPluginAdaptersList + ExternalPluginAdaptersList, + options: { derivePlugins: boolean } = { + derivePlugins: false, + } ) => { const { asset, owner, name, uri, ...rest } = input; const assetAddress = publicKey(input.asset); - const assetWithPlugins = await fetchAssetV1(umi, assetAddress); + const assetWithPlugins = options.derivePlugins + ? await fetchAsset(umi, assetAddress) + : await fetchAssetV1(umi, assetAddress); // Name. if (typeof name === 'string') t.is(assetWithPlugins.name, name); diff --git a/clients/js/test/externalPlugins/appData.ts b/clients/js/test/externalPlugins/appData.ts new file mode 100644 index 00000000..a1f48a25 --- /dev/null +++ b/clients/js/test/externalPlugins/appData.ts @@ -0,0 +1,513 @@ +import test from 'ava'; +import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; +import { Signer, Umi } from '@metaplex-foundation/umi'; +import { assertAsset, createUmi, DEFAULT_ASSET } from '../_setupRaw'; +import { createAsset } from '../_setupSdk'; +import { + ExternalPluginAdapterSchema, + PluginAuthority, + PluginAuthorityType, + updatePlugin, + writeData, +} from '../../src'; + +const DATA_AUTHORITIES: PluginAuthorityType[] = [ + 'Owner', + 'UpdateAuthority', + 'Address', +]; +const SCHEMAS: ExternalPluginAdapterSchema[] = [ + ExternalPluginAdapterSchema.Binary, + ExternalPluginAdapterSchema.Json /* , ExternalPluginAdapterSchema.MsgPack */, +]; + +type TestContext = { + umi: Umi; + owner: Signer; + dataAuthoritySigner: Signer; + dataAuthority: PluginAuthority; + wrongDataAuthoritySigner?: Signer; + wrongDataAuthority?: PluginAuthority; + data: string; + otherData: string; +}; + +async function generateTestContext( + dataAuthorityType: PluginAuthorityType, + schema: ExternalPluginAdapterSchema, + wrongDataAuthorityType?: PluginAuthorityType +): Promise { + const umi = await createUmi(); + + const owner = await generateSignerWithSol(umi); + let dataAuthoritySigner = null; + let dataAuthority = null; + if (dataAuthorityType === 'Address') { + dataAuthoritySigner = await generateSignerWithSol(umi); + dataAuthority = { + type: dataAuthorityType, + address: dataAuthoritySigner.publicKey, + }; + } else if (dataAuthorityType === 'UpdateAuthority') { + dataAuthoritySigner = umi.identity; + dataAuthority = { type: dataAuthorityType }; + } else if (dataAuthorityType === 'Owner') { + dataAuthoritySigner = owner; + dataAuthority = { type: dataAuthorityType }; + } + + let wrongDataAuthoritySigner; + let wrongDataAuthority; + if (wrongDataAuthorityType) { + if (wrongDataAuthorityType === 'Address') { + wrongDataAuthoritySigner = await generateSignerWithSol(umi); + wrongDataAuthority = { + type: wrongDataAuthorityType, + address: wrongDataAuthoritySigner.publicKey, + }; + } else if (wrongDataAuthorityType === 'UpdateAuthority') { + wrongDataAuthoritySigner = umi.identity; + wrongDataAuthority = { type: wrongDataAuthorityType }; + } else if (wrongDataAuthorityType === 'Owner') { + wrongDataAuthoritySigner = owner; + wrongDataAuthority = { type: wrongDataAuthorityType }; + } + } + + let data = ''; + let otherData = ''; + if (schema === ExternalPluginAdapterSchema.Binary) { + data = 'Hello, world!'; + otherData = 'Hello, world! Hello, world!'; + } else if (schema === ExternalPluginAdapterSchema.Json) { + data = JSON.stringify({ message: 'Hello', target: 'world' }); + otherData = JSON.stringify({ + message: 'Hello hello', + target: 'big wide world', + }); + } + + if (!dataAuthoritySigner) { + throw new Error('Data authority signer not set'); + } + if (!dataAuthority) { + throw new Error('Data authority not set'); + } + + return { + umi, + owner, + dataAuthoritySigner, + dataAuthority, + wrongDataAuthoritySigner, + wrongDataAuthority, + data, + otherData, + }; +} + +DATA_AUTHORITIES.forEach((dataAuthorityType) => { + SCHEMAS.forEach((schema) => { + test(`it can create a secure app data with ${dataAuthorityType} as data authority and ${ExternalPluginAdapterSchema[schema]} as schema`, async (t) => { + const { umi, dataAuthority, owner } = await generateTestContext( + dataAuthorityType, + schema + ); + + // create asset referencing the oracle account + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'AppData', + dataAuthority, + schema, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + }); + + test(`it can write ${ExternalPluginAdapterSchema[schema]} data to a secure app data as ${dataAuthorityType} data authority`, async (t) => { + const { umi, owner, dataAuthoritySigner, dataAuthority, data } = + await generateTestContext(dataAuthorityType, schema); + + // create asset with the Secure App Data + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'AppData', + dataAuthority, + schema, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'AppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + let assertData = null; + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(data)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(data); + } + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + }); + }); + + test(`it can write ${ExternalPluginAdapterSchema[schema]} data to a secure app data as ${dataAuthorityType} data authority multiple times`, async (t) => { + const { + umi, + owner, + dataAuthoritySigner, + dataAuthority, + data, + otherData, + } = await generateTestContext(dataAuthorityType, schema); + + // create asset with the Secure App Data + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'AppData', + dataAuthority, + schema, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'AppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + let assertData = null; + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(data)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(data); + } + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'AppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(otherData)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(otherData)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(otherData); + } + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + }); + }); + }); + + DATA_AUTHORITIES.filter( + (da) => da === 'Address' || da !== dataAuthorityType + ).forEach((otherDataAuthorityType) => { + test(`it cannot write data to a secure app data with ${dataAuthorityType} data authority using ${otherDataAuthorityType} data authority`, async (t) => { + const { umi, owner, dataAuthority, wrongDataAuthoritySigner, data } = + await generateTestContext( + dataAuthorityType, + ExternalPluginAdapterSchema.Binary, + otherDataAuthorityType + ); + // create asset with the Secure App Data + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'AppData', + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + const res = writeData(umi, { + key: { + type: 'AppData', + dataAuthority, + }, + authority: wrongDataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(res, { name: 'InvalidAuthority' }); + }); + }); + + test(`it cannot write data to a secure app data as ${dataAuthorityType} data authority if the data authority is None`, async (t) => { + const { umi, owner, dataAuthoritySigner, data } = await generateTestContext( + dataAuthorityType, + ExternalPluginAdapterSchema.Binary + ); + + // create asset with the Secure App Data + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'AppData', + dataAuthority: { type: 'None' }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority: { type: 'None' }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + const res = writeData(umi, { + key: { + type: 'AppData', + dataAuthority: { type: 'None' }, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(res, { name: 'InvalidAuthority' }); + }); +}); + +test(`updating a plugin before a secure app data does not corrupt the data`, async (t) => { + const { umi, owner, dataAuthoritySigner, dataAuthority, data } = + await generateTestContext( + 'UpdateAuthority', + ExternalPluginAdapterSchema.Json + ); + + // create asset with the Secure App Data + const asset = await createAsset(umi, { + owner: owner.publicKey, + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'Test', + value: 'Test', + }, + ], + }, + { + type: 'AppData', + dataAuthority, + schema: ExternalPluginAdapterSchema.Json, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'AppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + const assertData = JSON.parse(data); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: 'Test', value: 'Test' }], + }, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Json, + data: assertData, + }, + ], + }); + + await updatePlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'Updated Test', value: 'Updated Test' }], + }, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: 'Updated Test', value: 'Updated Test' }], + }, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Json, + data: assertData, + }, + ], + }); + + await updatePlugin(umi, { + asset: asset.publicKey, + plugin: { type: 'Attributes', attributeList: [{ key: '', value: '' }] }, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: '', value: '' }], + }, + appDatas: [ + { + type: 'AppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Json, + data: assertData, + }, + ], + }); +}); diff --git a/clients/js/test/externalPlugins/dataSection.test.ts b/clients/js/test/externalPlugins/dataSection.test.ts new file mode 100644 index 00000000..e130cdf4 --- /dev/null +++ b/clients/js/test/externalPlugins/dataSection.test.ts @@ -0,0 +1,112 @@ +import { SPL_SYSTEM_PROGRAM_ID } from '@metaplex-foundation/mpl-toolbox'; +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { createCollection, createAsset } from '../_setupSdk'; +import { createUmi, DEFAULT_ASSET } from '../_setupRaw'; +import { + addCollectionExternalPluginAdapterV1, + addExternalPluginAdapterV1, + createCollectionV2, + createV2, + ExternalPluginAdapterSchema, +} from '../../src'; + +test('it cannot create an asset with a DataSection', async (t) => { + const umi = await createUmi(); + const asset = await generateSigner(umi); + + const result = createV2(umi, { + asset, + ...DEFAULT_ASSET, + externalPluginAdapters: [ + { + __kind: 'DataSection', + fields: [ + { + parentKey: { + __kind: 'LinkedLifecycleHook', + fields: [SPL_SYSTEM_PROGRAM_ID], + }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + ], + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'CannotAddDataSection' }); +}); + +test('it cannot create a collection with a DataSection', async (t) => { + const umi = await createUmi(); + const collection = await generateSigner(umi); + + const result = createCollectionV2(umi, { + collection, + ...DEFAULT_ASSET, + externalPluginAdapters: [ + { + __kind: 'DataSection', + fields: [ + { + parentKey: { + __kind: 'LinkedLifecycleHook', + fields: [SPL_SYSTEM_PROGRAM_ID], + }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + ], + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'CannotAddDataSection' }); +}); + +test('it cannot add a DataSection to an asset', async (t) => { + const umi = await createUmi(); + + const asset = await createAsset(umi); + + const result = addExternalPluginAdapterV1(umi, { + asset: asset.publicKey, + initInfo: { + __kind: 'DataSection', + fields: [ + { + parentKey: { + __kind: 'LinkedLifecycleHook', + fields: [SPL_SYSTEM_PROGRAM_ID], + }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); +}); + +test('it cannot add a DataSection to a collection', async (t) => { + const umi = await createUmi(); + + const collection = await createCollection(umi); + + const result = addCollectionExternalPluginAdapterV1(umi, { + collection: collection.publicKey, + initInfo: { + __kind: 'DataSection', + fields: [ + { + parentKey: { + __kind: 'LinkedLifecycleHook', + fields: [SPL_SYSTEM_PROGRAM_ID], + }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); +}); diff --git a/clients/js/test/externalPlugins/linkedAppData.test.ts b/clients/js/test/externalPlugins/linkedAppData.test.ts new file mode 100644 index 00000000..4df3c1f5 --- /dev/null +++ b/clients/js/test/externalPlugins/linkedAppData.test.ts @@ -0,0 +1,688 @@ +import test from 'ava'; +import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; +import { Signer, Umi } from '@metaplex-foundation/umi'; +import { + assertAsset, + assertCollection, + createUmi, + DEFAULT_ASSET, + DEFAULT_COLLECTION, +} from '../_setupRaw'; +import { createAssetWithCollection } from '../_setupSdk'; +import { + ExternalPluginAdapterSchema, + PluginAuthority, + PluginAuthorityType, + updatePlugin, + writeData, +} from '../../src'; + +const DATA_AUTHORITIES: PluginAuthorityType[] = [ + 'Owner', + 'UpdateAuthority', + 'Address', +]; +const SCHEMAS: ExternalPluginAdapterSchema[] = [ + ExternalPluginAdapterSchema.Binary, + ExternalPluginAdapterSchema.Json /* , ExternalPluginAdapterSchema.MsgPack */, +]; + +type TestContext = { + umi: Umi; + owner: Signer; + dataAuthoritySigner: Signer; + dataAuthority: PluginAuthority; + wrongDataAuthoritySigner?: Signer; + wrongDataAuthority?: PluginAuthority; + data: string; + otherData: string; +}; + +async function generateTestContext( + dataAuthorityType: PluginAuthorityType, + schema: ExternalPluginAdapterSchema, + wrongDataAuthorityType?: PluginAuthorityType +): Promise { + const umi = await createUmi(); + + const owner = await generateSignerWithSol(umi); + let dataAuthoritySigner = null; + let dataAuthority = null; + if (dataAuthorityType === 'Address') { + dataAuthoritySigner = await generateSignerWithSol(umi); + dataAuthority = { + type: dataAuthorityType, + address: dataAuthoritySigner.publicKey, + }; + } else if (dataAuthorityType === 'UpdateAuthority') { + dataAuthoritySigner = umi.identity; + dataAuthority = { type: dataAuthorityType }; + } else if (dataAuthorityType === 'Owner') { + dataAuthoritySigner = owner; + dataAuthority = { type: dataAuthorityType }; + } + + let wrongDataAuthoritySigner; + let wrongDataAuthority; + if (wrongDataAuthorityType) { + if (wrongDataAuthorityType === 'Address') { + wrongDataAuthoritySigner = await generateSignerWithSol(umi); + wrongDataAuthority = { + type: wrongDataAuthorityType, + address: wrongDataAuthoritySigner.publicKey, + }; + } else if (wrongDataAuthorityType === 'UpdateAuthority') { + wrongDataAuthoritySigner = umi.identity; + wrongDataAuthority = { type: wrongDataAuthorityType }; + } else if (wrongDataAuthorityType === 'Owner') { + wrongDataAuthoritySigner = owner; + wrongDataAuthority = { type: wrongDataAuthorityType }; + } + } + + let data = ''; + let otherData = ''; + if (schema === ExternalPluginAdapterSchema.Binary) { + data = 'Hello, world!'; + otherData = 'Hello, world! Hello, world!'; + } else if (schema === ExternalPluginAdapterSchema.Json) { + data = JSON.stringify({ message: 'Hello', target: 'world' }); + otherData = JSON.stringify({ + message: 'Hello hello', + target: 'big wide world', + }); + } + + if (!dataAuthoritySigner) { + throw new Error('Data authority signer not set'); + } + if (!dataAuthority) { + throw new Error('Data authority not set'); + } + + return { + umi, + owner, + dataAuthoritySigner, + dataAuthority, + wrongDataAuthoritySigner, + wrongDataAuthority, + data, + otherData, + }; +} + +DATA_AUTHORITIES.forEach((dataAuthorityType) => { + SCHEMAS.forEach((schema) => { + test(`it can create an linked secure app data with ${dataAuthorityType} as data authority and ${ExternalPluginAdapterSchema[schema]} as schema`, async (t) => { + const { umi, dataAuthority, owner } = await generateTestContext( + dataAuthorityType, + schema + ); + + // create collection with linked secure app data + const { collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority, + schema, + }, + ], + } + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + }); + + test(`it can write ${ExternalPluginAdapterSchema[schema]} data to an linked secure app data as ${dataAuthorityType} data authority`, async (t) => { + const { umi, owner, dataAuthoritySigner, dataAuthority, data } = + await generateTestContext(dataAuthorityType, schema); + + // create collection with linked secure app data + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority, + schema, + }, + ], + } + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + collection: collection.publicKey, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + let assertData = null; + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(data)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(data); + } + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema, + }, + ], + }, + { + derivePlugins: true, + } + ); + }); + + test(`it can write ${ExternalPluginAdapterSchema[schema]} data to an linked secure app data as ${dataAuthorityType} data authority multiple times`, async (t) => { + const { + umi, + owner, + dataAuthoritySigner, + dataAuthority, + data, + otherData, + } = await generateTestContext(dataAuthorityType, schema); + + // create collection with linked secure app data + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority, + schema, + }, + ], + } + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + }, + ], + }); + + await writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + collection: collection.publicKey, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + let assertData = null; + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(data)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(data); + } + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema, + }, + ], + }, + { + derivePlugins: true, + } + ); + + await writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + collection: collection.publicKey, + data: Uint8Array.from(Buffer.from(otherData)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + if (schema === ExternalPluginAdapterSchema.Binary) { + assertData = Uint8Array.from(Buffer.from(otherData)); + } else if (schema === ExternalPluginAdapterSchema.Json) { + assertData = JSON.parse(otherData); + } + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema, + }, + ], + }, + { + derivePlugins: true, + } + ); + }); + }); + + DATA_AUTHORITIES.filter( + (da) => da === 'Address' || da !== dataAuthorityType + ).forEach((otherDataAuthorityType) => { + test(`it cannot write data to an linked secure app data with ${dataAuthorityType} data authority using ${otherDataAuthorityType} data authority`, async (t) => { + const { umi, owner, dataAuthority, wrongDataAuthoritySigner, data } = + await generateTestContext( + dataAuthorityType, + ExternalPluginAdapterSchema.Binary, + otherDataAuthorityType + ); + + // create collection with linked secure app data + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + } + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + const res = writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: wrongDataAuthoritySigner, + collection: collection.publicKey, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(res, { name: 'InvalidAuthority' }); + }); + }); + + test(`it cannot write data to an linked secure app data as ${dataAuthorityType} data authority if the data authority is None`, async (t) => { + const { umi, owner, dataAuthoritySigner, data } = await generateTestContext( + dataAuthorityType, + ExternalPluginAdapterSchema.Binary + ); + + // create collection with linked secure app data + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority: { type: 'None' }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + } + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority: { type: 'None' }, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + const res = writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority: { type: 'None' }, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + collection: collection.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(res, { name: 'InvalidAuthority' }); + }); +}); + +test(`updating a plugin before a secure app data does not corrupt the data`, async (t) => { + const { umi, owner, dataAuthoritySigner, dataAuthority, data } = + await generateTestContext( + 'UpdateAuthority', + ExternalPluginAdapterSchema.Binary + ); + + // create collection with linked secure app data + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner: owner.publicKey, + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'Test', + value: 'Test', + }, + ], + }, + ], + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + } + ); + + await writeData(umi, { + key: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: dataAuthoritySigner, + data: Uint8Array.from(Buffer.from(data)), + asset: asset.publicKey, + collection: collection.publicKey, + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }); + + const assertData = Uint8Array.from(Buffer.from(data)); + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: 'Test', value: 'Test' }], + }, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + { + derivePlugins: true, + } + ); + + await updatePlugin(umi, { + asset: asset.publicKey, + collection: collection.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'Updated Test', value: 'Updated Test' }], + }, + }).sendAndConfirm(umi); + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: 'Updated Test', value: 'Updated Test' }], + }, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + { + derivePlugins: true, + } + ); + + await updatePlugin(umi, { + asset: asset.publicKey, + collection: collection.publicKey, + plugin: { type: 'Attributes', attributeList: [{ key: '', value: '' }] }, + }).sendAndConfirm(umi); + + // check the derived asset sdk correctly injects the data + await assertAsset( + t, + umi, + { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + attributes: { + authority: { type: 'UpdateAuthority' }, + attributeList: [{ key: '', value: '' }], + }, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { type: 'UpdateAuthority' }, + dataAuthority, + schema: ExternalPluginAdapterSchema.Binary, + data: assertData, + }, + ], + dataSections: [ + { + type: 'DataSection', + parentKey: { + type: 'LinkedAppData', + dataAuthority, + }, + authority: { type: 'None' }, + data: assertData, + schema: ExternalPluginAdapterSchema.Binary, + }, + ], + }, + { + derivePlugins: true, + } + ); +}); diff --git a/clients/js/test/sdkv1.test.ts b/clients/js/test/sdkv1.test.ts index 33764222..b9c50e19 100644 --- a/clients/js/test/sdkv1.test.ts +++ b/clients/js/test/sdkv1.test.ts @@ -16,6 +16,7 @@ import { CollectionAddablePluginAuthorityPairArgsV2, addCollectionPlugin, removeCollectionPlugin, + ExternalPluginAdapterSchema, } from '../src'; import { createAsset, @@ -1031,3 +1032,80 @@ test('it can burn asset in collection', async (t) => { t.is(afterAsset.data.length, 1); t.is(afterAsset.data[0], Key.Uninitialized); }); + +test('it can fetch asset which correctly derived plugins', async (t) => { + const umi = await createUmi(); + const dataAuth = generateSigner(umi); + const { asset, collection } = await createAssetWithCollection( + umi, + { + plugins: [ + { + type: 'Edition', + number: 1, + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + ], + }, + { + plugins: [ + { + type: 'LinkedAppData', + dataAuthority: { + type: 'Address', + address: dataAuth.publicKey, + }, + schema: ExternalPluginAdapterSchema.Json, + }, + { + type: 'PermanentFreezeDelegate', + frozen: true, + }, + ], + } + ); + + await assertAsset( + t, + umi, + { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { + type: 'Collection', + address: collection.publicKey, + }, + edition: { + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + permanentFreezeDelegate: { + frozen: false, + authority: { + type: 'UpdateAuthority', + }, + }, + linkedAppDatas: [ + { + type: 'LinkedAppData', + authority: { + type: 'UpdateAuthority', + }, + dataAuthority: { + type: 'Address', + address: dataAuth.publicKey, + }, + schema: ExternalPluginAdapterSchema.Json, + }, + ], + }, + { + derivePlugins: true, + } + ); +}); diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index e7ef105f..af45fae9 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -103,11 +103,11 @@ pub enum MplCoreError { /// 30 (0x1E) - Invalid Log Wrapper Program #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, - /// 31 (0x1F) - External PluginExternalPluginAdapter not found - #[error("External PluginExternalPluginAdapter not found")] + /// 31 (0x1F) - External Plugin Adapter not found + #[error("External Plugin Adapter not found")] ExternalPluginAdapterNotFound, - /// 32 (0x20) - External PluginExternalPluginAdapter already exists - #[error("External PluginExternalPluginAdapter already exists")] + /// 32 (0x20) - External Plugin Adapter already exists + #[error("External Plugin Adapter already exists")] ExternalPluginAdapterAlreadyExists, /// 33 (0x21) - Missing asset needed for extra account PDA derivation #[error("Missing asset needed for extra account PDA derivation")] @@ -136,6 +136,21 @@ pub enum MplCoreError { /// 41 (0x29) - Invalid plugin operation #[error("Invalid plugin operation")] InvalidPluginOperation, + /// 42 (0x2A) - Two data sources provided, only one is allowed + #[error("Two data sources provided, only one is allowed")] + TwoDataSources, + /// 43 (0x2B) - External Plugin does not support this operation + #[error("External Plugin does not support this operation")] + UnsupportedOperation, + /// 44 (0x2C) - No data sources provided, one is required + #[error("No data sources provided, one is required")] + NoDataSources, + /// 45 (0x2D) - This plugin adapter cannot be added to an Asset + #[error("This plugin adapter cannot be added to an Asset")] + InvalidPluginAdapterTarget, + /// 46 (0x2E) - Cannot add a Data Section without a linked external plugin + #[error("Cannot add a Data Section without a linked external plugin")] + CannotAddDataSection, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs b/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs index 16bf3b74..9f4e9692 100644 --- a/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs +++ b/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs @@ -17,8 +17,10 @@ pub struct WriteCollectionExternalPluginAdapterDataV1 { pub collection: solana_program::pubkey::Pubkey, /// The account paying for the storage fees pub payer: solana_program::pubkey::Pubkey, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option, + /// The buffer to write to the external plugin + pub buffer: Option, /// The system program pub system_program: solana_program::pubkey::Pubkey, /// The SPL Noop Program @@ -38,7 +40,7 @@ impl WriteCollectionExternalPluginAdapterDataV1 { args: WriteCollectionExternalPluginAdapterDataV1InstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.collection, false, @@ -56,6 +58,16 @@ impl WriteCollectionExternalPluginAdapterDataV1 { false, )); } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + buffer, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.system_program, false, @@ -104,7 +116,7 @@ impl WriteCollectionExternalPluginAdapterDataV1InstructionData { #[derive(Clone, Debug, Eq, PartialEq)] pub struct WriteCollectionExternalPluginAdapterDataV1InstructionArgs { pub key: ExternalPluginAdapterKey, - pub data: Vec, + pub data: Option>, } /// Instruction builder for `WriteCollectionExternalPluginAdapterDataV1`. @@ -114,13 +126,15 @@ pub struct WriteCollectionExternalPluginAdapterDataV1InstructionArgs { /// 0. `[writable]` collection /// 1. `[writable, signer]` payer /// 2. `[signer, optional]` authority -/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) -/// 4. `[optional]` log_wrapper +/// 3. `[optional]` buffer +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper #[derive(Default)] pub struct WriteCollectionExternalPluginAdapterDataV1Builder { collection: Option, payer: Option, authority: Option, + buffer: Option, system_program: Option, log_wrapper: Option, key: Option, @@ -145,12 +159,19 @@ impl WriteCollectionExternalPluginAdapterDataV1Builder { self } /// `[optional account]` - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter #[inline(always)] pub fn authority(&mut self, authority: Option) -> &mut Self { self.authority = authority; self } + /// `[optional account]` + /// The buffer to write to the external plugin + #[inline(always)] + pub fn buffer(&mut self, buffer: Option) -> &mut Self { + self.buffer = buffer; + self + } /// `[optional account, default to '11111111111111111111111111111111']` /// The system program #[inline(always)] @@ -173,6 +194,7 @@ impl WriteCollectionExternalPluginAdapterDataV1Builder { self.key = Some(key); self } + /// `[optional argument]` #[inline(always)] pub fn data(&mut self, data: Vec) -> &mut Self { self.data = Some(data); @@ -202,6 +224,7 @@ impl WriteCollectionExternalPluginAdapterDataV1Builder { collection: self.collection.expect("collection is not set"), payer: self.payer.expect("payer is not set"), authority: self.authority, + buffer: self.buffer, system_program: self .system_program .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), @@ -209,7 +232,7 @@ impl WriteCollectionExternalPluginAdapterDataV1Builder { }; let args = WriteCollectionExternalPluginAdapterDataV1InstructionArgs { key: self.key.clone().expect("key is not set"), - data: self.data.clone().expect("data is not set"), + data: self.data.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -222,8 +245,10 @@ pub struct WriteCollectionExternalPluginAdapterDataV1CpiAccounts<'a, 'b> { pub collection: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The buffer to write to the external plugin + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, /// The SPL Noop Program @@ -238,8 +263,10 @@ pub struct WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { pub collection: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The buffer to write to the external plugin + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, /// The SPL Noop Program @@ -259,6 +286,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { collection: accounts.collection, payer: accounts.payer, authority: accounts.authority, + buffer: accounts.buffer, system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, __args: args, @@ -297,7 +325,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.collection.key, false, @@ -317,6 +345,17 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { false, )); } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *buffer.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.system_program.key, false, @@ -350,13 +389,16 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.collection.clone()); account_infos.push(self.payer.clone()); if let Some(authority) = self.authority { account_infos.push(authority.clone()); } + if let Some(buffer) = self.buffer { + account_infos.push(buffer.clone()); + } account_infos.push(self.system_program.clone()); if let Some(log_wrapper) = self.log_wrapper { account_infos.push(log_wrapper.clone()); @@ -380,8 +422,9 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { /// 0. `[writable]` collection /// 1. `[writable, signer]` payer /// 2. `[signer, optional]` authority -/// 3. `[]` system_program -/// 4. `[optional]` log_wrapper +/// 3. `[optional]` buffer +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper pub struct WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { instruction: Box>, } @@ -394,6 +437,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { collection: None, payer: None, authority: None, + buffer: None, system_program: None, log_wrapper: None, key: None, @@ -419,7 +463,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self } /// `[optional account]` - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter #[inline(always)] pub fn authority( &mut self, @@ -428,6 +472,16 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self.instruction.authority = authority; self } + /// `[optional account]` + /// The buffer to write to the external plugin + #[inline(always)] + pub fn buffer( + &mut self, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.buffer = buffer; + self + } /// The system program #[inline(always)] pub fn system_program( @@ -452,6 +506,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self.instruction.key = Some(key); self } + /// `[optional argument]` #[inline(always)] pub fn data(&mut self, data: Vec) -> &mut Self { self.instruction.data = Some(data); @@ -500,7 +555,7 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { ) -> solana_program::entrypoint::ProgramResult { let args = WriteCollectionExternalPluginAdapterDataV1InstructionArgs { key: self.instruction.key.clone().expect("key is not set"), - data: self.instruction.data.clone().expect("data is not set"), + data: self.instruction.data.clone(), }; let instruction = WriteCollectionExternalPluginAdapterDataV1Cpi { __program: self.instruction.__program, @@ -511,6 +566,8 @@ impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { authority: self.instruction.authority, + buffer: self.instruction.buffer, + system_program: self .instruction .system_program @@ -531,6 +588,7 @@ struct WriteCollectionExternalPluginAdapterDataV1CpiBuilderInstruction<'a, 'b> { collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, key: Option, diff --git a/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs b/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs index 0e0123e0..d5c51750 100644 --- a/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs +++ b/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs @@ -19,8 +19,10 @@ pub struct WriteExternalPluginAdapterDataV1 { pub collection: Option, /// The account paying for the storage fees pub payer: solana_program::pubkey::Pubkey, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option, + /// The buffer to write to the external plugin + pub buffer: Option, /// The system program pub system_program: solana_program::pubkey::Pubkey, /// The SPL Noop Program @@ -40,7 +42,7 @@ impl WriteExternalPluginAdapterDataV1 { args: WriteExternalPluginAdapterDataV1InstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.asset, false, )); @@ -67,6 +69,16 @@ impl WriteExternalPluginAdapterDataV1 { false, )); } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + buffer, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.system_program, false, @@ -115,7 +127,7 @@ impl WriteExternalPluginAdapterDataV1InstructionData { #[derive(Clone, Debug, Eq, PartialEq)] pub struct WriteExternalPluginAdapterDataV1InstructionArgs { pub key: ExternalPluginAdapterKey, - pub data: Vec, + pub data: Option>, } /// Instruction builder for `WriteExternalPluginAdapterDataV1`. @@ -126,14 +138,16 @@ pub struct WriteExternalPluginAdapterDataV1InstructionArgs { /// 1. `[writable, optional]` collection /// 2. `[writable, signer]` payer /// 3. `[signer, optional]` authority -/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) -/// 5. `[optional]` log_wrapper +/// 4. `[optional]` buffer +/// 5. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 6. `[optional]` log_wrapper #[derive(Default)] pub struct WriteExternalPluginAdapterDataV1Builder { asset: Option, collection: Option, payer: Option, authority: Option, + buffer: Option, system_program: Option, log_wrapper: Option, key: Option, @@ -165,12 +179,19 @@ impl WriteExternalPluginAdapterDataV1Builder { self } /// `[optional account]` - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter #[inline(always)] pub fn authority(&mut self, authority: Option) -> &mut Self { self.authority = authority; self } + /// `[optional account]` + /// The buffer to write to the external plugin + #[inline(always)] + pub fn buffer(&mut self, buffer: Option) -> &mut Self { + self.buffer = buffer; + self + } /// `[optional account, default to '11111111111111111111111111111111']` /// The system program #[inline(always)] @@ -193,6 +214,7 @@ impl WriteExternalPluginAdapterDataV1Builder { self.key = Some(key); self } + /// `[optional argument]` #[inline(always)] pub fn data(&mut self, data: Vec) -> &mut Self { self.data = Some(data); @@ -223,6 +245,7 @@ impl WriteExternalPluginAdapterDataV1Builder { collection: self.collection, payer: self.payer.expect("payer is not set"), authority: self.authority, + buffer: self.buffer, system_program: self .system_program .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), @@ -230,7 +253,7 @@ impl WriteExternalPluginAdapterDataV1Builder { }; let args = WriteExternalPluginAdapterDataV1InstructionArgs { key: self.key.clone().expect("key is not set"), - data: self.data.clone().expect("data is not set"), + data: self.data.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -245,8 +268,10 @@ pub struct WriteExternalPluginAdapterDataV1CpiAccounts<'a, 'b> { pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The buffer to write to the external plugin + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, /// The SPL Noop Program @@ -263,8 +288,10 @@ pub struct WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The buffer to write to the external plugin + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, /// The SPL Noop Program @@ -285,6 +312,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { collection: accounts.collection, payer: accounts.payer, authority: accounts.authority, + buffer: accounts.buffer, system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, __args: args, @@ -323,7 +351,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.asset.key, false, @@ -354,6 +382,17 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { false, )); } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *buffer.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.system_program.key, false, @@ -387,7 +426,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.asset.clone()); if let Some(collection) = self.collection { @@ -397,6 +436,9 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { if let Some(authority) = self.authority { account_infos.push(authority.clone()); } + if let Some(buffer) = self.buffer { + account_infos.push(buffer.clone()); + } account_infos.push(self.system_program.clone()); if let Some(log_wrapper) = self.log_wrapper { account_infos.push(log_wrapper.clone()); @@ -421,8 +463,9 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { /// 1. `[writable, optional]` collection /// 2. `[writable, signer]` payer /// 3. `[signer, optional]` authority -/// 4. `[]` system_program -/// 5. `[optional]` log_wrapper +/// 4. `[optional]` buffer +/// 5. `[]` system_program +/// 6. `[optional]` log_wrapper pub struct WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { instruction: Box>, } @@ -435,6 +478,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { collection: None, payer: None, authority: None, + buffer: None, system_program: None, log_wrapper: None, key: None, @@ -466,7 +510,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self } /// `[optional account]` - /// The Data Authority of the External PluginExternalPluginAdapter + /// The Data Authority of the External Plugin Adapter #[inline(always)] pub fn authority( &mut self, @@ -475,6 +519,16 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self.instruction.authority = authority; self } + /// `[optional account]` + /// The buffer to write to the external plugin + #[inline(always)] + pub fn buffer( + &mut self, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.buffer = buffer; + self + } /// The system program #[inline(always)] pub fn system_program( @@ -499,6 +553,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { self.instruction.key = Some(key); self } + /// `[optional argument]` #[inline(always)] pub fn data(&mut self, data: Vec) -> &mut Self { self.instruction.data = Some(data); @@ -547,7 +602,7 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { ) -> solana_program::entrypoint::ProgramResult { let args = WriteExternalPluginAdapterDataV1InstructionArgs { key: self.instruction.key.clone().expect("key is not set"), - data: self.instruction.data.clone().expect("data is not set"), + data: self.instruction.data.clone(), }; let instruction = WriteExternalPluginAdapterDataV1Cpi { __program: self.instruction.__program, @@ -560,6 +615,8 @@ impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { authority: self.instruction.authority, + buffer: self.instruction.buffer, + system_program: self .instruction .system_program @@ -581,6 +638,7 @@ struct WriteExternalPluginAdapterDataV1CpiBuilderInstruction<'a, 'b> { collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, key: Option, diff --git a/clients/rust/src/generated/types/data_store.rs b/clients/rust/src/generated/types/app_data.rs similarity index 97% rename from clients/rust/src/generated/types/data_store.rs rename to clients/rust/src/generated/types/app_data.rs index 7d7f5577..1c6c7603 100644 --- a/clients/rust/src/generated/types/data_store.rs +++ b/clients/rust/src/generated/types/app_data.rs @@ -16,7 +16,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct DataStore { +pub struct AppData { pub data_authority: PluginAuthority, pub schema: ExternalPluginAdapterSchema, } diff --git a/clients/rust/src/generated/types/data_store_init_info.rs b/clients/rust/src/generated/types/app_data_init_info.rs similarity index 96% rename from clients/rust/src/generated/types/data_store_init_info.rs rename to clients/rust/src/generated/types/app_data_init_info.rs index bdcf7213..408f13b7 100644 --- a/clients/rust/src/generated/types/data_store_init_info.rs +++ b/clients/rust/src/generated/types/app_data_init_info.rs @@ -16,7 +16,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct DataStoreInitInfo { +pub struct AppDataInitInfo { pub data_authority: PluginAuthority, pub init_plugin_authority: Option, pub schema: Option, diff --git a/clients/rust/src/generated/types/data_store_update_info.rs b/clients/rust/src/generated/types/app_data_update_info.rs similarity index 95% rename from clients/rust/src/generated/types/data_store_update_info.rs rename to clients/rust/src/generated/types/app_data_update_info.rs index c9fe1584..2cc4cf6f 100644 --- a/clients/rust/src/generated/types/data_store_update_info.rs +++ b/clients/rust/src/generated/types/app_data_update_info.rs @@ -15,6 +15,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct DataStoreUpdateInfo { +pub struct AppDataUpdateInfo { pub schema: Option, } diff --git a/clients/rust/src/generated/types/data_section.rs b/clients/rust/src/generated/types/data_section.rs new file mode 100644 index 00000000..ac1090bf --- /dev/null +++ b/clients/rust/src/generated/types/data_section.rs @@ -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. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::LinkedDataKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataSection { + pub parent_key: LinkedDataKey, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/data_section_init_info.rs b/clients/rust/src/generated/types/data_section_init_info.rs new file mode 100644 index 00000000..bdca4113 --- /dev/null +++ b/clients/rust/src/generated/types/data_section_init_info.rs @@ -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. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::LinkedDataKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataSectionInitInfo { + pub parent_key: LinkedDataKey, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/data_section_update_info.rs b/clients/rust/src/generated/types/data_section_update_info.rs new file mode 100644 index 00000000..06b84f15 --- /dev/null +++ b/clients/rust/src/generated/types/data_section_update_info.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataSectionUpdateInfo {} diff --git a/clients/rust/src/generated/types/external_plugin_adapter.rs b/clients/rust/src/generated/types/external_plugin_adapter.rs index 00e02d0a..d691df27 100644 --- a/clients/rust/src/generated/types/external_plugin_adapter.rs +++ b/clients/rust/src/generated/types/external_plugin_adapter.rs @@ -5,8 +5,11 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::DataStore; +use crate::generated::types::AppData; +use crate::generated::types::DataSection; use crate::generated::types::LifecycleHook; +use crate::generated::types::LinkedAppData; +use crate::generated::types::LinkedLifecycleHook; use crate::generated::types::Oracle; #[cfg(feature = "anchor")] use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; @@ -20,5 +23,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub enum ExternalPluginAdapter { LifecycleHook(LifecycleHook), Oracle(Oracle), - DataStore(DataStore), + AppData(AppData), + LinkedLifecycleHook(LinkedLifecycleHook), + LinkedAppData(LinkedAppData), + DataSection(DataSection), } diff --git a/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs b/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs index 2168a069..5992c9f7 100644 --- a/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs +++ b/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs @@ -5,8 +5,11 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::DataStoreInitInfo; +use crate::generated::types::AppDataInitInfo; +use crate::generated::types::DataSectionInitInfo; use crate::generated::types::LifecycleHookInitInfo; +use crate::generated::types::LinkedAppDataInitInfo; +use crate::generated::types::LinkedLifecycleHookInitInfo; use crate::generated::types::OracleInitInfo; #[cfg(feature = "anchor")] use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; @@ -20,5 +23,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub enum ExternalPluginAdapterInitInfo { LifecycleHook(LifecycleHookInitInfo), Oracle(OracleInitInfo), - DataStore(DataStoreInitInfo), + AppData(AppDataInitInfo), + LinkedLifecycleHook(LinkedLifecycleHookInitInfo), + LinkedAppData(LinkedAppDataInitInfo), + DataSection(DataSectionInitInfo), } diff --git a/clients/rust/src/generated/types/external_plugin_adapter_key.rs b/clients/rust/src/generated/types/external_plugin_adapter_key.rs index b8d3b68d..6defc9f7 100644 --- a/clients/rust/src/generated/types/external_plugin_adapter_key.rs +++ b/clients/rust/src/generated/types/external_plugin_adapter_key.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::LinkedDataKey; use crate::generated::types::PluginAuthority; #[cfg(feature = "anchor")] use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; @@ -27,5 +28,12 @@ pub enum ExternalPluginAdapterKey { serde(with = "serde_with::As::") )] Oracle(Pubkey), - DataStore(PluginAuthority), + AppData(PluginAuthority), + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + LinkedLifecycleHook(Pubkey), + LinkedAppData(PluginAuthority), + DataSection(LinkedDataKey), } diff --git a/clients/rust/src/generated/types/external_plugin_adapter_type.rs b/clients/rust/src/generated/types/external_plugin_adapter_type.rs index 0a7c025e..4a3e299d 100644 --- a/clients/rust/src/generated/types/external_plugin_adapter_type.rs +++ b/clients/rust/src/generated/types/external_plugin_adapter_type.rs @@ -18,5 +18,8 @@ use num_derive::FromPrimitive; pub enum ExternalPluginAdapterType { LifecycleHook, Oracle, - DataStore, + AppData, + LinkedLifecycleHook, + LinkedAppData, + DataSection, } diff --git a/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs b/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs index cd709990..3b4840e2 100644 --- a/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs +++ b/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs @@ -5,8 +5,10 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::DataStoreUpdateInfo; +use crate::generated::types::AppDataUpdateInfo; use crate::generated::types::LifecycleHookUpdateInfo; +use crate::generated::types::LinkedAppDataUpdateInfo; +use crate::generated::types::LinkedLifecycleHookUpdateInfo; use crate::generated::types::OracleUpdateInfo; #[cfg(feature = "anchor")] use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; @@ -20,5 +22,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub enum ExternalPluginAdapterUpdateInfo { LifecycleHook(LifecycleHookUpdateInfo), Oracle(OracleUpdateInfo), - DataStore(DataStoreUpdateInfo), + AppData(AppDataUpdateInfo), + LinkedLifecycleHook(LinkedLifecycleHookUpdateInfo), + LinkedAppData(LinkedAppDataUpdateInfo), } diff --git a/clients/rust/src/generated/types/linked_app_data.rs b/clients/rust/src/generated/types/linked_app_data.rs new file mode 100644 index 00000000..71ba8094 --- /dev/null +++ b/clients/rust/src/generated/types/linked_app_data.rs @@ -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. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedAppData { + pub data_authority: PluginAuthority, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/linked_app_data_init_info.rs b/clients/rust/src/generated/types/linked_app_data_init_info.rs new file mode 100644 index 00000000..2ecbbacd --- /dev/null +++ b/clients/rust/src/generated/types/linked_app_data_init_info.rs @@ -0,0 +1,23 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedAppDataInitInfo { + pub data_authority: PluginAuthority, + pub init_plugin_authority: Option, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/linked_app_data_update_info.rs b/clients/rust/src/generated/types/linked_app_data_update_info.rs new file mode 100644 index 00000000..9eabc134 --- /dev/null +++ b/clients/rust/src/generated/types/linked_app_data_update_info.rs @@ -0,0 +1,20 @@ +//! 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::ExternalPluginAdapterSchema; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedAppDataUpdateInfo { + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/linked_data_key.rs b/clients/rust/src/generated/types/linked_data_key.rs new file mode 100644 index 00000000..3dca4754 --- /dev/null +++ b/clients/rust/src/generated/types/linked_data_key.rs @@ -0,0 +1,26 @@ +//! 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::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum LinkedDataKey { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + LinkedLifecycleHook(Pubkey), + LinkedAppData(PluginAuthority), +} diff --git a/clients/rust/src/generated/types/linked_lifecycle_hook.rs b/clients/rust/src/generated/types/linked_lifecycle_hook.rs new file mode 100644 index 00000000..8359ec49 --- /dev/null +++ b/clients/rust/src/generated/types/linked_lifecycle_hook.rs @@ -0,0 +1,30 @@ +//! 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::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedLifecycleHook { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub hooked_program: Pubkey, + pub extra_accounts: Option>, + pub data_authority: Option, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/linked_lifecycle_hook_init_info.rs b/clients/rust/src/generated/types/linked_lifecycle_hook_init_info.rs new file mode 100644 index 00000000..e2952707 --- /dev/null +++ b/clients/rust/src/generated/types/linked_lifecycle_hook_init_info.rs @@ -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. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedLifecycleHookInitInfo { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub hooked_program: Pubkey, + pub init_plugin_authority: Option, + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + pub extra_accounts: Option>, + pub data_authority: Option, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/linked_lifecycle_hook_update_info.rs b/clients/rust/src/generated/types/linked_lifecycle_hook_update_info.rs new file mode 100644 index 00000000..ec583ba4 --- /dev/null +++ b/clients/rust/src/generated/types/linked_lifecycle_hook_update_info.rs @@ -0,0 +1,25 @@ +//! 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::ExternalCheckResult; +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LinkedLifecycleHookUpdateInfo { + pub lifecycle_checks: Option>, + pub extra_accounts: Option>, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index 1a02a50a..755acf21 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -6,6 +6,9 @@ //! pub(crate) mod r#add_blocker; +pub(crate) mod r#app_data; +pub(crate) mod r#app_data_init_info; +pub(crate) mod r#app_data_update_info; pub(crate) mod r#attribute; pub(crate) mod r#attributes; pub(crate) mod r#autograph; @@ -13,10 +16,10 @@ pub(crate) mod r#autograph_signature; pub(crate) mod r#burn_delegate; pub(crate) mod r#compression_proof; pub(crate) mod r#creator; +pub(crate) mod r#data_section; +pub(crate) mod r#data_section_init_info; +pub(crate) mod r#data_section_update_info; pub(crate) mod r#data_state; -pub(crate) mod r#data_store; -pub(crate) mod r#data_store_init_info; -pub(crate) mod r#data_store_update_info; pub(crate) mod r#edition; pub(crate) mod r#external_check_result; pub(crate) mod r#external_plugin_adapter; @@ -37,6 +40,13 @@ pub(crate) mod r#key; pub(crate) mod r#lifecycle_hook; pub(crate) mod r#lifecycle_hook_init_info; pub(crate) mod r#lifecycle_hook_update_info; +pub(crate) mod r#linked_app_data; +pub(crate) mod r#linked_app_data_init_info; +pub(crate) mod r#linked_app_data_update_info; +pub(crate) mod r#linked_data_key; +pub(crate) mod r#linked_lifecycle_hook; +pub(crate) mod r#linked_lifecycle_hook_init_info; +pub(crate) mod r#linked_lifecycle_hook_update_info; pub(crate) mod r#master_edition; pub(crate) mod r#oracle; pub(crate) mod r#oracle_init_info; @@ -62,6 +72,9 @@ pub(crate) mod r#verified_creators; pub(crate) mod r#verified_creators_signature; pub use self::r#add_blocker::*; +pub use self::r#app_data::*; +pub use self::r#app_data_init_info::*; +pub use self::r#app_data_update_info::*; pub use self::r#attribute::*; pub use self::r#attributes::*; pub use self::r#autograph::*; @@ -69,10 +82,10 @@ pub use self::r#autograph_signature::*; pub use self::r#burn_delegate::*; pub use self::r#compression_proof::*; pub use self::r#creator::*; +pub use self::r#data_section::*; +pub use self::r#data_section_init_info::*; +pub use self::r#data_section_update_info::*; pub use self::r#data_state::*; -pub use self::r#data_store::*; -pub use self::r#data_store_init_info::*; -pub use self::r#data_store_update_info::*; pub use self::r#edition::*; pub use self::r#external_check_result::*; pub use self::r#external_plugin_adapter::*; @@ -93,6 +106,13 @@ pub use self::r#key::*; pub use self::r#lifecycle_hook::*; pub use self::r#lifecycle_hook_init_info::*; pub use self::r#lifecycle_hook_update_info::*; +pub use self::r#linked_app_data::*; +pub use self::r#linked_app_data_init_info::*; +pub use self::r#linked_app_data_update_info::*; +pub use self::r#linked_data_key::*; +pub use self::r#linked_lifecycle_hook::*; +pub use self::r#linked_lifecycle_hook_init_info::*; +pub use self::r#linked_lifecycle_hook_update_info::*; pub use self::r#master_edition::*; pub use self::r#oracle::*; pub use self::r#oracle_init_info::*; diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 081d35f0..7581ebf6 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -8,11 +8,11 @@ use std::{cmp::Ordering, io::ErrorKind}; use crate::{ accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1}, types::{ - AddBlocker, Attributes, Autograph, BurnDelegate, DataStore, Edition, ExternalCheckResult, - ExternalPluginAdapter, ExternalPluginAdapterKey, FreezeDelegate, ImmutableMetadata, Key, - LifecycleHook, MasterEdition, Oracle, PermanentBurnDelegate, PermanentFreezeDelegate, - PermanentTransferDelegate, PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, - VerifiedCreators, + AddBlocker, AppData, Attributes, Autograph, BurnDelegate, DataSection, Edition, + ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterKey, FreezeDelegate, + ImmutableMetadata, Key, LifecycleHook, LinkedAppData, LinkedLifecycleHook, MasterEdition, + Oracle, PermanentBurnDelegate, PermanentFreezeDelegate, PermanentTransferDelegate, + PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, VerifiedCreators, }, }; @@ -182,8 +182,11 @@ pub struct PluginsList { #[derive(Debug, Default)] pub struct ExternalPluginAdaptersList { pub lifecycle_hooks: Vec, + pub linked_lifecycle_hooks: Vec, pub oracles: Vec, - pub data_stores: Vec, + pub app_data: Vec, + pub linked_app_data: Vec, + pub data_sections: Vec, } #[derive(Debug)] @@ -303,15 +306,22 @@ impl PluginRegistryV1Safe { impl From<&ExternalPluginAdapter> for ExternalPluginAdapterKey { fn from(plugin: &ExternalPluginAdapter) -> Self { match plugin { - ExternalPluginAdapter::DataStore(data_store) => { - ExternalPluginAdapterKey::DataStore(data_store.data_authority.clone()) + ExternalPluginAdapter::LinkedAppData(app_data) => { + ExternalPluginAdapterKey::AppData(app_data.data_authority.clone()) + } + ExternalPluginAdapter::AppData(app_data) => { + ExternalPluginAdapterKey::AppData(app_data.data_authority.clone()) } ExternalPluginAdapter::Oracle(oracle) => { ExternalPluginAdapterKey::Oracle(oracle.base_address) } + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + ExternalPluginAdapterKey::LifecycleHook(lifecycle_hook.hooked_program) + } ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { ExternalPluginAdapterKey::LifecycleHook(lifecycle_hook.hooked_program) } + ExternalPluginAdapter::DataSection(_) => todo!(), } } } diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 63b6f1d4..f1d3f5a3 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -242,9 +242,16 @@ pub(crate) fn registry_records_to_external_plugin_adapter_list( ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { acc.lifecycle_hooks.push(lifecycle_hook) } + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + acc.linked_lifecycle_hooks.push(lifecycle_hook) + } ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle), - ExternalPluginAdapter::DataStore(data_store) => { - acc.data_stores.push(data_store) + ExternalPluginAdapter::AppData(app_data) => acc.app_data.push(app_data), + ExternalPluginAdapter::LinkedAppData(app_data) => { + acc.linked_app_data.push(app_data) + } + ExternalPluginAdapter::DataSection(data_section) => { + acc.data_sections.push(data_section) } } } diff --git a/clients/rust/src/indexable_asset.rs b/clients/rust/src/indexable_asset.rs index 7ce31887..4c1efeaa 100644 --- a/clients/rust/src/indexable_asset.rs +++ b/clients/rust/src/indexable_asset.rs @@ -230,7 +230,7 @@ impl ProcessedExternalPlugin { ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { &lifecycle_hook.schema } - ExternalPluginAdapter::DataStore(data_store) => &data_store.schema, + ExternalPluginAdapter::AppData(app_data) => &app_data.schema, _ => &ExternalPluginAdapterSchema::Binary, // is this possible }; diff --git a/clients/rust/tests/add_external_plugins.rs b/clients/rust/tests/add_external_plugins.rs index f08935dc..747decbf 100644 --- a/clients/rust/tests/add_external_plugins.rs +++ b/clients/rust/tests/add_external_plugins.rs @@ -6,7 +6,7 @@ use mpl_core::{ AddCollectionExternalPluginAdapterV1Builder, AddExternalPluginAdapterV1Builder, }, types::{ - DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + AppData, AppDataInitInfo, ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, @@ -535,8 +535,7 @@ async fn test_cannot_add_oracle_with_duplicate_lifecycle_checks() { } #[tokio::test] -#[ignore] -async fn test_add_data_store() { +async fn test_add_app_data() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -578,13 +577,11 @@ async fn test_add_data_store() { let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() .asset(asset.pubkey()) .payer(context.payer.pubkey()) - .init_info(ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { - init_plugin_authority: Some(PluginAuthority::UpdateAuthority), - data_authority: PluginAuthority::UpdateAuthority, - schema: None, - }, - )) + .init_info(ExternalPluginAdapterInitInfo::AppData(AppDataInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + })) .instruction(); let tx = Transaction::new_signed_with_payer( @@ -605,7 +602,7 @@ async fn test_add_data_store() { name: None, uri: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { data_authority: PluginAuthority::UpdateAuthority, schema: ExternalPluginAdapterSchema::Binary, })], @@ -614,153 +611,6 @@ async fn test_add_data_store() { .await; } -#[tokio::test] -async fn test_temporarily_cannot_add_data_store() { - let mut context = program_test().start_with_context().await; - - let asset = Keypair::new(); - create_asset( - &mut context, - CreateAssetHelperArgs { - owner: None, - payer: None, - asset: &asset, - data_state: None, - name: None, - uri: None, - authority: None, - update_authority: None, - collection: None, - plugins: vec![], - external_plugin_adapters: vec![], - }, - ) - .await - .unwrap(); - - let owner = context.payer.pubkey(); - let update_authority = context.payer.pubkey(); - assert_asset( - &mut context, - AssertAssetHelperArgs { - asset: asset.pubkey(), - owner, - update_authority: Some(UpdateAuthority::Address(update_authority)), - name: None, - uri: None, - plugins: vec![], - external_plugin_adapters: vec![], - }, - ) - .await; - - let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() - .asset(asset.pubkey()) - .payer(context.payer.pubkey()) - .init_info(ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { - init_plugin_authority: Some(PluginAuthority::UpdateAuthority), - data_authority: PluginAuthority::UpdateAuthority, - schema: None, - }, - )) - .instruction(); - - let tx = Transaction::new_signed_with_payer( - &[add_external_plugin_adapter_ix], - Some(&context.payer.pubkey()), - &[&context.payer], - context.last_blockhash, - ); - - let error = context - .banks_client - .process_transaction(tx) - .await - .unwrap_err(); - - assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); - - assert_asset( - &mut context, - AssertAssetHelperArgs { - asset: asset.pubkey(), - owner, - update_authority: Some(UpdateAuthority::Address(update_authority)), - name: None, - uri: None, - plugins: vec![], - external_plugin_adapters: vec![], - }, - ) - .await; -} - -#[tokio::test] -async fn test_temporarily_cannot_add_data_store_on_collection() { - let mut context = program_test().start_with_context().await; - - let collection = Keypair::new(); - create_collection( - &mut context, - CreateCollectionHelperArgs { - collection: &collection, - update_authority: None, - payer: None, - name: None, - uri: None, - plugins: vec![], - external_plugin_adapters: vec![], - }, - ) - .await - .unwrap(); - - let update_authority = context.payer.pubkey(); - assert_collection( - &mut context, - AssertCollectionHelperArgs { - collection: collection.pubkey(), - update_authority, - name: None, - uri: None, - num_minted: 0, - current_size: 0, - plugins: vec![], - }, - ) - .await; - - let add_external_plugin_adapter_ix = AddCollectionExternalPluginAdapterV1Builder::new() - .collection(collection.pubkey()) - .payer(context.payer.pubkey()) - .init_info(ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { - init_plugin_authority: Some(PluginAuthority::UpdateAuthority), - data_authority: PluginAuthority::UpdateAuthority, - schema: None, - }, - )) - .instruction(); - - let tx = Transaction::new_signed_with_payer( - &[add_external_plugin_adapter_ix], - Some(&context.payer.pubkey()), - &[&context.payer], - context.last_blockhash, - ); - - let error = context - .banks_client - .process_transaction(tx) - .await - .unwrap_err(); - - assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); - - // TODO: add collection assert. -} - #[tokio::test] async fn test_cannot_add_duplicate_external_plugin_adapter() { let mut context = program_test().start_with_context().await; diff --git a/clients/rust/tests/create_with_external_plugins.rs b/clients/rust/tests/create_with_external_plugins.rs index 411e48ee..cd410519 100644 --- a/clients/rust/tests/create_with_external_plugins.rs +++ b/clients/rust/tests/create_with_external_plugins.rs @@ -3,7 +3,7 @@ pub mod setup; use mpl_core::{ errors::MplCoreError, types::{ - DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + AppData, AppDataInitInfo, ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, @@ -291,8 +291,7 @@ async fn test_cannot_create_oracle_with_duplicate_lifecycle_checks() { } #[tokio::test] -#[ignore] -async fn test_create_data_store() { +async fn test_create_app_data() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -309,8 +308,8 @@ async fn test_create_data_store() { update_authority: None, collection: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::AppData( + AppDataInitInfo { init_plugin_authority: Some(PluginAuthority::UpdateAuthority), data_authority: PluginAuthority::UpdateAuthority, schema: None, @@ -332,7 +331,7 @@ async fn test_create_data_store() { name: None, uri: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { data_authority: PluginAuthority::UpdateAuthority, schema: ExternalPluginAdapterSchema::Binary, })], @@ -340,65 +339,3 @@ async fn test_create_data_store() { ) .await; } - -#[tokio::test] -async fn test_temporarily_cannot_create_data_store() { - let mut context = program_test().start_with_context().await; - - let asset = Keypair::new(); - let error = create_asset( - &mut context, - CreateAssetHelperArgs { - owner: None, - payer: None, - asset: &asset, - data_state: None, - name: None, - uri: None, - authority: None, - update_authority: None, - collection: None, - plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { - init_plugin_authority: Some(PluginAuthority::UpdateAuthority), - data_authority: PluginAuthority::UpdateAuthority, - schema: None, - }, - )], - }, - ) - .await - .unwrap_err(); - - assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); -} - -#[tokio::test] -async fn test_temporarily_cannot_create_data_store_on_collection() { - let mut context = program_test().start_with_context().await; - - let collection = Keypair::new(); - let error = create_collection( - &mut context, - CreateCollectionHelperArgs { - collection: &collection, - update_authority: None, - payer: None, - name: None, - uri: None, - plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { - init_plugin_authority: Some(PluginAuthority::UpdateAuthority), - data_authority: PluginAuthority::UpdateAuthority, - schema: None, - }, - )], - }, - ) - .await - .unwrap_err(); - - assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); -} diff --git a/clients/rust/tests/remove_external_plugins.rs b/clients/rust/tests/remove_external_plugins.rs index 6e1a99ca..17e4634c 100644 --- a/clients/rust/tests/remove_external_plugins.rs +++ b/clients/rust/tests/remove_external_plugins.rs @@ -3,7 +3,7 @@ pub mod setup; use mpl_core::{ instructions::RemoveExternalPluginAdapterV1Builder, types::{ - DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + AppData, AppDataInitInfo, ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, ExternalPluginAdapterSchema, HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, @@ -189,8 +189,7 @@ async fn test_remove_oracle() { } #[tokio::test] -#[ignore] -async fn test_remove_data_store() { +async fn test_remove_app_data() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -207,8 +206,8 @@ async fn test_remove_data_store() { update_authority: None, collection: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::AppData( + AppDataInitInfo { init_plugin_authority: Some(PluginAuthority::UpdateAuthority), data_authority: PluginAuthority::UpdateAuthority, schema: None, @@ -230,7 +229,7 @@ async fn test_remove_data_store() { name: None, uri: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { data_authority: PluginAuthority::UpdateAuthority, schema: ExternalPluginAdapterSchema::Binary, })], @@ -241,7 +240,7 @@ async fn test_remove_data_store() { let ix = RemoveExternalPluginAdapterV1Builder::new() .asset(asset.pubkey()) .payer(context.payer.pubkey()) - .key(ExternalPluginAdapterKey::DataStore( + .key(ExternalPluginAdapterKey::AppData( PluginAuthority::UpdateAuthority, )) .instruction(); diff --git a/clients/rust/tests/setup/mod.rs b/clients/rust/tests/setup/mod.rs index 6a7c402e..d25fe63b 100644 --- a/clients/rust/tests/setup/mod.rs +++ b/clients/rust/tests/setup/mod.rs @@ -136,7 +136,7 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe input.external_plugin_adapters.len(), asset.external_plugin_adapter_list.lifecycle_hooks.len() + asset.external_plugin_adapter_list.oracles.len() - + asset.external_plugin_adapter_list.data_stores.len() + + asset.external_plugin_adapter_list.app_data.len() ); for plugin in input.external_plugin_adapters { match plugin { @@ -149,11 +149,29 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe ExternalPluginAdapter::Oracle(oracle) => { assert!(asset.external_plugin_adapter_list.oracles.contains(&oracle)) } - ExternalPluginAdapter::DataStore(data_store) => { + ExternalPluginAdapter::AppData(app_data) => { assert!(asset .external_plugin_adapter_list - .data_stores - .contains(&data_store)) + .app_data + .contains(&app_data)) + } + ExternalPluginAdapter::LinkedLifecycleHook(hook) => { + assert!(asset + .external_plugin_adapter_list + .linked_lifecycle_hooks + .contains(&hook)) + } + ExternalPluginAdapter::LinkedAppData(app_data) => { + assert!(asset + .external_plugin_adapter_list + .linked_app_data + .contains(&app_data)) + } + ExternalPluginAdapter::DataSection(data) => { + assert!(asset + .external_plugin_adapter_list + .data_sections + .contains(&data)) } } } diff --git a/clients/rust/tests/update_external_plugins.rs b/clients/rust/tests/update_external_plugins.rs index 22972f9e..d4397509 100644 --- a/clients/rust/tests/update_external_plugins.rs +++ b/clients/rust/tests/update_external_plugins.rs @@ -4,11 +4,11 @@ use mpl_core::{ errors::MplCoreError, instructions::UpdateExternalPluginAdapterV1Builder, types::{ - DataStore, DataStoreInitInfo, DataStoreUpdateInfo, ExternalCheckResult, - ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, - ExternalPluginAdapterSchema, ExternalPluginAdapterUpdateInfo, HookableLifecycleEvent, - LifecycleHook, LifecycleHookInitInfo, LifecycleHookUpdateInfo, Oracle, OracleInitInfo, - OracleUpdateInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, + AppData, AppDataInitInfo, AppDataUpdateInfo, ExternalCheckResult, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, ExternalPluginAdapterSchema, + ExternalPluginAdapterUpdateInfo, HookableLifecycleEvent, LifecycleHook, + LifecycleHookInitInfo, LifecycleHookUpdateInfo, Oracle, OracleInitInfo, OracleUpdateInfo, + PluginAuthority, UpdateAuthority, ValidationResultsOffset, }, }; pub use setup::*; @@ -403,8 +403,7 @@ async fn test_cannot_update_oracle_to_have_duplicate_lifecycle_checks() { } #[tokio::test] -#[ignore] -async fn test_update_data_store() { +async fn test_update_app_data() { let mut context = program_test().start_with_context().await; let asset = Keypair::new(); @@ -421,8 +420,8 @@ async fn test_update_data_store() { update_authority: None, collection: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( - DataStoreInitInfo { + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::AppData( + AppDataInitInfo { init_plugin_authority: Some(PluginAuthority::UpdateAuthority), data_authority: PluginAuthority::UpdateAuthority, schema: None, @@ -444,7 +443,7 @@ async fn test_update_data_store() { name: None, uri: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { data_authority: PluginAuthority::UpdateAuthority, schema: ExternalPluginAdapterSchema::Binary, })], @@ -455,11 +454,11 @@ async fn test_update_data_store() { let ix = UpdateExternalPluginAdapterV1Builder::new() .asset(asset.pubkey()) .payer(context.payer.pubkey()) - .key(ExternalPluginAdapterKey::DataStore( + .key(ExternalPluginAdapterKey::AppData( PluginAuthority::UpdateAuthority, )) - .update_info(ExternalPluginAdapterUpdateInfo::DataStore( - DataStoreUpdateInfo { + .update_info(ExternalPluginAdapterUpdateInfo::AppData( + AppDataUpdateInfo { schema: Some(ExternalPluginAdapterSchema::Json), }, )) @@ -483,7 +482,7 @@ async fn test_update_data_store() { name: None, uri: None, plugins: vec![], - external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + external_plugin_adapters: vec![ExternalPluginAdapter::AppData(AppData { data_authority: PluginAuthority::UpdateAuthority, schema: ExternalPluginAdapterSchema::Json, })], diff --git a/configs/kinobi.cjs b/configs/kinobi.cjs index c6aaf750..63684cf5 100755 --- a/configs/kinobi.cjs +++ b/configs/kinobi.cjs @@ -198,6 +198,9 @@ kinobi.update( externalPluginAdapterKey: { name: "baseExternalPluginAdapterKey" }, + linkedDataKey: { + name: 'baseLinkedDataKey' + }, externalPluginAdapterInitInfo: { name: "baseExternalPluginAdapterInitInfo" }, @@ -222,14 +225,41 @@ kinobi.update( lifecycleHookUpdateInfo: { name: "baseLifecycleHookUpdateInfo" }, - dataStore: { - name: "baseDataStore" + linkedLifecycleHook: { + name: "baseLinkedLifecycleHook" + }, + linkedLifecycleHookInitInfo: { + name: "baseLinkedLifecycleHookInitInfo" + }, + linkedLifecycleHookUpdateInfo: { + name: "baseLinkedLifecycleHookUpdateInfo" + }, + appData: { + name: "baseAppData" + }, + appDataInitInfo: { + name: "baseAppDataInitInfo" + }, + appDataUpdateInfo: { + name: "baseAppDataUpdateInfo" + }, + linkedAppData: { + name: "baseLinkedAppData" + }, + linkedAppDataInitInfo: { + name: "baseLinkedAppDataInitInfo" + }, + linkedAppDataUpdateInfo: { + name: "baseLinkedAppDataUpdateInfo" + }, + dataSection: { + name: "baseDataSection" }, - dataStoreInitInfo: { - name: "baseDataStoreInitInfo" + dataSectionInitInfo: { + name: "baseDataSectionInitInfo" }, - dataStoreUpdateInfo: { - name: "baseDataStoreUpdateInfo" + dataSectionUpdateInfo: { + name: "baseDataSectionUpdateInfo" }, validationResultsOffset: { name: "baseValidationResultsOffset" diff --git a/idls/mpl_core.json b/idls/mpl_core.json index acc00a06..b4c34756 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -1819,7 +1819,16 @@ "isSigner": true, "isOptional": true, "docs": [ - "The Data Authority of the External PluginExternalPluginAdapter" + "The Data Authority of the External Plugin Adapter" + ] + }, + { + "name": "buffer", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The buffer to write to the external plugin" ] }, { @@ -1878,7 +1887,16 @@ "isSigner": true, "isOptional": true, "docs": [ - "The Data Authority of the External PluginExternalPluginAdapter" + "The Data Authority of the External Plugin Adapter" + ] + }, + { + "name": "buffer", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The buffer to write to the external plugin" ] }, { @@ -2088,6 +2106,72 @@ "fields": [] } }, + { + "name": "AppData", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } + } + ] + } + }, + { + "name": "AppDataInitInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, + { + "name": "AppDataUpdateInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, { "name": "Attribute", "type": { @@ -2160,14 +2244,14 @@ } }, { - "name": "DataStore", + "name": "DataSection", "type": { "kind": "struct", "fields": [ { - "name": "dataAuthority", + "name": "parentKey", "type": { - "defined": "Authority" + "defined": "LinkedDataKey" } }, { @@ -2180,40 +2264,161 @@ } }, { - "name": "DataStoreInitInfo", + "name": "DataSectionInitInfo", "type": { "kind": "struct", "fields": [ { - "name": "dataAuthority", + "name": "parentKey", "type": { - "defined": "Authority" + "defined": "LinkedDataKey" } }, { - "name": "initPluginAuthority", + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } + } + ] + } + }, + { + "name": "DataSectionUpdateInfo", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "Edition", + "type": { + "kind": "struct", + "fields": [ + { + "name": "number", + "type": "u32" + } + ] + } + }, + { + "name": "FreezeDelegate", + "type": { + "kind": "struct", + "fields": [ + { + "name": "frozen", + "type": "bool" + } + ] + } + }, + { + "name": "ImmutableMetadata", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "ExternalCheckResult", + "type": { + "kind": "struct", + "fields": [ + { + "name": "flags", + "type": "u32" + } + ] + } + }, + { + "name": "LifecycleHook", + "type": { + "kind": "struct", + "fields": [ + { + "name": "hookedProgram", + "type": "publicKey" + }, + { + "name": "extraAccounts", "type": { "option": { - "defined": "Authority" + "vec": { + "defined": "ExtraAccount" + } } } }, { - "name": "schema", + "name": "dataAuthority", "type": { "option": { - "defined": "ExternalPluginAdapterSchema" + "defined": "Authority" } } + }, + { + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } } ] } }, { - "name": "DataStoreUpdateInfo", + "name": "LifecycleHookInitInfo", "type": { "kind": "struct", "fields": [ + { + "name": "hookedProgram", + "type": "publicKey" + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "lifecycleChecks", + "type": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + }, + { + "name": "extraAccounts", + "type": { + "option": { + "vec": { + "defined": "ExtraAccount" + } + } + } + }, + { + "name": "dataAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, { "name": "schema", "type": { @@ -2226,50 +2431,116 @@ } }, { - "name": "Edition", + "name": "LifecycleHookUpdateInfo", "type": { "kind": "struct", "fields": [ { - "name": "number", - "type": "u32" + "name": "lifecycleChecks", + "type": { + "option": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + } + }, + { + "name": "extraAccounts", + "type": { + "option": { + "vec": { + "defined": "ExtraAccount" + } + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } } ] } }, { - "name": "FreezeDelegate", + "name": "LinkedAppData", "type": { "kind": "struct", "fields": [ { - "name": "frozen", - "type": "bool" + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } } ] } }, { - "name": "ImmutableMetadata", + "name": "LinkedAppDataInitInfo", "type": { "kind": "struct", - "fields": [] + "fields": [ + { + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] } }, { - "name": "ExternalCheckResult", + "name": "LinkedAppDataUpdateInfo", "type": { "kind": "struct", "fields": [ { - "name": "flags", - "type": "u32" + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } } ] } }, { - "name": "LifecycleHook", + "name": "LinkedLifecycleHook", "type": { "kind": "struct", "fields": [ @@ -2305,7 +2576,7 @@ } }, { - "name": "LifecycleHookInitInfo", + "name": "LinkedLifecycleHookInitInfo", "type": { "kind": "struct", "fields": [ @@ -2366,7 +2637,7 @@ } }, { - "name": "LifecycleHookUpdateInfo", + "name": "LinkedLifecycleHookUpdateInfo", "type": { "kind": "struct", "fields": [ @@ -3280,7 +3551,9 @@ }, { "name": "data", - "type": "bytes" + "type": { + "option": "bytes" + } } ] } @@ -3298,7 +3571,9 @@ }, { "name": "data", - "type": "bytes" + "type": { + "option": "bytes" + } } ] } @@ -3586,7 +3861,16 @@ "name": "Oracle" }, { - "name": "DataStore" + "name": "AppData" + }, + { + "name": "LinkedLifecycleHook" + }, + { + "name": "LinkedAppData" + }, + { + "name": "DataSection" } ] } @@ -3613,10 +3897,34 @@ ] }, { - "name": "DataStore", + "name": "AppData", "fields": [ { - "defined": "DataStore" + "defined": "AppData" + } + ] + }, + { + "name": "LinkedLifecycleHook", + "fields": [ + { + "defined": "LinkedLifecycleHook" + } + ] + }, + { + "name": "LinkedAppData", + "fields": [ + { + "defined": "LinkedAppData" + } + ] + }, + { + "name": "DataSection", + "fields": [ + { + "defined": "DataSection" } ] } @@ -3831,10 +4139,34 @@ ] }, { - "name": "DataStore", + "name": "AppData", + "fields": [ + { + "defined": "AppDataInitInfo" + } + ] + }, + { + "name": "LinkedLifecycleHook", "fields": [ { - "defined": "DataStoreInitInfo" + "defined": "LinkedLifecycleHookInitInfo" + } + ] + }, + { + "name": "LinkedAppData", + "fields": [ + { + "defined": "LinkedAppDataInitInfo" + } + ] + }, + { + "name": "DataSection", + "fields": [ + { + "defined": "DataSectionInitInfo" } ] } @@ -3863,10 +4195,26 @@ ] }, { - "name": "DataStore", + "name": "AppData", + "fields": [ + { + "defined": "AppDataUpdateInfo" + } + ] + }, + { + "name": "LinkedLifecycleHook", + "fields": [ + { + "defined": "LinkedLifecycleHookUpdateInfo" + } + ] + }, + { + "name": "LinkedAppData", "fields": [ { - "defined": "DataStoreUpdateInfo" + "defined": "LinkedAppDataUpdateInfo" } ] } @@ -3891,7 +4239,51 @@ ] }, { - "name": "DataStore", + "name": "AppData", + "fields": [ + { + "defined": "Authority" + } + ] + }, + { + "name": "LinkedLifecycleHook", + "fields": [ + "publicKey" + ] + }, + { + "name": "LinkedAppData", + "fields": [ + { + "defined": "Authority" + } + ] + }, + { + "name": "DataSection", + "fields": [ + { + "defined": "LinkedDataKey" + } + ] + } + ] + } + }, + { + "name": "LinkedDataKey", + "type": { + "kind": "enum", + "variants": [ + { + "name": "LinkedLifecycleHook", + "fields": [ + "publicKey" + ] + }, + { + "name": "LinkedAppData", "fields": [ { "defined": "Authority" @@ -4274,12 +4666,12 @@ { "code": 31, "name": "ExternalPluginAdapterNotFound", - "msg": "External PluginExternalPluginAdapter not found" + "msg": "External Plugin Adapter not found" }, { "code": 32, "name": "ExternalPluginAdapterAlreadyExists", - "msg": "External PluginExternalPluginAdapter already exists" + "msg": "External Plugin Adapter already exists" }, { "code": 33, @@ -4325,6 +4717,31 @@ "code": 41, "name": "InvalidPluginOperation", "msg": "Invalid plugin operation" + }, + { + "code": 42, + "name": "TwoDataSources", + "msg": "Two data sources provided, only one is allowed" + }, + { + "code": 43, + "name": "UnsupportedOperation", + "msg": "External Plugin does not support this operation" + }, + { + "code": 44, + "name": "NoDataSources", + "msg": "No data sources provided, one is required" + }, + { + "code": 45, + "name": "InvalidPluginAdapterTarget", + "msg": "This plugin adapter cannot be added to an Asset" + }, + { + "code": 46, + "name": "CannotAddDataSection", + "msg": "Cannot add a Data Section without a linked external plugin" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 59b19a1f..2a0c97c0 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -133,12 +133,12 @@ pub enum MplCoreError { #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, - /// 31 - External PluginExternalPluginAdapter not found - #[error("External PluginExternalPluginAdapter not found")] + /// 31 - External Plugin Adapter not found + #[error("External Plugin Adapter not found")] ExternalPluginAdapterNotFound, - /// 32 - External PluginExternalPluginAdapter already exists - #[error("External PluginExternalPluginAdapter already exists")] + /// 32 - External Plugin Adapter already exists + #[error("External Plugin Adapter already exists")] ExternalPluginAdapterAlreadyExists, /// 33 - Missing asset needed for extra account PDA derivation @@ -176,6 +176,26 @@ pub enum MplCoreError { /// 41 - Invalid plugin operation #[error("Invalid plugin operation")] InvalidPluginOperation, + + /// 42 - Two data sources provided, only one is allowed + #[error("Two data sources provided, only one is allowed")] + TwoDataSources, + + /// 43 - External Plugin does not support this operation + #[error("External Plugin does not support this operation")] + UnsupportedOperation, + + /// 44 - No data sources provided, one is required + #[error("No data sources provided, one is required")] + NoDataSources, + + /// 45 - This plugin adapter cannot be added to an Asset + #[error("This plugin adapter cannot be added to an Asset")] + InvalidPluginAdapterTarget, + + /// 46 - Cannot add a Data Section without a linked external plugin + #[error("Cannot add a Data Section without a linked external plugin")] + CannotAddDataSection, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/instruction.rs b/programs/mpl-core/src/instruction.rs index 4ee1dffa..721b86d8 100644 --- a/programs/mpl-core/src/instruction.rs +++ b/programs/mpl-core/src/instruction.rs @@ -268,16 +268,18 @@ pub(crate) enum MplAssetInstruction { #[account(0, writable, name="asset", desc = "The address of the asset")] #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(3, optional, signer, name="authority", desc = "The Data Authority of the External PluginExternalPluginAdapter")] - #[account(4, name="system_program", desc = "The system program")] - #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + #[account(3, optional, signer, name="authority", desc = "The Data Authority of the External Plugin Adapter")] + #[account(4, optional, name="buffer", desc = "The buffer to write to the external plugin")] + #[account(5, name="system_program", desc = "The system program")] + #[account(6, optional, name="log_wrapper", desc = "The SPL Noop Program")] WriteExternalPluginAdapterDataV1(WriteExternalPluginAdapterDataV1Args), /// Add an external plugin adapter to an mpl-core. #[account(0, writable, name="collection", desc = "The address of the asset")] #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(2, optional, signer, name="authority", desc = "The Data Authority of the External PluginExternalPluginAdapter")] - #[account(3, name="system_program", desc = "The system program")] - #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + #[account(2, optional, signer, name="authority", desc = "The Data Authority of the External Plugin Adapter")] + #[account(3, optional, name="buffer", desc = "The buffer to write to the external plugin")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] WriteCollectionExternalPluginAdapterDataV1(WriteCollectionExternalPluginAdapterDataV1Args), } diff --git a/programs/mpl-core/src/plugins/data_store.rs b/programs/mpl-core/src/plugins/app_data.rs similarity index 74% rename from programs/mpl-core/src/plugins/data_store.rs rename to programs/mpl-core/src/plugins/app_data.rs index da45859e..0d069558 100644 --- a/programs/mpl-core/src/plugins/data_store.rs +++ b/programs/mpl-core/src/plugins/app_data.rs @@ -8,30 +8,30 @@ use super::{ ValidationResult, }; -/// The data store third party plugin contains arbitrary data that can be written to by the +/// The app data third party plugin contains arbitrary data that can be written to by the /// `data_authority`. Note this is different then the overall plugin authority stored in the /// `ExternalRegistryRecord` as it cannot update/revoke authority or change other metadata for the /// plugin. The data is stored at the plugin's data offset (which in the account is immediately /// after this header). #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] -pub struct DataStore { - /// Data authority who can update the data store. Cannot be changed after plugin is +pub struct AppData { + /// Data authority who can update the app data. Cannot be changed after plugin is /// added. pub data_authority: Authority, /// Schema for the data used by the plugin. pub schema: ExternalPluginAdapterSchema, } -impl DataStore { - /// Updates the data store with the new info. - pub fn update(&mut self, info: &DataStoreUpdateInfo) { +impl AppData { + /// Updates the app data with the new info. + pub fn update(&mut self, info: &AppDataUpdateInfo) { if let Some(schema) = &info.schema { self.schema = *schema; } } } -impl PluginValidation for DataStore { +impl PluginValidation for AppData { fn validate_add_external_plugin_adapter( &self, _ctx: &PluginValidationContext, @@ -47,8 +47,8 @@ impl PluginValidation for DataStore { } } -impl From<&DataStoreInitInfo> for DataStore { - fn from(init_info: &DataStoreInitInfo) -> Self { +impl From<&AppDataInitInfo> for AppData { + fn from(init_info: &AppDataInitInfo) -> Self { Self { data_authority: init_info.data_authority, schema: init_info.schema.unwrap_or_default(), @@ -56,10 +56,10 @@ impl From<&DataStoreInitInfo> for DataStore { } } -/// Data store initialization info. +/// App data initialization info. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] -pub struct DataStoreInitInfo { - /// Data authority who can update the data store. This field cannot be +pub struct AppDataInitInfo { + /// Data authority who can update the app data. This field cannot be /// changed after the plugin is added. pub data_authority: Authority, /// Initial plugin authority who can update plugin properties. @@ -68,9 +68,9 @@ pub struct DataStoreInitInfo { pub schema: Option, } -/// Data store update info. +/// App data update info. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] -pub struct DataStoreUpdateInfo { +pub struct AppDataUpdateInfo { /// Schema for the data used by the plugin. pub schema: Option, } diff --git a/programs/mpl-core/src/plugins/data_section.rs b/programs/mpl-core/src/plugins/data_section.rs new file mode 100644 index 00000000..6f6ad65a --- /dev/null +++ b/programs/mpl-core/src/plugins/data_section.rs @@ -0,0 +1,37 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::{ExternalPluginAdapterSchema, LinkedDataKey, PluginValidation}; + +/// The data section plugin is a third party plugin that is _always_ managed by another plugin. +/// Currently these are used for the `LinkedAppData`, and `LinkedLifecycleHook` plugins. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataSection { + /// The key to the plugin that manages this data section. + pub parent_key: LinkedDataKey, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, +} + +impl PluginValidation for DataSection {} + +impl From<&DataSectionInitInfo> for DataSection { + fn from(init_info: &DataSectionInitInfo) -> Self { + Self { + parent_key: init_info.parent_key, + schema: init_info.schema, + } + } +} + +/// App data initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataSectionInitInfo { + /// The key to the plugin that manages this data section. + pub parent_key: LinkedDataKey, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, +} + +/// App data update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataSectionUpdateInfo {} diff --git a/programs/mpl-core/src/plugins/external_plugin_adapters.rs b/programs/mpl-core/src/plugins/external_plugin_adapters.rs index ceb25fb4..5a2209dd 100644 --- a/programs/mpl-core/src/plugins/external_plugin_adapters.rs +++ b/programs/mpl-core/src/plugins/external_plugin_adapters.rs @@ -11,8 +11,10 @@ use crate::{ }; use super::{ - Authority, DataStore, DataStoreInitInfo, DataStoreUpdateInfo, ExternalCheckResult, - ExternalRegistryRecord, LifecycleHook, LifecycleHookInitInfo, LifecycleHookUpdateInfo, Oracle, + AppData, AppDataInitInfo, AppDataUpdateInfo, Authority, DataSection, DataSectionInitInfo, + ExternalCheckResult, ExternalRegistryRecord, LifecycleHook, LifecycleHookInitInfo, + LifecycleHookUpdateInfo, LinkedAppData, LinkedAppDataInitInfo, LinkedAppDataUpdateInfo, + LinkedLifecycleHook, LinkedLifecycleHookInitInfo, LinkedLifecycleHookUpdateInfo, Oracle, OracleInitInfo, OracleUpdateInfo, PluginValidation, PluginValidationContext, ValidationResult, }; @@ -26,16 +28,27 @@ pub enum ExternalPluginAdapterType { LifecycleHook, /// Oracle. Oracle, - /// Data Store. - DataStore, + /// App Data. + AppData, + /// Linked Lifecycle Hook. + LinkedLifecycleHook, + /// Linked App Data. + LinkedAppData, + /// Data Section. + DataSection, } impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType { fn from(key: &ExternalPluginAdapterKey) -> Self { match key { ExternalPluginAdapterKey::LifecycleHook(_) => ExternalPluginAdapterType::LifecycleHook, + ExternalPluginAdapterKey::LinkedLifecycleHook(_) => { + ExternalPluginAdapterType::LinkedLifecycleHook + } ExternalPluginAdapterKey::Oracle(_) => ExternalPluginAdapterType::Oracle, - ExternalPluginAdapterKey::DataStore(_) => ExternalPluginAdapterType::DataStore, + ExternalPluginAdapterKey::AppData(_) => ExternalPluginAdapterType::AppData, + ExternalPluginAdapterKey::LinkedAppData(_) => ExternalPluginAdapterType::LinkedAppData, + ExternalPluginAdapterKey::DataSection(_) => ExternalPluginAdapterType::DataSection, } } } @@ -47,7 +60,14 @@ impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapterType { ExternalPluginAdapterType::LifecycleHook } ExternalPluginAdapterInitInfo::Oracle(_) => ExternalPluginAdapterType::Oracle, - ExternalPluginAdapterInitInfo::DataStore(_) => ExternalPluginAdapterType::DataStore, + ExternalPluginAdapterInitInfo::AppData(_) => ExternalPluginAdapterType::AppData, + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(_) => { + ExternalPluginAdapterType::LinkedLifecycleHook + } + ExternalPluginAdapterInitInfo::LinkedAppData(_) => { + ExternalPluginAdapterType::LinkedAppData + } + ExternalPluginAdapterInitInfo::DataSection(_) => ExternalPluginAdapterType::DataSection, } } } @@ -66,7 +86,17 @@ pub enum ExternalPluginAdapter { Oracle(Oracle), /// Arbitrary data that can be written to by the data `Authority` stored in the attached /// struct. Note this data authority is different then the plugin authority. - DataStore(DataStore), + AppData(AppData), + /// Collection Only: Linked Lifecycle Hook. The hooked program and extra accounts are specified in the attached + /// struct. The hooked program is called at specified lifecycle events and will return a + /// validation result and new data to store. + LinkedLifecycleHook(LinkedLifecycleHook), + /// Collection only: Arbitrary data that can be written to by the data `Authority` stored on any asset in the Collection in the Data Section struct. + /// Authority is different then the plugin authority. + LinkedAppData(LinkedAppData), + /// Data Section. This is a special plugin that is used to contain the data of other external + /// plugins. + DataSection(DataSection), } impl ExternalPluginAdapter { @@ -86,10 +116,10 @@ impl ExternalPluginAdapter { oracle.update(update_info); } ( - ExternalPluginAdapter::DataStore(data_store), - ExternalPluginAdapterUpdateInfo::DataStore(update_info), + ExternalPluginAdapter::AppData(app_data), + ExternalPluginAdapterUpdateInfo::AppData(update_info), ) => { - data_store.update(update_info); + app_data.update(update_info); } _ => unreachable!(), } @@ -120,7 +150,20 @@ impl ExternalPluginAdapter { ExternalCheckResult::none() } } - ExternalPluginAdapterInitInfo::DataStore(_) => ExternalCheckResult::none(), + ExternalPluginAdapterInitInfo::AppData(_) => ExternalCheckResult::none(), + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(init_info) => { + if let Some(checks) = init_info + .lifecycle_checks + .iter() + .find(|event| event.0 == HookableLifecycleEvent::Create) + { + checks.1 + } else { + ExternalCheckResult::none() + } + } + ExternalPluginAdapterInitInfo::LinkedAppData(_) => ExternalCheckResult::none(), + ExternalPluginAdapterInitInfo::DataSection(_) => ExternalCheckResult::none(), } } @@ -129,12 +172,19 @@ impl ExternalPluginAdapter { external_plugin_adapter: &ExternalPluginAdapter, ctx: &PluginValidationContext, ) -> Result { + solana_program::msg!("ExternalPluginAdapter::validate_create"); match external_plugin_adapter { ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { lifecycle_hook.validate_create(ctx) } ExternalPluginAdapter::Oracle(oracle) => oracle.validate_create(ctx), - ExternalPluginAdapter::DataStore(data_store) => data_store.validate_create(ctx), + ExternalPluginAdapter::AppData(app_data) => app_data.validate_create(ctx), + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_create(ctx) + } + ExternalPluginAdapter::LinkedAppData(app_data) => app_data.validate_create(ctx), + // Here we block the creation of a DataSection plugin because this is only done internally. + ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Rejected), } } @@ -148,7 +198,12 @@ impl ExternalPluginAdapter { lifecycle_hook.validate_update(ctx) } ExternalPluginAdapter::Oracle(oracle) => oracle.validate_update(ctx), - ExternalPluginAdapter::DataStore(data_store) => data_store.validate_update(ctx), + ExternalPluginAdapter::AppData(app_data) => app_data.validate_update(ctx), + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_update(ctx) + } + ExternalPluginAdapter::LinkedAppData(app_data) => app_data.validate_update(ctx), + ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Pass), } } @@ -162,7 +217,12 @@ impl ExternalPluginAdapter { lifecycle_hook.validate_burn(ctx) } ExternalPluginAdapter::Oracle(oracle) => oracle.validate_burn(ctx), - ExternalPluginAdapter::DataStore(data_store) => data_store.validate_burn(ctx), + ExternalPluginAdapter::AppData(app_data) => app_data.validate_burn(ctx), + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_burn(ctx) + } + ExternalPluginAdapter::LinkedAppData(app_data) => app_data.validate_burn(ctx), + ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Pass), } } @@ -176,7 +236,12 @@ impl ExternalPluginAdapter { lifecycle_hook.validate_transfer(ctx) } ExternalPluginAdapter::Oracle(oracle) => oracle.validate_transfer(ctx), - ExternalPluginAdapter::DataStore(data_store) => data_store.validate_transfer(ctx), + ExternalPluginAdapter::AppData(app_data) => app_data.validate_transfer(ctx), + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_transfer(ctx) + } + ExternalPluginAdapter::LinkedAppData(app_data) => app_data.validate_transfer(ctx), + ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Pass), } } @@ -192,9 +257,17 @@ impl ExternalPluginAdapter { ExternalPluginAdapter::Oracle(oracle) => { oracle.validate_add_external_plugin_adapter(ctx) } - ExternalPluginAdapter::DataStore(data_store) => { - data_store.validate_add_external_plugin_adapter(ctx) + ExternalPluginAdapter::AppData(app_data) => { + app_data.validate_add_external_plugin_adapter(ctx) + } + ExternalPluginAdapter::LinkedLifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_add_external_plugin_adapter(ctx) + } + ExternalPluginAdapter::LinkedAppData(app_data) => { + app_data.validate_add_external_plugin_adapter(ctx) } + // Here we block the creation of a DataSection plugin because this is only done internally. + ExternalPluginAdapter::DataSection(_) => Ok(ValidationResult::Rejected), } } @@ -225,8 +298,17 @@ impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapter { ExternalPluginAdapterInitInfo::Oracle(init_info) => { ExternalPluginAdapter::Oracle(Oracle::from(init_info)) } - ExternalPluginAdapterInitInfo::DataStore(init_info) => { - ExternalPluginAdapter::DataStore(DataStore::from(init_info)) + ExternalPluginAdapterInitInfo::AppData(init_info) => { + ExternalPluginAdapter::AppData(AppData::from(init_info)) + } + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(init_info) => { + ExternalPluginAdapter::LinkedLifecycleHook(LinkedLifecycleHook::from(init_info)) + } + ExternalPluginAdapterInitInfo::LinkedAppData(init_info) => { + ExternalPluginAdapter::LinkedAppData(LinkedAppData::from(init_info)) + } + ExternalPluginAdapterInitInfo::DataSection(init_info) => { + ExternalPluginAdapter::DataSection(DataSection::from(init_info)) } } } @@ -467,8 +549,14 @@ pub enum ExternalPluginAdapterInitInfo { LifecycleHook(LifecycleHookInitInfo), /// Oracle. Oracle(OracleInitInfo), - /// Data Store. - DataStore(DataStoreInitInfo), + /// App Data. + AppData(AppDataInitInfo), + /// Linked Lifecycle Hook. + LinkedLifecycleHook(LinkedLifecycleHookInitInfo), + /// Linked App Data. + LinkedAppData(LinkedAppDataInitInfo), + /// Data Section. + DataSection(DataSectionInitInfo), } /// Information needed to update an external plugin adapter. @@ -479,22 +567,44 @@ pub enum ExternalPluginAdapterUpdateInfo { LifecycleHook(LifecycleHookUpdateInfo), /// Oracle. Oracle(OracleUpdateInfo), - /// Data Store. - DataStore(DataStoreUpdateInfo), + /// App Data. + AppData(AppDataUpdateInfo), + /// Linked Lifecycle Hook. + LinkedLifecycleHook(LinkedLifecycleHookUpdateInfo), + /// Linked App Data. + LinkedAppData(LinkedAppDataUpdateInfo), } /// Key used to uniquely specify an external plugin adapter after it is created. #[repr(C)] #[derive( - Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, + Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, )] pub enum ExternalPluginAdapterKey { /// Lifecycle Hook. LifecycleHook(Pubkey), /// Oracle. Oracle(Pubkey), - /// Data Store. - DataStore(Authority), + /// App Data. + AppData(Authority), + /// Linked Lifecycle Hook. + LinkedLifecycleHook(Pubkey), + /// Linked App Data. + LinkedAppData(Authority), + /// Data Section. + DataSection(LinkedDataKey), +} + +/// Key to point to the plugin that manages this data section. +#[repr(C)] +#[derive( + Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, +)] +pub enum LinkedDataKey { + /// Lifecycle Hook. + LinkedLifecycleHook(Pubkey), + /// Linked App Data. + LinkedAppData(Authority), } impl ExternalPluginAdapterKey { @@ -513,16 +623,33 @@ impl ExternalPluginAdapterKey { Pubkey::deserialize(&mut &account.data.borrow()[pubkey_or_authority_offset..])?; Ok(Self::LifecycleHook(pubkey)) } + ExternalPluginAdapterType::LinkedLifecycleHook => { + let pubkey = + Pubkey::deserialize(&mut &account.data.borrow()[pubkey_or_authority_offset..])?; + Ok(Self::LinkedLifecycleHook(pubkey)) + } ExternalPluginAdapterType::Oracle => { let pubkey = Pubkey::deserialize(&mut &account.data.borrow()[pubkey_or_authority_offset..])?; Ok(Self::Oracle(pubkey)) } - ExternalPluginAdapterType::DataStore => { + ExternalPluginAdapterType::AppData => { + let authority = Authority::deserialize( + &mut &account.data.borrow()[pubkey_or_authority_offset..], + )?; + Ok(Self::AppData(authority)) + } + ExternalPluginAdapterType::LinkedAppData => { let authority = Authority::deserialize( &mut &account.data.borrow()[pubkey_or_authority_offset..], )?; - Ok(Self::DataStore(authority)) + Ok(Self::LinkedAppData(authority)) + } + ExternalPluginAdapterType::DataSection => { + let linked_data_key = LinkedDataKey::deserialize( + &mut &account.data.borrow()[pubkey_or_authority_offset..], + )?; + Ok(Self::DataSection(linked_data_key)) } } } @@ -537,8 +664,17 @@ impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapterKey { ExternalPluginAdapterInitInfo::Oracle(init_info) => { ExternalPluginAdapterKey::Oracle(init_info.base_address) } - ExternalPluginAdapterInitInfo::DataStore(init_info) => { - ExternalPluginAdapterKey::DataStore(init_info.data_authority) + ExternalPluginAdapterInitInfo::AppData(init_info) => { + ExternalPluginAdapterKey::AppData(init_info.data_authority) + } + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(init_info) => { + ExternalPluginAdapterKey::LinkedLifecycleHook(init_info.hooked_program) + } + ExternalPluginAdapterInitInfo::LinkedAppData(init_info) => { + ExternalPluginAdapterKey::LinkedAppData(init_info.data_authority) + } + ExternalPluginAdapterInitInfo::DataSection(init_info) => { + ExternalPluginAdapterKey::DataSection(init_info.parent_key) } } } diff --git a/programs/mpl-core/src/plugins/linked_app_data.rs b/programs/mpl-core/src/plugins/linked_app_data.rs new file mode 100644 index 00000000..a2132e3c --- /dev/null +++ b/programs/mpl-core/src/plugins/linked_app_data.rs @@ -0,0 +1,71 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::{ + Authority, ExternalPluginAdapterSchema, PluginValidation, PluginValidationContext, + ValidationResult, +}; + +/// The app data third party plugin contains arbitrary data that can be written to by the +/// `data_authority`. Note this is different then the overall plugin authority stored in the +/// `ExternalRegistryRecord` as it cannot update/revoke authority or change other metadata for the +/// plugin. The data is stored at the plugin's data offset (which in the account is immediately +/// after this header). +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedAppData { + /// Data authority who can update the app data. Cannot be changed after plugin is + /// added. + pub data_authority: Authority, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, +} + +impl LinkedAppData { + /// Updates the app data with the new info. + pub fn update(&mut self, info: &LinkedAppDataUpdateInfo) { + if let Some(schema) = &info.schema { + self.schema = *schema; + } + } +} + +impl PluginValidation for LinkedAppData { + fn validate_create( + &self, + ctx: &PluginValidationContext, + ) -> Result { + solana_program::msg!("LinkedAppData::validate_create"); + if ctx.asset_info.is_some() { + Ok(ValidationResult::Rejected) + } else { + Ok(ValidationResult::Pass) + } + } +} + +impl From<&LinkedAppDataInitInfo> for LinkedAppData { + fn from(init_info: &LinkedAppDataInitInfo) -> Self { + Self { + data_authority: init_info.data_authority, + schema: init_info.schema.unwrap_or_default(), + } + } +} + +/// App data initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedAppDataInitInfo { + /// Data authority who can update the app data. This field cannot be + /// changed after the plugin is added. + pub data_authority: Authority, + /// Initial plugin authority who can update plugin properties. + pub init_plugin_authority: Option, + /// Schema for the data used by the plugin. + pub schema: Option, +} + +/// App data update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedAppDataUpdateInfo { + /// Schema for the data used by the plugin. + pub schema: Option, +} diff --git a/programs/mpl-core/src/plugins/linked_lifecycle_hook.rs b/programs/mpl-core/src/plugins/linked_lifecycle_hook.rs new file mode 100644 index 00000000..d2846bf0 --- /dev/null +++ b/programs/mpl-core/src/plugins/linked_lifecycle_hook.rs @@ -0,0 +1,96 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; + +use super::{ + Authority, ExternalCheckResult, ExternalPluginAdapterSchema, ExtraAccount, + HookableLifecycleEvent, PluginValidation, PluginValidationContext, ValidationResult, +}; + +/// Lifecycle hook that CPIs into the `hooked_program`. This hook is used for any lifecycle events +/// that were selected in the `ExternalRegistryRecord` for the plugin. If any extra accounts are +/// present in the `extra_accounts` optional `Vec`, then these accounts are added to the CPI call +/// in the order in which they are in the Vec. Any PDAs in the `Vec` are derived using the hooked +/// program. The hooked program will return a validation result and new data to store at the +/// plugin's data offset (which in the account is immediately after this header). +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedLifecycleHook { + /// The `Pubkey` for the hooked program. + pub hooked_program: Pubkey, // 32 + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// The authority of who can update the Lifecycle Hook data. This can be for the purposes + /// of initialization of data, or schema migration. This field cannot be changed after + /// the plugin is added. + pub data_authority: Option, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, // 1 +} + +impl LinkedLifecycleHook { + /// Updates the lifecycle hook with the new info. + pub fn update(&mut self, info: &LinkedLifecycleHookUpdateInfo) { + if let Some(extra_accounts) = &info.extra_accounts { + self.extra_accounts = Some(extra_accounts.clone()); + } + if let Some(schema) = &info.schema { + self.schema = *schema; + } + } +} + +impl PluginValidation for LinkedLifecycleHook { + fn validate_add_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_transfer( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } +} + +impl From<&LinkedLifecycleHookInitInfo> for LinkedLifecycleHook { + fn from(init_info: &LinkedLifecycleHookInitInfo) -> Self { + Self { + hooked_program: init_info.hooked_program, + extra_accounts: init_info.extra_accounts.clone(), + data_authority: init_info.data_authority, + schema: init_info.schema.unwrap_or_default(), + } + } +} + +/// Lifecycle hook initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedLifecycleHookInitInfo { + /// The `Pubkey` for the hooked program. + pub hooked_program: Pubkey, + /// Initial plugin authority. + pub init_plugin_authority: Option, + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// The authority of who can update the Lifecycle Hook data. This can be for the purposes + /// of initialization of data, or schema migration. This field cannot be changed after + /// the plugin is added. + pub data_authority: Option, + /// Schema for the data used by the plugin. + pub schema: Option, +} + +/// Lifecycle hook update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LinkedLifecycleHookUpdateInfo { + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Option>, + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// Schema for the data used by the plugin. + pub schema: Option, +} diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index 109987cc..b2177509 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -1,19 +1,19 @@ mod add_blocker; +mod app_data; mod attributes; +mod autograph; mod burn_delegate; -mod data_store; +mod data_section; mod edition; mod external_plugin_adapters; mod freeze_delegate; mod immutable_metadata; mod lifecycle; - mod lifecycle_hook; -mod oracle; - +mod linked_app_data; +mod linked_lifecycle_hook; mod master_edition; - -mod autograph; +mod oracle; mod permanent_burn_delegate; mod permanent_freeze_delegate; mod permanent_transfer_delegate; @@ -26,16 +26,19 @@ mod utils; mod verified_creators; pub use add_blocker::*; +pub use app_data::*; pub use attributes::*; pub use autograph::*; pub use burn_delegate::*; -pub use data_store::*; +pub use data_section::*; pub use edition::*; pub use external_plugin_adapters::*; pub use freeze_delegate::*; pub use immutable_metadata::*; pub use lifecycle::*; pub use lifecycle_hook::*; +pub use linked_app_data::*; +pub use linked_lifecycle_hook::*; pub use master_edition::*; pub use oracle::*; pub use permanent_burn_delegate::*; diff --git a/programs/mpl-core/src/plugins/plugin_registry.rs b/programs/mpl-core/src/plugins/plugin_registry.rs index 24730269..f45b4813 100644 --- a/programs/mpl-core/src/plugins/plugin_registry.rs +++ b/programs/mpl-core/src/plugins/plugin_registry.rs @@ -4,6 +4,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use std::{cmp::Ordering, collections::BTreeMap}; use crate::{ + error::MplCoreError, plugins::validate_lifecycle_checks, state::{Authority, DataBlob, Key, SolanaAccount}, }; @@ -72,6 +73,40 @@ impl PluginRegistryV1 { Ok(()) } + + /// Increase the offsets of all plugins after a certain offset. + pub(crate) fn bump_offsets(&mut self, offset: usize, size_diff: isize) -> ProgramResult { + for record in &mut self.registry { + if record.offset > offset { + record.offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize; + } + } + + for record in &mut self.external_registry { + if record.offset > offset { + record.offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize; + } + + if let Some(data_offset) = record.data_offset { + if data_offset > offset { + record.data_offset = Some( + (data_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize, + ); + } + } + } + + Ok(()) + } } impl DataBlob for PluginRegistryV1 { @@ -111,7 +146,7 @@ impl RegistryRecord { /// A type to store the mapping of third party plugin type to third party plugin header and data. #[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct ExternalRegistryRecord { /// The adapter, third party plugin type. pub plugin_type: ExternalPluginAdapterType, diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index d124862f..71f18ec1 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -1,7 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::sol_memcpy, pubkey::Pubkey, + account_info::AccountInfo, + entrypoint::ProgramResult, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, }; use std::collections::HashSet; @@ -13,9 +16,10 @@ use crate::{ }; use super::{ - ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, - ExternalPluginAdapterType, ExternalRegistryRecord, Plugin, PluginHeaderV1, PluginRegistryV1, - PluginType, RegistryRecord, + AppDataInitInfo, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, + ExternalPluginAdapterKey, ExternalPluginAdapterType, ExternalRegistryRecord, + LinkedAppDataInitInfo, LinkedDataKey, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, + RegistryRecord, }; /// Create plugin header and registry if it doesn't exist @@ -185,7 +189,7 @@ pub fn fetch_wrapped_external_plugin_adapter( account: &AccountInfo, core: Option<&T>, plugin_key: &ExternalPluginAdapterKey, -) -> Result<(Authority, ExternalPluginAdapter), ProgramError> { +) -> Result<(ExternalRegistryRecord, ExternalPluginAdapter), ProgramError> { let size = match core { Some(core) => core.get_size(), None => { @@ -211,7 +215,7 @@ pub fn fetch_wrapped_external_plugin_adapter( ExternalPluginAdapter::deserialize(&mut &(*account.data).borrow()[record.offset..])?; // Return the plugin and its authority. - Ok((record.authority, plugin)) + Ok((record.clone(), plugin)) } else { Err(MplCoreError::ExternalPluginAdapterNotFound.into()) } @@ -310,26 +314,39 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( } /// Add an external plugin adapter to the registry and initialize it. +#[allow(clippy::too_many_arguments)] pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( init_info: &ExternalPluginAdapterInitInfo, + core: Option<&T>, plugin_header: &mut PluginHeaderV1, plugin_registry: &mut PluginRegistryV1, account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, + appended_data: Option<&[u8]>, ) -> ProgramResult { - let core = T::load(account, 0)?; - let header_offset = core.get_size(); + let header_offset = match core { + Some(core) => core.get_size(), + None => { + let asset = T::load(account, 0)?; + + if asset.get_size() == account.data_len() { + return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); + } + + asset.get_size() + } + }; let plugin_type = init_info.into(); - // Note currently we are blocking adding LifecycleHook and DataStore external plugin adapters as they + // Note currently we are blocking adding LifecycleHook and LinkedLifecycleHook external plugin adapters as they // are still in development. match init_info { ExternalPluginAdapterInitInfo::LifecycleHook(_) - | ExternalPluginAdapterInitInfo::DataStore(_) => { - return Err(MplCoreError::NotAvailable.into()); + | ExternalPluginAdapterInitInfo::LinkedLifecycleHook(_) => { + return Err(MplCoreError::NotAvailable.into()) } - ExternalPluginAdapterInitInfo::Oracle(_) => (), + _ => {} } // You cannot add a duplicate plugin. @@ -349,6 +366,13 @@ pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( Some(init_info.lifecycle_checks.clone()), ) } + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(init_info) => { + validate_lifecycle_checks(&init_info.lifecycle_checks, false)?; + ( + init_info.init_plugin_authority, + Some(init_info.lifecycle_checks.clone()), + ) + } ExternalPluginAdapterInitInfo::Oracle(init_info) => { validate_lifecycle_checks(&init_info.lifecycle_checks, true)?; ( @@ -356,9 +380,16 @@ pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( Some(init_info.lifecycle_checks.clone()), ) } - ExternalPluginAdapterInitInfo::DataStore(init_info) => { - (init_info.init_plugin_authority, None) - } + ExternalPluginAdapterInitInfo::AppData(AppDataInitInfo { + init_plugin_authority, + .. + }) + | ExternalPluginAdapterInitInfo::LinkedAppData(LinkedAppDataInitInfo { + init_plugin_authority, + .. + }) => (*init_plugin_authority, None), + // The DataSection is only updated via its managing plugin so it has no authority. + ExternalPluginAdapterInitInfo::DataSection(_) => (Some(Authority::None), None), }; let old_registry_offset = plugin_header.plugin_registry_offset; @@ -372,29 +403,46 @@ pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( data_len: None, }; - let mut plugin = ExternalPluginAdapter::from(init_info); - - // If the plugin is a LifecycleHook or DataStore, then we need to set the data offset and length. - match &mut plugin { - ExternalPluginAdapter::LifecycleHook(_) | ExternalPluginAdapter::DataStore(_) => { - new_registry_record.data_offset = Some(old_registry_offset); - new_registry_record.data_len = Some(0); + let plugin = ExternalPluginAdapter::from(init_info); + + // If the plugin is a LifecycleHook or AppData, then we need to set the data offset and length. + match plugin { + ExternalPluginAdapter::LifecycleHook(_) + | ExternalPluginAdapter::AppData(_) + | ExternalPluginAdapter::DataSection(_) => { + // Here we use a 0 value for the data offset as it will be updated after the data is appended. + new_registry_record.data_offset = Some(0); + new_registry_record.data_len = match appended_data { + Some(data) => Some(data.len()), + None => Some(0), + }; } _ => {} }; - let plugin_metadata = plugin.try_to_vec()?; - let plugin_size = plugin_metadata.len(); + let serialized_plugin = plugin.try_to_vec()?; + let plugin_size = serialized_plugin.len(); let size_increase = plugin_size .checked_add(new_registry_record.try_to_vec()?.len()) + .ok_or(MplCoreError::NumericalOverflow)? + .checked_add(new_registry_record.data_len.unwrap_or(0)) .ok_or(MplCoreError::NumericalOverflow)?; - let new_registry_offset = plugin_header + let data_offset = plugin_header .plugin_registry_offset .checked_add(plugin_size) .ok_or(MplCoreError::NumericalOverflow)?; + // If the data offset has been initialized, then we need to set it to the correct value. + if new_registry_record.data_offset.is_some() { + new_registry_record.data_offset = Some(data_offset); + } + + let new_registry_offset = data_offset + .checked_add(new_registry_record.data_len.unwrap_or(0)) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset; plugin_registry.external_registry.push(new_registry_record); @@ -407,13 +455,92 @@ pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( resize_or_reallocate_account(account, payer, system_program, new_size)?; plugin_header.save(account, header_offset)?; plugin.save(account, old_registry_offset)?; + + if let Some(data) = appended_data { + sol_memcpy( + &mut account.data.borrow_mut()[data_offset..], + data, + data.len(), + ); + }; + plugin_registry.save(account, new_registry_offset)?; Ok(()) } +/// Add an external plugin adapter to the registry and initialize it. +#[allow(clippy::too_many_arguments)] +pub fn update_external_plugin_adapter_data<'a, T: DataBlob + SolanaAccount>( + record: &ExternalRegistryRecord, + core: Option<&T>, + plugin_header: &mut PluginHeaderV1, + plugin_registry: &mut PluginRegistryV1, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, + data: &[u8], +) -> ProgramResult { + // Extract the data offset and data length as they should always be set. + let data_offset = record.data_offset.ok_or(MplCoreError::InvalidPlugin)?; + let data_len = record.data_len.ok_or(MplCoreError::InvalidPlugin)?; + let new_data_len = data.len(); + let size_diff = (new_data_len as isize) + .checked_sub(data_len as isize) + .ok_or(MplCoreError::NumericalOverflow)?; + + // Update any offsets that will change. + plugin_registry.bump_offsets(data_offset, size_diff)?; + + let new_registry_offset = (plugin_header.plugin_registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + let new_size = (account.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; + + let next_plugin_offset = data_offset + .checked_add(data_len) + .ok_or(MplCoreError::NumericalOverflow)?; + let new_next_plugin_offset = (next_plugin_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + unsafe { + let base = account.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base.add(new_next_plugin_offset as usize), + base.add(next_plugin_offset), + account.data_len().saturating_sub(next_plugin_offset), + ) + } + + sol_memcpy( + &mut account.data.borrow_mut()[data_offset..], + data, + new_data_len, + ); + + // Find the record in the registry and update the data length. + let record_index = plugin_registry + .external_registry + .iter() + .position(|r| r == record) + .ok_or(MplCoreError::InvalidPlugin)?; + plugin_registry.external_registry[record_index].data_len = Some(new_data_len); + + plugin_registry.save(account, new_registry_offset as usize)?; + plugin_header.save(account, core.map_or(0, |core| core.get_size()))?; + + Ok(()) +} + pub(crate) fn validate_lifecycle_checks( - lifecycle_checks: &Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + lifecycle_checks: &[(HookableLifecycleEvent, ExternalCheckResult)], can_reject_only: bool, ) -> ProgramResult { if lifecycle_checks.is_empty() { @@ -688,7 +815,8 @@ pub(crate) fn find_external_plugin_adapter<'b>( if record.plugin_type == ExternalPluginAdapterType::from(plugin_key) && (match plugin_key { ExternalPluginAdapterKey::LifecycleHook(address) - | ExternalPluginAdapterKey::Oracle(address) => { + | ExternalPluginAdapterKey::Oracle(address) + | ExternalPluginAdapterKey::LinkedLifecycleHook(address) => { let pubkey_offset = record .offset .checked_add(1) @@ -700,7 +828,7 @@ pub(crate) fn find_external_plugin_adapter<'b>( Err(_) => return Err(MplCoreError::DeserializationError.into()), } } - ExternalPluginAdapterKey::DataStore(authority) => { + ExternalPluginAdapterKey::AppData(authority) => { let authority_offset = record .offset .checked_add(1) @@ -713,6 +841,32 @@ pub(crate) fn find_external_plugin_adapter<'b>( Err(_) => return Err(MplCoreError::DeserializationError.into()), } } + ExternalPluginAdapterKey::LinkedAppData(authority) => { + let authority_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + authority + == &match Authority::deserialize( + &mut &account.data.borrow()[authority_offset..], + ) { + Ok(authority) => authority, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } + ExternalPluginAdapterKey::DataSection(linked_data_key) => { + let linked_data_key_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + linked_data_key + == &match LinkedDataKey::deserialize( + &mut &account.data.borrow()[linked_data_key_offset..], + ) { + Ok(linked_data_key) => linked_data_key, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } }) { result = (Some(i), Some(record)); diff --git a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs index 3b7a9f90..1f126d63 100644 --- a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs @@ -101,6 +101,7 @@ pub(crate) fn add_external_plugin_adapter<'a>( ctx.accounts.payer, ctx.accounts.system_program, &args.init_info, + &asset, ) } @@ -155,7 +156,7 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); // Validate collection permissions. - let _ = validate_collection_permissions( + let (core, _, _) = validate_collection_permissions( accounts, authority, ctx.accounts.collection, @@ -175,6 +176,7 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( ctx.accounts.payer, ctx.accounts.system_program, &args.init_info, + &core, ) } @@ -183,16 +185,19 @@ fn process_add_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, init_info: &ExternalPluginAdapterInitInfo, + core: &T, ) -> ProgramResult { let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::(account, payer, system_program)?; initialize_external_plugin_adapter::( init_info, + Some(core), &mut plugin_header, &mut plugin_registry, account, payer, system_program, + None, )?; Ok(()) } diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 195e4cab..6abf89e5 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -214,6 +214,18 @@ pub(crate) fn process_create<'a>( ExternalPluginAdapter::check_create(plugin_init_info), ); + // TODO: This should be handled in the validate call. + match plugin_init_info { + ExternalPluginAdapterInitInfo::LinkedLifecycleHook(_) + | ExternalPluginAdapterInitInfo::LinkedAppData(_) => { + return Err(MplCoreError::InvalidPluginAdapterTarget.into()) + } + ExternalPluginAdapterInitInfo::DataSection(_) => { + return Err(MplCoreError::CannotAddDataSection.into()) + } + _ => (), + } + if external_check_result_bits.can_reject() { let validation_ctx = PluginValidationContext { accounts, @@ -238,11 +250,13 @@ pub(crate) fn process_create<'a>( } initialize_external_plugin_adapter::( plugin_init_info, + Some(&new_asset), &mut plugin_header, &mut plugin_registry, ctx.accounts.asset, ctx.accounts.payer, ctx.accounts.system_program, + None, )?; } } diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index 2ea0b7fa..c07e83e3 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -172,6 +172,10 @@ pub(crate) fn process_create_collection<'a>( ctx.accounts.system_program, )?; for plugin_init_info in &plugins { + if let ExternalPluginAdapterInitInfo::DataSection(_) = plugin_init_info { + return Err(MplCoreError::CannotAddDataSection.into()); + } + let external_check_result_bits = ExternalCheckResultBits::from( ExternalPluginAdapter::check_create(plugin_init_info), ); @@ -200,11 +204,13 @@ pub(crate) fn process_create_collection<'a>( } initialize_external_plugin_adapter::( plugin_init_info, + Some(&new_collection), &mut plugin_header, &mut plugin_registry, ctx.accounts.collection, ctx.accounts.payer, ctx.accounts.system_program, + None, )?; } } diff --git a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs index 0b35197e..d21a6080 100644 --- a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs @@ -226,25 +226,7 @@ fn process_update_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( plugin_header.save(account, core.get_size())?; // Move offsets for existing registry records. - for record in &mut plugin_registry.external_registry { - if registry_record.offset < record.offset { - let new_offset = (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - record.offset = new_offset as usize; - } - } - - for record in &mut plugin_registry.registry { - if registry_record.offset < record.offset { - let new_offset = (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - record.offset = new_offset as usize; - } - } + plugin_registry.bump_offsets(registry_record.offset, size_diff)?; plugin_registry.save(account, new_registry_offset as usize)?; new_plugin.save(account, registry_record.offset)?; diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index c67563da..a4810f36 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -198,26 +198,7 @@ fn process_update_plugin<'a, T: DataBlob + SolanaAccount>( plugin_header.save(account, core.get_size())?; - // Move offsets for existing registry records. - for record in &mut plugin_registry.external_registry { - if registry_record.offset < record.offset { - let new_offset = (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - record.offset = new_offset as usize; - } - } - - for record in &mut plugin_registry.registry { - if registry_record.offset < record.offset { - let new_offset = (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - record.offset = new_offset as usize; - } - } + plugin_registry.bump_offsets(registry_record.offset, size_diff)?; plugin_registry.save(account, new_registry_offset as usize)?; new_plugin.save(account, registry_record.offset)?; diff --git a/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs index 5c132fcb..ac4060d7 100644 --- a/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs +++ b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs @@ -1,7 +1,28 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; -use crate::{error::MplCoreError, plugins::ExternalPluginAdapterKey}; +use crate::{ + error::MplCoreError, + instruction::accounts::{ + WriteCollectionExternalPluginAdapterDataV1Accounts, + WriteExternalPluginAdapterDataV1Accounts, + }, + plugins::{ + create_meta_idempotent, fetch_wrapped_external_plugin_adapter, + initialize_external_plugin_adapter, update_external_plugin_adapter_data, AppData, + DataSectionInitInfo, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, + ExternalPluginAdapterKey, ExternalRegistryRecord, LifecycleHook, LinkedAppData, + LinkedDataKey, LinkedLifecycleHook, PluginHeaderV1, PluginRegistryV1, + }, + state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, + utils::{ + fetch_core_data, load_key, resolve_authority, resolve_pubkey_to_authorities, + resolve_pubkey_to_authorities_collection, + }, +}; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] @@ -9,14 +30,67 @@ pub(crate) struct WriteExternalPluginAdapterDataV1Args { /// External plugin adapter key. pub key: ExternalPluginAdapterKey, /// The data to write. - pub data: Vec, + pub data: Option>, } pub(crate) fn write_external_plugin_adapter_data<'a>( - _accounts: &'a [AccountInfo<'a>], - _args: WriteExternalPluginAdapterDataV1Args, + accounts: &'a [AccountInfo<'a>], + args: WriteExternalPluginAdapterDataV1Args, ) -> ProgramResult { - Err(MplCoreError::NotAvailable.into()) + let ctx = WriteExternalPluginAdapterDataV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + let (asset, mut header, mut registry) = fetch_core_data::(ctx.accounts.asset)?; + + let authorities = resolve_pubkey_to_authorities(authority, ctx.accounts.collection, &asset)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + if let Key::HashedAssetV1 = load_key(ctx.accounts.asset, 0)? { + msg!("Error: Update plugin for compressed is not available"); + return Err(MplCoreError::NotAvailable.into()); + } + + let (record, plugin) = match args.key { + ExternalPluginAdapterKey::LifecycleHook(_) | ExternalPluginAdapterKey::AppData(_) => { + fetch_wrapped_external_plugin_adapter::(ctx.accounts.asset, None, &args.key) + } + ExternalPluginAdapterKey::LinkedLifecycleHook(_) + | ExternalPluginAdapterKey::LinkedAppData(_) => { + let collection = ctx + .accounts + .collection + .ok_or(MplCoreError::MissingCollection)?; + fetch_wrapped_external_plugin_adapter::(collection, None, &args.key) + } + _ => return Err(MplCoreError::UnsupportedOperation.into()), + }?; + + process_write_external_plugin_data::( + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + ctx.accounts.buffer, + args.data.as_deref(), + &asset, + &record, + &plugin, + header.as_mut(), + registry.as_mut(), + &authorities, + &args.key, + ) } #[repr(C)] @@ -25,12 +99,196 @@ pub(crate) struct WriteCollectionExternalPluginAdapterDataV1Args { /// External plugin adapter key. pub key: ExternalPluginAdapterKey, /// The data to write. - pub data: Vec, + pub data: Option>, } pub(crate) fn write_collection_external_plugin_adapter_data<'a>( - _accounts: &'a [AccountInfo<'a>], - _args: WriteCollectionExternalPluginAdapterDataV1Args, + accounts: &'a [AccountInfo<'a>], + args: WriteCollectionExternalPluginAdapterDataV1Args, +) -> ProgramResult { + let ctx = WriteCollectionExternalPluginAdapterDataV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + let (collection, mut header, mut registry) = + fetch_core_data::(ctx.accounts.collection)?; + + let authorities = resolve_pubkey_to_authorities_collection(authority, ctx.accounts.collection)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + let (record, plugin) = fetch_wrapped_external_plugin_adapter::( + ctx.accounts.collection, + None, + &args.key, + )?; + + process_write_external_plugin_data::( + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + ctx.accounts.buffer, + args.data.as_deref(), + &collection, + &record, + &plugin, + header.as_mut(), + registry.as_mut(), + &authorities, + &args.key, + ) +} + +#[allow(clippy::too_many_arguments)] +fn process_write_external_plugin_data<'a, T: DataBlob + SolanaAccount>( + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, + buffer: Option<&AccountInfo<'a>>, + data: Option<&[u8]>, + core: &T, + record: &ExternalRegistryRecord, + wrapped_plugin: &ExternalPluginAdapter, + header: Option<&mut PluginHeaderV1>, + registry: Option<&mut PluginRegistryV1>, + authorities: &[Authority], + _key: &ExternalPluginAdapterKey, ) -> ProgramResult { - Err(MplCoreError::NotAvailable.into()) + // Check that the authority is the same as the plugin's data authority. + match wrapped_plugin { + ExternalPluginAdapter::LifecycleHook(LifecycleHook { + data_authority: Some(data_authority), + .. + }) + | ExternalPluginAdapter::LinkedLifecycleHook(LinkedLifecycleHook { + data_authority: Some(data_authority), + .. + }) + | ExternalPluginAdapter::AppData(AppData { data_authority, .. }) + | ExternalPluginAdapter::LinkedAppData(LinkedAppData { data_authority, .. }) => { + if !authorities.contains(data_authority) { + return Err(MplCoreError::InvalidAuthority.into()); + } + } + _ => return Err(MplCoreError::UnsupportedOperation.into()), + } + + // AppData and LifecycleHook both write the data after the plugin. + // LinkedAppData writes the data to the asset directly. + match wrapped_plugin { + ExternalPluginAdapter::LifecycleHook(_) | ExternalPluginAdapter::AppData(_) => { + let header = header.ok_or(MplCoreError::PluginsNotInitialized)?; + let registry = registry.ok_or(MplCoreError::PluginsNotInitialized)?; + match (data, buffer) { + (Some(data), None) => update_external_plugin_adapter_data( + record, + Some(core), + header, + registry, + account, + payer, + system_program, + data, + ), + (None, Some(buffer)) => update_external_plugin_adapter_data( + record, + Some(core), + header, + registry, + account, + payer, + system_program, + &buffer.data.borrow(), + ), + (Some(_), Some(_)) => Err(MplCoreError::TwoDataSources.into()), + (None, None) => Err(MplCoreError::NoDataSources.into()), + } + } + ExternalPluginAdapter::LinkedAppData(app_data) => { + let (_, mut header, mut registry) = + create_meta_idempotent::(account, payer, system_program)?; + + match fetch_wrapped_external_plugin_adapter( + account, + Some(core), + &ExternalPluginAdapterKey::DataSection(LinkedDataKey::LinkedAppData( + app_data.data_authority, + )), + ) { + Ok((section_record, _)) => match (data, buffer) { + (Some(data), None) => update_external_plugin_adapter_data( + §ion_record, + Some(core), + &mut header, + &mut registry, + account, + payer, + system_program, + data, + ), + (None, Some(buffer)) => update_external_plugin_adapter_data( + §ion_record, + Some(core), + &mut header, + &mut registry, + account, + payer, + system_program, + &buffer.data.borrow(), + ), + (Some(_), Some(_)) => Err(MplCoreError::TwoDataSources.into()), + (None, None) => Err(MplCoreError::NoDataSources.into()), + }, + Err(ref e) + if *e + == ProgramError::Custom( + MplCoreError::ExternalPluginAdapterNotFound as u32, + ) => + { + match (data, buffer) { + (Some(data), None) => initialize_external_plugin_adapter::( + &ExternalPluginAdapterInitInfo::DataSection(DataSectionInitInfo { + parent_key: LinkedDataKey::LinkedAppData(app_data.data_authority), + schema: app_data.schema, + }), + Some(core), + &mut header, + &mut registry, + account, + payer, + system_program, + Some(data), + ), + (None, Some(buffer)) => initialize_external_plugin_adapter::( + &ExternalPluginAdapterInitInfo::DataSection(DataSectionInitInfo { + parent_key: LinkedDataKey::LinkedAppData(app_data.data_authority), + schema: app_data.schema, + }), + Some(core), + &mut header, + &mut registry, + account, + payer, + system_program, + Some(&buffer.data.borrow()), + ), + (Some(_), Some(_)) => Err(MplCoreError::TwoDataSources.into()), + (None, None) => Err(MplCoreError::NoDataSources.into()), + } + } + Err(e) => Err(e), + } + } + _ => Err(MplCoreError::UnsupportedOperation.into()), + } }