diff --git a/clients/js/src/generated/accounts/asset.ts b/clients/js/src/generated/accounts/asset.ts index bc97bc08..a3379eda 100644 --- a/clients/js/src/generated/accounts/asset.ts +++ b/clients/js/src/generated/accounts/asset.ts @@ -26,21 +26,28 @@ import { string, struct, } from '@metaplex-foundation/umi/serializers'; -import { Key, KeyArgs, getKeySerializer } from '../types'; +import { + Key, + KeyArgs, + UpdateAuthority, + UpdateAuthorityArgs, + getKeySerializer, + getUpdateAuthoritySerializer, +} from '../types'; export type Asset = Account; export type AssetAccountData = { key: Key; - updateAuthority: PublicKey; owner: PublicKey; + updateAuthority: UpdateAuthority; name: string; uri: string; }; export type AssetAccountDataArgs = { - updateAuthority: PublicKey; owner: PublicKey; + updateAuthority: UpdateAuthorityArgs; name: string; uri: string; }; @@ -53,8 +60,8 @@ export function getAssetAccountDataSerializer(): Serializer< struct( [ ['key', getKeySerializer()], - ['updateAuthority', publicKeySerializer()], ['owner', publicKeySerializer()], + ['updateAuthority', getUpdateAuthoritySerializer()], ['name', string()], ['uri', string()], ], @@ -130,15 +137,15 @@ export function getAssetGpaBuilder(context: Pick) { return gpaBuilder(context, programId) .registerFields<{ key: KeyArgs; - updateAuthority: PublicKey; owner: PublicKey; + updateAuthority: UpdateAuthorityArgs; name: string; uri: string; }>({ key: [0, getKeySerializer()], - updateAuthority: [1, publicKeySerializer()], - owner: [33, publicKeySerializer()], - name: [65, string()], + owner: [1, publicKeySerializer()], + updateAuthority: [33, getUpdateAuthoritySerializer()], + name: [null, string()], uri: [null, string()], }) .deserializeUsing((account) => deserializeAsset(account)) diff --git a/clients/js/src/generated/errors/mplCoreProgram.ts b/clients/js/src/generated/errors/mplCoreProgram.ts index 170be296..57963158 100644 --- a/clients/js/src/generated/errors/mplCoreProgram.ts +++ b/clients/js/src/generated/errors/mplCoreProgram.ts @@ -265,6 +265,19 @@ export class AlreadyDecompressedError extends ProgramError { codeToErrorMap.set(0x12, AlreadyDecompressedError); nameToErrorMap.set('AlreadyDecompressed', AlreadyDecompressedError); +/** InvalidCollection: Invalid Collection passed in */ +export class InvalidCollectionError extends ProgramError { + readonly name: string = 'InvalidCollection'; + + readonly code: number = 0x13; // 19 + + constructor(program: Program, cause?: Error) { + super('Invalid Collection passed in', program, cause); + } +} +codeToErrorMap.set(0x13, InvalidCollectionError); +nameToErrorMap.set('InvalidCollection', InvalidCollectionError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/types/collection.ts b/clients/js/src/generated/types/collection.ts deleted file mode 100644 index 946fffac..00000000 --- a/clients/js/src/generated/types/collection.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { PublicKey } from '@metaplex-foundation/umi'; -import { - Serializer, - bool, - publicKey as publicKeySerializer, - struct, -} from '@metaplex-foundation/umi/serializers'; - -export type Collection = { collectionAddress: PublicKey; managed: boolean }; - -export type CollectionArgs = Collection; - -export function getCollectionSerializer(): Serializer< - CollectionArgs, - Collection -> { - return struct( - [ - ['collectionAddress', publicKeySerializer()], - ['managed', bool()], - ], - { description: 'Collection' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/compressionProof.ts b/clients/js/src/generated/types/compressionProof.ts index 8b706b09..428e5bee 100644 --- a/clients/js/src/generated/types/compressionProof.ts +++ b/clients/js/src/generated/types/compressionProof.ts @@ -13,20 +13,27 @@ import { string, struct, } from '@metaplex-foundation/umi/serializers'; -import { Key, KeyArgs, getKeySerializer } from '.'; +import { + Key, + KeyArgs, + UpdateAuthority, + UpdateAuthorityArgs, + getKeySerializer, + getUpdateAuthoritySerializer, +} from '.'; export type CompressionProof = { key: Key; - updateAuthority: PublicKey; owner: PublicKey; + updateAuthority: UpdateAuthority; name: string; uri: string; }; export type CompressionProofArgs = { key: KeyArgs; - updateAuthority: PublicKey; owner: PublicKey; + updateAuthority: UpdateAuthorityArgs; name: string; uri: string; }; @@ -38,8 +45,8 @@ export function getCompressionProofSerializer(): Serializer< return struct( [ ['key', getKeySerializer()], - ['updateAuthority', publicKeySerializer()], ['owner', publicKeySerializer()], + ['updateAuthority', getUpdateAuthoritySerializer()], ['name', string()], ['uri', string()], ], diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index e23143fc..98a18533 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -8,7 +8,6 @@ export * from './authority'; export * from './burn'; -export * from './collection'; export * from './compressionProof'; export * from './creator'; export * from './dataState'; @@ -24,4 +23,5 @@ export * from './registryRecord'; export * from './royalties'; export * from './ruleSet'; export * from './transfer'; +export * from './updateAuthority'; export * from './updateDelegate'; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index c3cd3389..e6599527 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -18,8 +18,6 @@ import { import { Burn, BurnArgs, - Collection, - CollectionArgs, Freeze, FreezeArgs, Royalties, @@ -29,7 +27,6 @@ import { UpdateDelegate, UpdateDelegateArgs, getBurnSerializer, - getCollectionSerializer, getFreezeSerializer, getRoyaltiesSerializer, getTransferSerializer, @@ -42,7 +39,6 @@ export type Plugin = | { __kind: 'Freeze'; fields: [Freeze] } | { __kind: 'Burn'; fields: [Burn] } | { __kind: 'Transfer'; fields: [Transfer] } - | { __kind: 'Collection'; fields: [Collection] } | { __kind: 'UpdateDelegate'; fields: [UpdateDelegate] }; export type PluginArgs = @@ -51,7 +47,6 @@ export type PluginArgs = | { __kind: 'Freeze'; fields: [FreezeArgs] } | { __kind: 'Burn'; fields: [BurnArgs] } | { __kind: 'Transfer'; fields: [TransferArgs] } - | { __kind: 'Collection'; fields: [CollectionArgs] } | { __kind: 'UpdateDelegate'; fields: [UpdateDelegateArgs] }; export function getPluginSerializer(): Serializer { @@ -82,12 +77,6 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getTransferSerializer()])], ]), ], - [ - 'Collection', - struct>([ - ['fields', tuple([getCollectionSerializer()])], - ]), - ], [ 'UpdateDelegate', struct>([ @@ -119,10 +108,6 @@ export function plugin( kind: 'Transfer', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; -export function plugin( - kind: 'Collection', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; export function plugin( kind: 'UpdateDelegate', data: GetDataEnumKindContent['fields'] diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts index 58bca11f..e66967ac 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -14,7 +14,6 @@ export enum PluginType { Freeze, Burn, Transfer, - Collection, UpdateDelegate, } diff --git a/clients/js/src/generated/types/updateAuthority.ts b/clients/js/src/generated/types/updateAuthority.ts new file mode 100644 index 00000000..38d010cd --- /dev/null +++ b/clients/js/src/generated/types/updateAuthority.ts @@ -0,0 +1,77 @@ +/** + * 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, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type UpdateAuthority = + | { __kind: 'None' } + | { __kind: 'Address'; fields: [PublicKey] } + | { __kind: 'Collection'; fields: [PublicKey] }; + +export type UpdateAuthorityArgs = UpdateAuthority; + +export function getUpdateAuthoritySerializer(): Serializer< + UpdateAuthorityArgs, + UpdateAuthority +> { + return dataEnum( + [ + ['None', unit()], + [ + 'Address', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + [ + 'Collection', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + ], + { description: 'UpdateAuthority' } + ) as Serializer; +} + +// Data Enum Helpers. +export function updateAuthority( + kind: 'None' +): GetDataEnumKind; +export function updateAuthority( + kind: 'Address', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function updateAuthority( + kind: 'Collection', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function updateAuthority( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isUpdateAuthority( + kind: K, + value: UpdateAuthority +): value is UpdateAuthority & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/hooked/fetchAssetWithPlugins.ts b/clients/js/src/hooked/fetchAssetWithPlugins.ts index 25407573..3c1b22ce 100644 --- a/clients/js/src/hooked/fetchAssetWithPlugins.ts +++ b/clients/js/src/hooked/fetchAssetWithPlugins.ts @@ -8,7 +8,11 @@ import { } from '@metaplex-foundation/umi'; import { Asset, + Authority, + Plugin, + PluginHeader, PluginHeaderAccountData, + PluginRegistry, PluginRegistryAccountData, deserializeAsset, getAssetAccountDataSerializer, @@ -16,9 +20,17 @@ import { getPluginRegistryAccountDataSerializer, getPluginSerializer, } from '../generated'; -import { PluginList, PluginWithAuthorities } from '.'; +export type PluginWithAuthorities = { + plugin: Plugin; + authorities: Authority[]; +}; +export type PluginList = { + pluginHeader?: Omit; + plugins?: PluginWithAuthorities[]; + pluginRegistry?: Omit; +}; export type AssetWithPlugins = Asset & PluginList; export async function fetchAssetWithPlugins( diff --git a/clients/js/test/addAuthority.test.ts b/clients/js/test/addAuthority.test.ts index 16fc03c9..01862b3f 100644 --- a/clients/js/test/addAuthority.test.ts +++ b/clients/js/test/addAuthority.test.ts @@ -11,6 +11,7 @@ import { create, fetchAsset, fetchAssetWithPlugins, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -34,7 +35,7 @@ test('it can add an authority to a plugin', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -63,20 +64,20 @@ test('it can add an authority to a plugin', async (t) => { // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset1, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [ { __kind: 'Owner' }, { __kind: 'Pubkey', address: delegateAddress.publicKey }, diff --git a/clients/js/test/addPlugin.test.ts b/clients/js/test/addPlugin.test.ts index d458044f..7cb5c76c 100644 --- a/clients/js/test/addPlugin.test.ts +++ b/clients/js/test/addPlugin.test.ts @@ -9,6 +9,7 @@ import { create, fetchAsset, fetchAssetWithPlugins, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -31,7 +32,7 @@ test('it can add a plugin to an asset', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -49,20 +50,20 @@ test('it can add a plugin to an asset', async (t) => { // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset1, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [{ __kind: 'Owner' }], }, ], diff --git a/clients/js/test/compress.test.ts b/clients/js/test/compress.test.ts index f4d39493..2aba1bae 100644 --- a/clients/js/test/compress.test.ts +++ b/clients/js/test/compress.test.ts @@ -11,6 +11,7 @@ import { getHashedAssetSchemaSerializer, hash, HashedAssetSchema, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; // import bs58 from 'bs58'; @@ -34,7 +35,7 @@ test('it can compress an asset without any plugins as the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -83,7 +84,7 @@ test('it cannot compress an asset if not the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -100,7 +101,7 @@ test('it cannot compress an asset if not the owner', async (t) => { // console.log("Account State:", afterAsset); t.like(afterAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index 125a5a89..5728aee9 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -10,6 +10,7 @@ import { fetchAssetWithPlugins, fetchHashedAsset, getAssetAccountDataSerializer, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -32,7 +33,7 @@ test('it can create a new asset in account state', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -69,7 +70,7 @@ test('it can create a new asset in ledger state', async (t) => { const parsed = getAssetAccountDataSerializer().deserialize(data)[0]; // console.log("Ledger State:", parsed); t.like(parsed, { - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -96,20 +97,20 @@ test('it can create a new asset with plugins', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [{ __kind: 'Owner' }], }, ], diff --git a/clients/js/test/createCollection.test.ts b/clients/js/test/createCollection.test.ts index 4a0e6567..95cfa904 100644 --- a/clients/js/test/createCollection.test.ts +++ b/clients/js/test/createCollection.test.ts @@ -10,6 +10,7 @@ import { fetchAssetWithPlugins, fetchCollectionData, fetchCollectionWithPlugins, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -105,12 +106,7 @@ test('it can create a new asset with a collection', async (t) => { name: 'Test Bread', uri: 'https://example.com/bread', collection: collectionAddress.publicKey, - plugins: [{ - __kind: 'Collection', fields: [{ - collectionAddress: collectionAddress.publicKey, - managed: true - }] - }], + plugins: [], }).sendAndConfirm(umi); // Then an account was created with the correct data. @@ -118,34 +114,21 @@ test('it can create a new asset with a collection', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Collection", [collectionAddress.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(151), + pluginRegistryOffset: BigInt(118), }, pluginRegistry: { key: 4, - registry: [ - { - pluginType: 5, - offset: BigInt(117), - authorities: [{ __kind: 'UpdateAuthority' }], - }, - ], }, - plugins: [ - { - authorities: [{ __kind: 'UpdateAuthority' }], - plugin: { - __kind: 'Collection', - fields: [{ collectionAddress: collectionAddress.publicKey, managed: true }], - }, - }, - ], }); + + t.assert(asset.pluginRegistry?.registry.length === 0); + t.assert(asset.plugins?.length === 0); }); test('it cannot create a new asset with a collection if it is not the collection auth', async (t) => { @@ -203,12 +186,7 @@ test('it cannot create a new asset with a collection if it is not the collection name: 'Test Bread', uri: 'https://example.com/bread', collection: collectionAddress.publicKey, - plugins: [{ - __kind: 'Collection', fields: [{ - collectionAddress: collectionAddress.publicKey, - managed: true - }] - }], + plugins: [], }).sendAndConfirm(umi); await t.throwsAsync(result, { name: "InvalidAuthority" }); diff --git a/clients/js/test/decompress.test.ts b/clients/js/test/decompress.test.ts index 83f66ae0..742a64a1 100644 --- a/clients/js/test/decompress.test.ts +++ b/clients/js/test/decompress.test.ts @@ -13,6 +13,7 @@ import { hash, HashedAssetSchema, Key, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -35,7 +36,7 @@ test('it can decompress a previously compressed asset as the owner', async (t) = // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -70,7 +71,7 @@ test('it can decompress a previously compressed asset as the owner', async (t) = owner: umi.identity, compressionProof: { key: Key.Asset, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -82,7 +83,7 @@ test('it can decompress a previously compressed asset as the owner', async (t) = t.like(afterDecompressedAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/js/test/burn.test.ts b/clients/js/test/plugins/burn.test.ts similarity index 88% rename from clients/js/test/burn.test.ts rename to clients/js/test/plugins/burn.test.ts index 4a0faac6..33978754 100644 --- a/clients/js/test/burn.test.ts +++ b/clients/js/test/plugins/burn.test.ts @@ -5,8 +5,8 @@ import { } from '@metaplex-foundation/umi'; import test from 'ava'; // import { base58 } from '@metaplex-foundation/umi/serializers'; -import { Asset, DataState, create, fetchAsset, burn, Key } from '../src'; -import { createUmi } from './_setup'; +import { Asset, DataState, create, fetchAsset, burn, Key, updateAuthority } from '../../src'; +import { createUmi } from '../_setup'; test('it can burn an asset as the owner', async (t) => { // Given a Umi instance and a new signer. @@ -27,7 +27,7 @@ test('it can burn an asset as the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -67,7 +67,7 @@ test('it cannot burn an asset if not the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -85,7 +85,7 @@ test('it cannot burn an asset if not the owner', async (t) => { // console.log("Account State:", afterAsset); t.like(afterAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/js/test/collectionUpdateDelegate.test.ts b/clients/js/test/plugins/collectionUpdateDelegate.test.ts similarity index 79% rename from clients/js/test/collectionUpdateDelegate.test.ts rename to clients/js/test/plugins/collectionUpdateDelegate.test.ts index 1ea68ccf..e20cba60 100644 --- a/clients/js/test/collectionUpdateDelegate.test.ts +++ b/clients/js/test/plugins/collectionUpdateDelegate.test.ts @@ -11,8 +11,9 @@ import { createCollection, fetchAssetWithPlugins, fetchCollectionWithPlugins, -} from '../src'; -import { createUmi } from './_setup'; + updateAuthority, +} from '../../src'; +import { createUmi } from '../_setup'; test('it can create a new asset with a collection if it is the collection update delegate', async (t) => { // Given a Umi instance and a new signer. @@ -93,44 +94,26 @@ test('it can create a new asset with a collection if it is the collection update name: 'Test Bread', uri: 'https://example.com/bread', collection: collectionAddress.publicKey, - plugins: [{ - __kind: 'Collection', fields: [{ - collectionAddress: collectionAddress.publicKey, - managed: true - }] - }], + plugins: [], }).sendAndConfirm(umi); const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); - console.log("Asset State:", asset); + // console.log("Asset State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Collection", [collectionAddress.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(151), + pluginRegistryOffset: BigInt(118), }, pluginRegistry: { key: 4, - registry: [ - { - pluginType: PluginType.Collection, - offset: BigInt(117), - authorities: [{ __kind: 'UpdateAuthority' }], - }, - ], }, - plugins: [ - { - authorities: [{ __kind: 'UpdateAuthority' }], - plugin: { - __kind: 'Collection', - fields: [{ collectionAddress: collectionAddress.publicKey, managed: true }], - }, - }, - ], }); + + t.assert(asset.pluginRegistry?.registry.length === 0); + t.assert(asset.plugins?.length === 0); }); \ No newline at end of file diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/plugins/delegate.test.ts similarity index 92% rename from clients/js/test/delegate.test.ts rename to clients/js/test/plugins/delegate.test.ts index 3a4ac761..c81c789f 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/plugins/delegate.test.ts @@ -9,9 +9,10 @@ import { addPlugin, create, fetchAssetWithPlugins, + updateAuthority, updatePlugin, -} from '../src'; -import { createUmi } from './_setup'; +} from '../../src'; +import { createUmi } from '../_setup'; test('it can delegate a new authority', async (t) => { // Given a Umi instance and a new signer. @@ -49,20 +50,19 @@ test('it can delegate a new authority', async (t) => { // console.log(asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [ { __kind: 'Owner' }, { __kind: 'Pubkey', address: delegateAddress.publicKey }, @@ -129,20 +129,20 @@ test('a delegate can freeze the token', async (t) => { // console.log(asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [ { __kind: 'Owner' }, { __kind: 'Pubkey', address: delegateAddress.publicKey }, diff --git a/clients/js/test/delegateTransfer.test.ts b/clients/js/test/plugins/delegateTransfer.test.ts similarity index 90% rename from clients/js/test/delegateTransfer.test.ts rename to clients/js/test/plugins/delegateTransfer.test.ts index 38bfe501..ede28003 100644 --- a/clients/js/test/delegateTransfer.test.ts +++ b/clients/js/test/plugins/delegateTransfer.test.ts @@ -9,8 +9,9 @@ import { create, fetchAssetWithPlugins, transfer, -} from '../src'; -import { createUmi } from './_setup'; + updateAuthority, +} from '../../src'; +import { createUmi } from '../_setup'; test('a delegate can transfer the asset', async (t) => { // Given a Umi instance and a new signer. @@ -56,20 +57,20 @@ test('a delegate can transfer the asset', async (t) => { // console.log(asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: newOwnerAddress.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(118), + pluginRegistryOffset: BigInt(119), }, pluginRegistry: { key: 4, registry: [ { pluginType: PluginType.Transfer, - offset: BigInt(117), + offset: BigInt(118), authorities: [ { __kind: 'Owner' }, { __kind: 'Pubkey', address: delegateAddress.publicKey }, diff --git a/clients/js/test/removeAuthority.test.ts b/clients/js/test/removeAuthority.test.ts index 87d1f47f..8ddbcc4e 100644 --- a/clients/js/test/removeAuthority.test.ts +++ b/clients/js/test/removeAuthority.test.ts @@ -12,6 +12,7 @@ import { fetchAsset, fetchAssetWithPlugins, removeAuthority, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -35,7 +36,7 @@ test('it can remove an authority from a plugin', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -64,20 +65,20 @@ test('it can remove an authority from a plugin', async (t) => { // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset1, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [ { __kind: 'Owner' }, { __kind: 'Pubkey', address: delegateAddress.publicKey }, @@ -112,20 +113,20 @@ test('it can remove an authority from a plugin', async (t) => { // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset2, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [{ __kind: 'Owner' }], }, ], @@ -161,7 +162,7 @@ test('it can remove the default authority from a plugin to make it immutable', a // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -187,20 +188,20 @@ test('it can remove the default authority from a plugin to make it immutable', a // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset1, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [{ __kind: 'None' }], }, ], diff --git a/clients/js/test/removePlugin.test.ts b/clients/js/test/removePlugin.test.ts index 16da4e39..c57d7854 100644 --- a/clients/js/test/removePlugin.test.ts +++ b/clients/js/test/removePlugin.test.ts @@ -11,6 +11,7 @@ import { fetchAsset, fetchAssetWithPlugins, removePlugin, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -33,7 +34,7 @@ test('it can remove a plugin from an asset', async (t) => { // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -51,20 +52,20 @@ test('it can remove a plugin from an asset', async (t) => { // console.log(JSON.stringify(asset1, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset1, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(119), + pluginRegistryOffset: BigInt(120), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(117), + offset: BigInt(118), authorities: [{ __kind: 'Owner' }], }, ], @@ -89,13 +90,13 @@ test('it can remove a plugin from an asset', async (t) => { // console.log(JSON.stringify(asset2, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset2, ({ publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(117), + pluginRegistryOffset: BigInt(118), }, pluginRegistry: { key: 4, diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index 3085f075..a0e762d9 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -1,7 +1,7 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; // import { base58 } from '@metaplex-foundation/umi/serializers'; -import { Asset, DataState, create, fetchAsset, transfer } from '../src'; +import { Asset, DataState, create, fetchAsset, transfer, updateAuthority } from '../src'; import { createUmi } from './_setup'; test('it can transfer an asset as the owner', async (t) => { @@ -24,7 +24,7 @@ test('it can transfer an asset as the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -40,7 +40,7 @@ test('it can transfer an asset as the owner', async (t) => { // console.log("Account State:", afterAsset); t.like(afterAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: newOwner.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -68,7 +68,7 @@ test('it cannot transfer an asset if not the owner', async (t) => { // console.log("Account State:", beforeAsset); t.like(beforeAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', @@ -87,7 +87,7 @@ test('it cannot transfer an asset if not the owner', async (t) => { // console.log("Account State:", afterAsset); t.like(afterAsset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/js/test/update.test.ts b/clients/js/test/update.test.ts index 831f563f..7df0cef6 100644 --- a/clients/js/test/update.test.ts +++ b/clients/js/test/update.test.ts @@ -9,6 +9,7 @@ import { create, fetchAssetWithPlugins, update, + updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -36,7 +37,7 @@ test('it can update an asset to be larger', async (t) => { const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); t.like(asset, { key: Key.Asset, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread 2', uri: 'https://example.com/bread2', @@ -67,7 +68,7 @@ test('it can update an asset to be smaller', async (t) => { const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); t.like(asset, { key: Key.Asset, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: '', uri: '', @@ -107,20 +108,20 @@ test('it can update an asset with plugins to be larger', async (t) => { // console.log(JSON.stringify(asset, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: 'Test Bread 2', uri: 'https://example.com/bread2', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(122), + pluginRegistryOffset: BigInt(123), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(120), + offset: BigInt(121), authorities: [{ __kind: 'Owner' }], }, ], @@ -169,20 +170,20 @@ test('it can update an asset with plugins to be smaller', async (t) => { const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); t.like(asset, { publicKey: assetAddress.publicKey, - updateAuthority: umi.identity.publicKey, + updateAuthority: updateAuthority("Address", [umi.identity.publicKey]), owner: umi.identity.publicKey, name: '', uri: '', pluginHeader: { key: 3, - pluginRegistryOffset: BigInt(84), + pluginRegistryOffset: BigInt(85), }, pluginRegistry: { key: 4, registry: [ { pluginType: 2, - offset: BigInt(82), + offset: BigInt(83), authorities: [{ __kind: 'Owner' }], }, ], diff --git a/clients/rust/src/generated/accounts/asset.rs b/clients/rust/src/generated/accounts/asset.rs index 876396ee..7e4dc39f 100644 --- a/clients/rust/src/generated/accounts/asset.rs +++ b/clients/rust/src/generated/accounts/asset.rs @@ -6,6 +6,7 @@ //! use crate::generated::types::Key; +use crate::generated::types::UpdateAuthority; use borsh::BorshDeserialize; use borsh::BorshSerialize; use solana_program::pubkey::Pubkey; @@ -18,12 +19,8 @@ pub struct Asset { feature = "serde", serde(with = "serde_with::As::") )] - pub update_authority: Pubkey, - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] pub owner: Pubkey, + pub update_authority: UpdateAuthority, pub name: String, pub uri: String, } diff --git a/clients/rust/src/generated/errors/mpl_core_program.rs b/clients/rust/src/generated/errors/mpl_core_program.rs index 9bd108a0..b4fe9f7d 100644 --- a/clients/rust/src/generated/errors/mpl_core_program.rs +++ b/clients/rust/src/generated/errors/mpl_core_program.rs @@ -67,6 +67,9 @@ pub enum MplCoreProgramError { /// 18 (0x12) - Already decompressed account #[error("Already decompressed account")] AlreadyDecompressed, + /// 19 (0x13) - Invalid Collection passed in + #[error("Invalid Collection passed in")] + InvalidCollection, } impl solana_program::program_error::PrintProgramError for MplCoreProgramError { diff --git a/clients/rust/src/generated/types/compression_proof.rs b/clients/rust/src/generated/types/compression_proof.rs index 0ae2e256..05c47b51 100644 --- a/clients/rust/src/generated/types/compression_proof.rs +++ b/clients/rust/src/generated/types/compression_proof.rs @@ -6,6 +6,7 @@ //! use crate::generated::types::Key; +use crate::generated::types::UpdateAuthority; use borsh::BorshDeserialize; use borsh::BorshSerialize; use solana_program::pubkey::Pubkey; @@ -18,12 +19,8 @@ pub struct CompressionProof { feature = "serde", serde(with = "serde_with::As::") )] - pub update_authority: Pubkey, - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] pub owner: Pubkey, + pub update_authority: UpdateAuthority, pub name: String, pub uri: String, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index e7d293e7..29fa601c 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -7,7 +7,6 @@ pub(crate) mod authority; pub(crate) mod burn; -pub(crate) mod collection; pub(crate) mod compression_proof; pub(crate) mod creator; pub(crate) mod data_state; @@ -23,11 +22,11 @@ pub(crate) mod registry_record; pub(crate) mod royalties; pub(crate) mod rule_set; pub(crate) mod transfer; +pub(crate) mod update_authority; pub(crate) mod update_delegate; pub use self::authority::*; pub use self::burn::*; -pub use self::collection::*; pub use self::compression_proof::*; pub use self::creator::*; pub use self::data_state::*; @@ -43,4 +42,5 @@ pub use self::registry_record::*; pub use self::royalties::*; pub use self::rule_set::*; pub use self::transfer::*; +pub use self::update_authority::*; pub use self::update_delegate::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 9fa18b42..950af19a 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -6,7 +6,6 @@ //! use crate::generated::types::Burn; -use crate::generated::types::Collection; use crate::generated::types::Freeze; use crate::generated::types::Royalties; use crate::generated::types::Transfer; @@ -22,6 +21,5 @@ pub enum Plugin { Freeze(Freeze), Burn(Burn), Transfer(Transfer), - Collection(Collection), UpdateDelegate(UpdateDelegate), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index 61422210..4689e209 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -16,6 +16,5 @@ pub enum PluginType { Freeze, Burn, Transfer, - Collection, UpdateDelegate, } diff --git a/clients/rust/src/generated/types/collection.rs b/clients/rust/src/generated/types/update_authority.rs similarity index 70% rename from clients/rust/src/generated/types/collection.rs rename to clients/rust/src/generated/types/update_authority.rs index 70312186..4aa85693 100644 --- a/clients/rust/src/generated/types/collection.rs +++ b/clients/rust/src/generated/types/update_authority.rs @@ -11,11 +11,8 @@ use solana_program::pubkey::Pubkey; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Collection { - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - pub collection_address: Pubkey, - pub managed: bool, +pub enum UpdateAuthority { + None, + Address(Pubkey), + Collection(Pubkey), } diff --git a/idls/mpl_core_program.json b/idls/mpl_core_program.json index 713a2052..d58070fc 100644 --- a/idls/mpl_core_program.json +++ b/idls/mpl_core_program.json @@ -854,12 +854,14 @@ } }, { - "name": "updateAuthority", + "name": "owner", "type": "publicKey" }, { - "name": "owner", - "type": "publicKey" + "name": "updateAuthority", + "type": { + "defined": "UpdateAuthority" + } }, { "name": "name", @@ -938,22 +940,6 @@ "fields": [] } }, - { - "name": "Collection", - "type": { - "kind": "struct", - "fields": [ - { - "name": "collectionAddress", - "type": "publicKey" - }, - { - "name": "managed", - "type": "bool" - } - ] - } - }, { "name": "Freeze", "type": { @@ -1287,12 +1273,14 @@ } }, { - "name": "updateAuthority", + "name": "owner", "type": "publicKey" }, { - "name": "owner", - "type": "publicKey" + "name": "updateAuthority", + "type": { + "defined": "UpdateAuthority" + } }, { "name": "name", @@ -1396,14 +1384,6 @@ } ] }, - { - "name": "Collection", - "fields": [ - { - "defined": "Collection" - } - ] - }, { "name": "UpdateDelegate", "fields": [ @@ -1435,9 +1415,6 @@ { "name": "Transfer" }, - { - "name": "Collection" - }, { "name": "UpdateDelegate" } @@ -1585,6 +1562,29 @@ } ] } + }, + { + "name": "UpdateAuthority", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "Address", + "fields": [ + "publicKey" + ] + }, + { + "name": "Collection", + "fields": [ + "publicKey" + ] + } + ] + } } ], "errors": [ @@ -1682,6 +1682,11 @@ "code": 18, "name": "AlreadyDecompressed", "msg": "Already decompressed account" + }, + { + "code": 19, + "name": "InvalidCollection", + "msg": "Invalid Collection passed in" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index d4adb264..078aae20 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -84,6 +84,10 @@ pub enum MplCoreError { /// 18 - Already decompressed #[error("Already decompressed account")] AlreadyDecompressed, + + /// 19 - Invalid Collection passed in + #[error("Invalid Collection passed in")] + InvalidCollection, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/plugins/collection.rs b/programs/mpl-core/src/plugins/collection.rs deleted file mode 100644 index f82c662a..00000000 --- a/programs/mpl-core/src/plugins/collection.rs +++ /dev/null @@ -1,124 +0,0 @@ -use borsh::{BorshDeserialize, BorshSerialize}; -use mpl_utils::assert_signer; -use solana_program::pubkey::Pubkey; - -use crate::{ - instruction::accounts::{ - BurnAccounts, CompressAccounts, CreateAccounts, DecompressAccounts, TransferAccounts, - UpdateAccounts, - }, - plugins::{fetch_plugin, PluginType}, - processor::{BurnArgs, CompressArgs, CreateArgs, DecompressArgs, TransferArgs, UpdateArgs}, - state::{Authority, CollectionData, DataBlob, SolanaAccount}, - utils::assert_collection_authority, -}; - -use super::{PluginValidation, ValidationResult}; - -/// This plugin manages a collection or grouping of assets. -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] -pub struct Collection { - /// A pointer to the collection which the asset is a part of. - collection_address: Pubkey, // 32 - /// This flag indicates if the collection is required when operating on the asset. - /// Managed collections use the Collection as a parent which can store plugins that - /// are applied to all assets in the collection by default. Plugins on the asset itself - /// can override the collection plugins. - managed: bool, // 1 -} - -impl DataBlob for Collection { - fn get_initial_size() -> usize { - 33 - } - - fn get_size(&self) -> usize { - 33 - } -} - -impl PluginValidation for Collection { - fn validate_create( - &self, - ctx: &CreateAccounts, - _args: &CreateArgs, - _authorities: &[Authority], - ) -> Result { - match ctx.collection { - Some(collection_info) => { - let collection = CollectionData::load(collection_info, 0)?; - solana_program::msg!("Collection: {:?}", collection); - // Check that the collection update authority is a signer. - let authority = match ctx.update_authority { - Some(authority) => authority, - None => ctx.payer, - }; - - assert_signer(authority)?; - - let maybe_update_delegate = - fetch_plugin(collection_info, PluginType::UpdateDelegate); - - if let Ok((mut authorities, _, _)) = maybe_update_delegate { - authorities.push(Authority::UpdateAuthority); - if assert_collection_authority(&collection, authority, &authorities).is_err() { - return Ok(ValidationResult::Rejected); - } - } else if authority.key != &collection.update_authority { - return Ok(ValidationResult::Rejected); - } - - Ok(ValidationResult::Pass) - } - None => { - solana_program::msg!("No collection provided"); - Ok(ValidationResult::Rejected) - } - } - } - - fn validate_update( - &self, - _ctx: &UpdateAccounts, - _args: &UpdateArgs, - _authorities: &[Authority], - ) -> Result { - Ok(ValidationResult::Pass) - } - - fn validate_burn( - &self, - _ctx: &BurnAccounts, - _args: &BurnArgs, - _authorities: &[Authority], - ) -> Result { - Ok(ValidationResult::Pass) - } - - fn validate_transfer( - &self, - _ctx: &TransferAccounts, - _args: &TransferArgs, - _authorities: &[Authority], - ) -> Result { - Ok(ValidationResult::Pass) - } - - fn validate_compress( - &self, - _ctx: &CompressAccounts, - _args: &CompressArgs, - _authorities: &[Authority], - ) -> Result { - Ok(ValidationResult::Pass) - } - - fn validate_decompress( - &self, - _ctx: &DecompressAccounts, - _args: &DecompressArgs, - _authorities: &[Authority], - ) -> Result { - Ok(ValidationResult::Pass) - } -} diff --git a/programs/mpl-core/src/plugins/freeze.rs b/programs/mpl-core/src/plugins/freeze.rs index a51b9053..064b573e 100644 --- a/programs/mpl-core/src/plugins/freeze.rs +++ b/programs/mpl-core/src/plugins/freeze.rs @@ -71,7 +71,7 @@ impl PluginValidation for Freeze { ) -> Result { if !self.frozen && ((ctx.authority.key == &asset.owner && authorities.contains(&Authority::Owner)) - || (ctx.authority.key == &asset.update_authority + || (ctx.authority.key == &asset.update_authority.key() && authorities.contains(&Authority::UpdateAuthority)) || authorities.contains(&Authority::Pubkey { address: *ctx.authority.key, diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index d6c1390f..bac98b4e 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -32,15 +32,14 @@ impl PluginType { pub fn check_create(&self) -> CheckResult { #[allow(clippy::match_single_binding)] match self { - PluginType::Collection => CheckResult::CanReject, _ => CheckResult::None, } } /// Check if a plugin is permitted to approve or deny an update action. pub fn check_update(&self) -> CheckResult { + #[allow(clippy::match_single_binding)] match self { - PluginType::Collection => CheckResult::CanApprove, _ => CheckResult::None, } } @@ -96,7 +95,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_create(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_create(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_create(ctx, args, authorities), - Plugin::Collection(collection) => collection.validate_create(ctx, args, authorities), Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_create(ctx, args, authorities) } @@ -116,7 +114,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_update(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_update(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_update(ctx, args, authorities), - Plugin::Collection(collection) => collection.validate_update(ctx, args, authorities), Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_update(ctx, args, authorities) } @@ -142,9 +139,6 @@ impl Plugin { Plugin::Transfer(transfer) => { transfer.validate_update_plugin(asset, ctx, args, authorities) } - Plugin::Collection(collection) => { - collection.validate_update_plugin(asset, ctx, args, authorities) - } Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_update_plugin(asset, ctx, args, authorities) } @@ -164,7 +158,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_burn(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_burn(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_burn(ctx, args, authorities), - Plugin::Collection(collection) => collection.validate_burn(ctx, args, authorities), Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_burn(ctx, args, authorities) } @@ -184,7 +177,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_transfer(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_transfer(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_transfer(ctx, args, authorities), - Plugin::Collection(collection) => collection.validate_transfer(ctx, args, authorities), Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_transfer(ctx, args, authorities) } @@ -204,7 +196,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_compress(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_compress(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_compress(ctx, args, authorities), - Plugin::Collection(collection) => collection.validate_compress(ctx, args, authorities), Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_compress(ctx, args, authorities) } @@ -224,9 +215,6 @@ impl Plugin { Plugin::Freeze(freeze) => freeze.validate_decompress(ctx, args, authorities), Plugin::Burn(burn) => burn.validate_decompress(ctx, args, authorities), Plugin::Transfer(transfer) => transfer.validate_decompress(ctx, args, authorities), - Plugin::Collection(collection) => { - collection.validate_decompress(ctx, args, authorities) - } Plugin::UpdateDelegate(update_delegate) => { update_delegate.validate_decompress(ctx, args, authorities) } @@ -273,7 +261,7 @@ pub trait PluginValidation { authorities: &[Authority], ) -> Result { if (ctx.authority.key == &asset.owner && authorities.contains(&Authority::Owner)) - || (ctx.authority.key == &asset.update_authority + || (ctx.authority.key == &asset.update_authority.key() && authorities.contains(&Authority::UpdateAuthority)) || authorities.contains(&Authority::Pubkey { address: *ctx.authority.key, diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index 2fb92a73..e98a6912 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -1,5 +1,4 @@ mod burn; -mod collection; mod freeze; mod lifecycle; mod plugin_header; @@ -10,7 +9,6 @@ mod update_delegate; mod utils; pub use burn::*; -pub use collection::*; pub use freeze::*; pub use lifecycle::*; pub use plugin_header::*; @@ -45,8 +43,6 @@ pub enum Plugin { Burn(Burn), /// Transfer plugin. Transfer(Transfer), - /// Collection plugin. - Collection(Collection), /// Update Delegate plugin. UpdateDelegate(UpdateDelegate), } @@ -60,7 +56,6 @@ impl Plugin { Plugin::Freeze(_) => Ok(Authority::Owner), Plugin::Burn(_) => Ok(Authority::Owner), Plugin::Transfer(_) => Ok(Authority::Owner), - Plugin::Collection(_) => Ok(Authority::UpdateAuthority), Plugin::UpdateDelegate(_) => Ok(Authority::UpdateAuthority), } } @@ -82,8 +77,6 @@ pub enum PluginType { Burn, /// Transfer plugin. Transfer, - /// Collection plugin. - Collection, /// Update Delegate plugin. UpdateDelegate, } @@ -106,7 +99,6 @@ impl From<&Plugin> for PluginType { Plugin::Freeze(_) => PluginType::Freeze, Plugin::Burn(_) => PluginType::Burn, Plugin::Transfer(_) => PluginType::Transfer, - Plugin::Collection(_) => PluginType::Collection, Plugin::UpdateDelegate(_) => PluginType::UpdateDelegate, } } diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 3b73879e..66ef4b0e 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -9,10 +9,12 @@ use crate::{ error::MplCoreError, instruction::accounts::CreateAccounts, plugins::{create_meta_idempotent, initialize_plugin, CheckResult, Plugin, ValidationResult}, - state::{Asset, Compressible, DataState, HashedAsset, Key}, + state::{Asset, Compressible, DataState, HashedAsset, Key, UpdateAuthority}, utils::fetch_core_data, }; +use super::update; + #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct CreateArgs { @@ -35,13 +37,23 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P return Err(MplCoreError::InvalidSystemProgram.into()); } + let update_authority = match ctx.accounts.collection { + Some(collection) => UpdateAuthority::Collection(*collection.key), + None => UpdateAuthority::Address( + *ctx.accounts + .update_authority + .unwrap_or(ctx.accounts.payer) + .key, + ), + }; + + if update_authority.validate_create(&ctx.accounts, &args)? == ValidationResult::Rejected { + return Err(MplCoreError::InvalidAuthority.into()); + } + let new_asset = Asset { key: Key::Asset, - update_authority: *ctx - .accounts - .update_authority - .unwrap_or(ctx.accounts.payer) - .key, + update_authority, owner: *ctx .accounts .owner diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 69d0338e..3278827b 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -8,7 +8,7 @@ use crate::{ error::MplCoreError, instruction::accounts::UpdateAccounts, plugins::{CheckResult, Plugin, RegistryRecord, ValidationResult}, - state::{Asset, DataBlob, SolanaAccount}, + state::{Asset, DataBlob, SolanaAccount, UpdateAuthority}, utils::fetch_core_data, }; @@ -71,7 +71,7 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P let mut dirty = false; if let Some(new_update_authority) = ctx.accounts.new_update_authority { - asset.update_authority = *new_update_authority.key; + asset.update_authority = UpdateAuthority::Address(*new_update_authority.key); dirty = true; } if let Some(new_name) = &args.new_name { diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index 03575b04..eef55bf9 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -14,17 +14,17 @@ use crate::{ }, }; -use super::CoreAsset; +use super::{CoreAsset, UpdateAuthority}; /// The Core Asset structure that exists at the beginning of every asset account. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount, Eq, PartialEq)] pub struct Asset { /// The account discriminator. pub key: Key, //1 - /// The update authority of the asset. - pub update_authority: Pubkey, //32 /// The owner of the asset. pub owner: Pubkey, //32 + /// The update authority of the asset. + pub update_authority: UpdateAuthority, //33 /// The name of the asset. pub name: String, //4 /// The URI of the asset that points to the off-chain data. @@ -33,7 +33,7 @@ pub struct Asset { impl Asset { /// The base length of the asset account with an empty name and uri. - pub const BASE_LENGTH: usize = 1 + 32 + 32 + 4 + 4; + pub const BASE_LENGTH: usize = 1 + 32 + 33 + 4 + 4; /// Check that a compression proof results in same on-chain hash. pub fn verify_proof( @@ -71,7 +71,7 @@ impl Asset { /// Validate the update lifecycle event. pub fn validate_update(&self, ctx: &UpdateAccounts) -> Result { - if ctx.authority.key == &self.update_authority { + if ctx.authority.key == &self.update_authority.key() { Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -155,8 +155,8 @@ impl From for Asset { } impl CoreAsset for Asset { - fn update_authority(&self) -> &Pubkey { - &self.update_authority + fn update_authority(&self) -> UpdateAuthority { + self.update_authority.clone() } fn owner(&self) -> &Pubkey { diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index 171d9a87..ebac6745 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; -use super::{CoreAsset, DataBlob, Key, SolanaAccount}; +use super::{CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority}; /// The representation of a collection of assets. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] @@ -61,8 +61,8 @@ impl SolanaAccount for CollectionData { } impl CoreAsset for CollectionData { - fn update_authority(&self) -> &Pubkey { - &self.update_authority + fn update_authority(&self) -> UpdateAuthority { + UpdateAuthority::Collection(self.update_authority) } fn owner(&self) -> &Pubkey { diff --git a/programs/mpl-core/src/state/mod.rs b/programs/mpl-core/src/state/mod.rs index 84c38523..6fcbd419 100644 --- a/programs/mpl-core/src/state/mod.rs +++ b/programs/mpl-core/src/state/mod.rs @@ -13,6 +13,9 @@ pub use traits::*; mod collection; pub use collection::*; +mod update_authority; +pub use update_authority::*; + use borsh::{BorshDeserialize, BorshSerialize}; use num_derive::{FromPrimitive, ToPrimitive}; use solana_program::pubkey::Pubkey; @@ -108,10 +111,10 @@ impl Key { pub struct CompressionProof { /// The discriminator of the account. pub key: Key, //1 - /// The update authority of the asset. - pub update_authority: Pubkey, //32 /// The owner of the asset. pub owner: Pubkey, //32 + /// The update authority of the asset. + pub update_authority: UpdateAuthority, //33 /// The name of the asset. pub name: String, //4 /// The URI of the asset that points to the off-chain data. diff --git a/programs/mpl-core/src/state/traits.rs b/programs/mpl-core/src/state/traits.rs index 97552db8..76fe8b4e 100644 --- a/programs/mpl-core/src/state/traits.rs +++ b/programs/mpl-core/src/state/traits.rs @@ -5,6 +5,8 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +use super::UpdateAuthority; + /// A trait for generic blobs of data that have size. pub trait DataBlob: BorshSerialize + BorshDeserialize { /// Get the size of an empty instance of the data blob. @@ -60,7 +62,7 @@ pub trait Compressible: BorshSerialize + BorshDeserialize { /// A trait for core assets. pub trait CoreAsset { /// Get the update authority of the asset. - fn update_authority(&self) -> &Pubkey; + fn update_authority(&self) -> UpdateAuthority; /// Get the owner of the asset. fn owner(&self) -> &Pubkey; diff --git a/programs/mpl-core/src/state/update_authority.rs b/programs/mpl-core/src/state/update_authority.rs new file mode 100644 index 00000000..f1208c13 --- /dev/null +++ b/programs/mpl-core/src/state/update_authority.rs @@ -0,0 +1,141 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; + +use crate::{ + error::MplCoreError, + instruction::accounts::{ + BurnAccounts, CompressAccounts, CreateAccounts, DecompressAccounts, TransferAccounts, + UpdateAccounts, + }, + plugins::{fetch_plugin, CheckResult, PluginType, ValidationResult}, + processor::CreateArgs, + state::{Authority, CollectionData, SolanaAccount}, + utils::assert_collection_authority, +}; + +/// An enum representing the types of accounts that can update data on an asset. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] +pub enum UpdateAuthority { + /// No update authority, used for immutability. + None, + /// A standard address or PDA. + Address(Pubkey), + /// Authority delegated to a collection. + Collection(Pubkey), +} + +impl UpdateAuthority { + /// Get the address of the update authority. + pub fn key(&self) -> Pubkey { + match self { + Self::None => Pubkey::default(), + Self::Address(address) => *address, + Self::Collection(address) => *address, + } + } + + /// Check permissions for the create lifecycle event. + pub fn check_create() -> CheckResult { + CheckResult::CanReject + } + + /// Check permissions for the update lifecycle event. + pub fn check_update() -> CheckResult { + CheckResult::CanApprove + } + + /// Validate the create lifecycle event. + pub fn validate_create( + &self, + ctx: &CreateAccounts, + _args: &CreateArgs, + ) -> Result { + match (ctx.collection, self) { + // If you're trying to add a collection, then check the authority. + (Some(collection_info), UpdateAuthority::Collection(collection_address)) => { + if collection_info.key != collection_address { + return Err(MplCoreError::InvalidCollection.into()); + } + let collection = CollectionData::load(collection_info, 0)?; + solana_program::msg!("Collection: {:?}", collection); + + assert_signer(ctx.payer)?; + + let maybe_update_delegate = + fetch_plugin(collection_info, PluginType::UpdateDelegate); + + if let Ok((mut authorities, _, _)) = maybe_update_delegate { + authorities.push(Authority::UpdateAuthority); + if assert_collection_authority(&collection, ctx.payer, &authorities).is_err() { + return Ok(ValidationResult::Rejected); + } + } else if ctx.payer.key != &collection.update_authority { + return Ok(ValidationResult::Rejected); + } + + Ok(ValidationResult::Pass) + } + // If you're not trying add a collection, then just pass. + (_, UpdateAuthority::Address(_)) => Ok(ValidationResult::Pass), + // Otherwise reject because you're doing something weird. + _ => Ok(ValidationResult::Rejected), + } + + // let authority = match self { + // Self::None => return Ok(ValidationResult::Rejected), + // Self::Address(address) => address, + // Self::Collection(address) => address, + // }; + + // if ctx.authority.key == authority { + // Ok(ValidationResult::Approved) + // } else { + // Ok(ValidationResult::Pass) + // } + } + + /// Validate the update lifecycle event. + pub fn validate_update(&self, ctx: &UpdateAccounts) -> Result { + let authority = match self { + Self::None => return Ok(ValidationResult::Rejected), + Self::Address(address) => address, + Self::Collection(address) => address, + }; + + if ctx.authority.key == authority { + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + + /// Validate the burn lifecycle event. + pub fn validate_burn(&self, _ctx: &BurnAccounts) -> Result { + Ok(ValidationResult::Pass) + } + + /// Validate the transfer lifecycle event. + pub fn validate_transfer( + &self, + ctx: &TransferAccounts, + ) -> Result { + Ok(ValidationResult::Pass) + } + + /// Validate the compress lifecycle event. + pub fn validate_compress( + &self, + _ctx: &CompressAccounts, + ) -> Result { + Ok(ValidationResult::Pass) + } + + /// Validate the decompress lifecycle event. + pub fn validate_decompress( + &self, + _ctx: &DecompressAccounts, + ) -> Result { + Ok(ValidationResult::Pass) + } +} diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 173ea5ed..84890164 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -35,7 +35,7 @@ pub fn assert_authority( } } Authority::UpdateAuthority => { - if asset.update_authority() == authority.key { + if asset.update_authority().key() == *authority.key { return Ok(()); } }