From 49a780bc2f480f2528ef4160193beb4a2558c2aa Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Fri, 8 Mar 2024 13:37:10 -0800 Subject: [PATCH] create js test helper functions --- .../instructions/createCollection.ts | 9 +- clients/js/test/_setup.ts | 190 +++++++++++++++++- clients/js/test/create.test.ts | 25 +-- clients/js/test/createCollection.test.ts | 87 +++----- .../instructions/create_collection.rs | 65 +----- idls/mpl_core.json | 9 - programs/mpl-core/src/instruction.rs | 3 +- 7 files changed, 230 insertions(+), 158 deletions(-) diff --git a/clients/js/src/generated/instructions/createCollection.ts b/clients/js/src/generated/instructions/createCollection.ts index 33eedce4..9b611208 100644 --- a/clients/js/src/generated/instructions/createCollection.ts +++ b/clients/js/src/generated/instructions/createCollection.ts @@ -37,8 +37,6 @@ export type CreateCollectionInstructionAccounts = { updateAuthority?: PublicKey | Pda; /** The account paying for the storage fees */ payer?: Signer; - /** The owner of the new asset. Defaults to the authority if not present. */ - owner?: PublicKey | Pda; /** The system program */ systemProgram?: PublicKey | Pda; }; @@ -114,13 +112,8 @@ export function createCollection( isWritable: true as boolean, value: input.payer ?? null, }, - owner: { - index: 3, - isWritable: false as boolean, - value: input.owner ?? null, - }, systemProgram: { - index: 4, + index: 3, isWritable: false as boolean, value: input.systemProgram ?? null, }, diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index 14055824..7f0f82d7 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -1,5 +1,193 @@ /* eslint-disable import/no-extraneous-dependencies */ import { createUmi as basecreateUmi } from '@metaplex-foundation/umi-bundle-tests'; -import { mplCore } from '../src'; +import { Assertions } from 'ava'; +import { PublicKey, Signer, Umi, generateSigner, publicKey } from '@metaplex-foundation/umi'; +import { + Collection, + DataState, + Key, + create, + fetchAssetWithPlugins, + fetchCollectionWithPlugins, + mplCore, + PluginArgs, + createCollection as baseCreateCollection, + CollectionWithPlugins, + AssetWithPlugins, + UpdateAuthority, + Plugin, +} from '../src'; export const createUmi = async () => (await basecreateUmi()).use(mplCore()); + +export type CreateAssetHelperArgs = { + owner?: PublicKey | Signer; + payer?: Signer; + asset?: Signer + dataState?: DataState; + name?: string; + uri?: string; + updateAuthority?: PublicKey | Signer + collection?: PublicKey + // TODO use PluginList type here + plugins?: PluginArgs[] +} + +export const DEFAULT_ASSET = { + name: 'Test Asset', + uri: 'https://example.com/asset', +} + +export const DEFAULT_COLLECTION = { + name: 'Test Collection', + uri: 'https://example.com/collection', +} + +export const createAsset = async ( + umi: Umi, + input: CreateAssetHelperArgs +) => { + const payer = input.payer || umi.identity; + const owner = publicKey(input.owner || input.payer || umi.identity); + const asset = input.asset || generateSigner(umi) + const updateAuthority = publicKey(input.updateAuthority || payer) + await create(umi, { + owner, + payer, + dataState: input.dataState || DataState.AccountState, + asset, + updateAuthority, + name: input.name || DEFAULT_ASSET.name, + uri: input.uri || DEFAULT_ASSET.uri, + plugins: input.plugins || [], + collection: input.collection, + }).sendAndConfirm(umi); + + return fetchAssetWithPlugins(umi, publicKey(asset)); +}; + +export type CreateCollectionHelperArgs = { + payer?: Signer; + collection?: Signer + name?: string; + uri?: string; + updateAuthority?: PublicKey | Signer + // TODO use CollectionPluginList type here + plugins?: PluginArgs[] +} + +export const createCollection = async (umi: Umi, input:CreateCollectionHelperArgs) => { + const payer = input.payer || umi.identity; + const collection = input.collection || generateSigner(umi); + const updateAuthority = publicKey(input.updateAuthority || payer); + await baseCreateCollection(umi, { + ...DEFAULT_COLLECTION, + collection, + payer, + updateAuthority, + plugins: input.plugins || [] + }).sendAndConfirm(umi); + + return fetchCollectionWithPlugins(umi, publicKey(collection)); +} + +export const createAssetWithCollection: ( + umi: Umi, + assetInput: CreateAssetHelperArgs & { collection?: Collection }, + collectionInput?: CreateCollectionHelperArgs +) => Promise<{ asset: AssetWithPlugins; collection: CollectionWithPlugins }> = async (umi, assetInput, collectionInput = {}) => { + const collection = assetInput.collection ? await fetchCollectionWithPlugins(umi, assetInput.collection.publicKey) : await createCollection(umi, { + payer: assetInput.payer, + updateAuthority: assetInput.updateAuthority, + ...collectionInput + }); + + const asset = await createAsset(umi, {...assetInput, collection: collection.publicKey}); + + return { + asset, + collection + } +}; + +export const assertAsset = async ( + t: Assertions, + umi: Umi, + input: { + asset: PublicKey | Signer; + owner: PublicKey | Signer; + updateAuthority: UpdateAuthority; + name?: string | RegExp; + uri?: string | RegExp; + // TODO replace with remapped PluginList type + plugins?: Plugin[] + } +) => { + const assetAddress = publicKey(input.asset); + const owner = publicKey(input.owner); + const { name, uri, updateAuthority } = input; + const asset = await fetchAssetWithPlugins(umi, assetAddress); + + // Name. + if (typeof name === 'string') t.is(asset.name, name); + else if (name !== undefined) t.regex(asset.name, name); + + // Uri. + if (typeof uri === 'string') t.is(asset.uri, uri); + else if (uri !== undefined) t.regex(asset.uri, uri); + + t.like(asset, { + key: Key.Asset, + publicKey: assetAddress, + owner, + updateAuthority, + // TODO test plugins + }); + +}; + +export const assertCollection = async ( + t: Assertions, + umi: Umi, + input: { + collection: PublicKey | Signer; + updateAuthority: PublicKey | Signer; + name?: string | RegExp; + uri?: string | RegExp; + numMinted?: number; + currentSize?: number; + // TODO replace with remapped PluginList type + plugins?: Plugin[] + } +) => { + const collectionAddress = publicKey(input.collection); + const updateAuthority = publicKey(input.updateAuthority); + const { name, uri, numMinted, currentSize } = input; + const collection = await fetchCollectionWithPlugins(umi, collectionAddress); + + // Name. + if (typeof name === 'string') t.is(collection.name, name); + else if (name !== undefined) t.regex(collection.name, name); + + // Uri. + if (typeof uri === 'string') t.is(collection.uri, uri); + else if (uri !== undefined) t.regex(collection.uri, uri); + + const testObj = { + key: Key.Collection, + publicKey: collectionAddress, + updateAuthority, + // TODO test plugins + } + + if(numMinted !== undefined) { + testObj.numMinted = numMinted; + }; + + if(currentSize !== undefined) { + testObj.currentSize = currentSize; + } + + t.like(collection, testObj); + +}; diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index 5b601ee7..b80a2921 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -13,32 +13,23 @@ import { getAssetAccountDataSerializer, updateAuthority, } from '../src'; -import { createUmi } from './_setup'; +import { DEFAULT_ASSET, assertAsset, createAsset, createUmi } from './_setup'; test('it can create a new asset in account state', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const assetAddress = generateSigner(umi); - // When we create a new account. - await create(umi, { - dataState: DataState.AccountState, + await createAsset(umi, { asset: assetAddress, - name: 'Test Bread', - uri: 'https://example.com/bread', - plugins: [], - }).sendAndConfirm(umi); + }) - // Then an account was created with the correct data. - const asset = await fetchAsset(umi, assetAddress.publicKey); - // console.log("Account State:", asset); - t.like(asset, { - publicKey: assetAddress.publicKey, + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: assetAddress.publicKey, + owner: umi.identity, updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), - owner: umi.identity.publicKey, - name: 'Test Bread', - uri: 'https://example.com/bread', - }); + }) }); test('it can create a new asset with a different payer', async (t) => { diff --git a/clients/js/test/createCollection.test.ts b/clients/js/test/createCollection.test.ts index 702c9727..2d6bd5f7 100644 --- a/clients/js/test/createCollection.test.ts +++ b/clients/js/test/createCollection.test.ts @@ -2,7 +2,6 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; import { AssetWithPlugins, - Collection, CollectionWithPlugins, DataState, PluginType, @@ -10,40 +9,28 @@ import { approveCollectionPluginAuthority, authority, create, - createCollection, fetchAssetWithPlugins, - fetchCollection, + createCollection as baseCreateCollection, fetchCollectionWithPlugins, plugin, updateAuthority, } from '../src'; -import { createUmi } from './_setup'; +import { DEFAULT_ASSET, DEFAULT_COLLECTION, assertAsset, assertCollection, createAssetWithCollection, createCollection, createUmi } from './_setup'; test('it can create a new collection', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const collectionAddress = generateSigner(umi); - // When we create a new account. await createCollection(umi, { collection: collectionAddress, - name: 'Test Bread Collection', - uri: 'https://example.com/bread', - plugins: [], - }).sendAndConfirm(umi); + }) - // Then an account was created with the correct data. - const collection = await fetchCollection( - umi, - collectionAddress.publicKey - ); - // console.log("Account State:", collection); - t.like(collection, { - publicKey: collectionAddress.publicKey, + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collectionAddress, updateAuthority: umi.identity.publicKey, - name: 'Test Bread Collection', - uri: 'https://example.com/bread', - }); + }) }); test('it can create a new collection with plugins', async (t) => { @@ -52,7 +39,7 @@ test('it can create a new collection with plugins', async (t) => { const collectionAddress = generateSigner(umi); // When we create a new account. - await createCollection(umi, { + await baseCreateCollection(umi, { collection: collectionAddress, name: 'Test Bread Collection', uri: 'https://example.com/bread', @@ -99,46 +86,24 @@ test('it can create a new collection with plugins', async (t) => { test('it can create a new asset with a collection', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); - const collectionAddress = generateSigner(umi); - const assetAddress = generateSigner(umi); - - // When we create a new account. - await createCollection(umi, { - collection: collectionAddress, - name: 'Test Bread Collection', - uri: 'https://example.com/bread', - plugins: [{ __kind: 'Freeze', fields: [{ frozen: false }] }], - }).sendAndConfirm(umi); - - // When we create a new account. - await create(umi, { - dataState: DataState.AccountState, - asset: assetAddress, - name: 'Test Bread', - uri: 'https://example.com/bread', - collection: collectionAddress.publicKey, - plugins: [], - }).sendAndConfirm(umi); + const { asset, collection} = await createAssetWithCollection(umi, { + }, { + plugins: [plugin('Freeze', [{ frozen: false }])], + }) + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + plugins: [plugin('Freeze', [{ frozen: false }])] + }) - // Then an account was created with the correct data. - const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey); - // console.log("Account State:", asset); - t.like(asset, { - publicKey: assetAddress.publicKey, - updateAuthority: updateAuthority('Collection', [ - collectionAddress.publicKey, - ]), + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, owner: umi.identity.publicKey, - name: 'Test Bread', - uri: 'https://example.com/bread', - pluginHeader: { - key: 3, - pluginRegistryOffset: BigInt(118), - }, - pluginRegistry: { - key: 4, - }, - }); + updateAuthority: updateAuthority('Collection', [collection.publicKey]), + }) t.assert(asset.pluginRegistry?.registry.length === 0); t.assert(asset.plugins?.length === 0); @@ -152,7 +117,7 @@ test('it can create a new asset with a collection with collection delegate', asy const delegate = generateSigner(umi); // When we create a new account. - await createCollection(umi, { + await baseCreateCollection(umi, { collection: collectionAddress, name: 'Test Bread Collection', uri: 'https://example.com/bread', @@ -219,7 +184,7 @@ test('it cannot create a new asset with a collection if it is not the collection const collectionAuth = generateSigner(umi); // When we create a new account. - await createCollection(umi, { + await baseCreateCollection(umi, { collection: collectionAddress, updateAuthority: collectionAuth.publicKey, name: 'Test Bread Collection', diff --git a/clients/rust/src/generated/instructions/create_collection.rs b/clients/rust/src/generated/instructions/create_collection.rs index 5e6eeb80..c3b095b2 100644 --- a/clients/rust/src/generated/instructions/create_collection.rs +++ b/clients/rust/src/generated/instructions/create_collection.rs @@ -17,8 +17,6 @@ pub struct CreateCollection { pub update_authority: Option, /// The account paying for the storage fees pub payer: solana_program::pubkey::Pubkey, - /// The owner of the new asset. Defaults to the authority if not present. - pub owner: Option, /// The system program pub system_program: solana_program::pubkey::Pubkey, } @@ -36,7 +34,7 @@ impl CreateCollection { args: CreateCollectionInstructionArgs, 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(4 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.collection, true, @@ -55,16 +53,6 @@ impl CreateCollection { accounts.push(solana_program::instruction::AccountMeta::new( self.payer, true, )); - if let Some(owner) = self.owner { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - owner, 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, @@ -108,14 +96,12 @@ pub struct CreateCollectionInstructionArgs { /// 0. `[writable, signer]` collection /// 1. `[optional]` update_authority /// 2. `[writable, signer]` payer -/// 3. `[optional]` owner -/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) #[derive(Default)] pub struct CreateCollectionBuilder { collection: Option, update_authority: Option, payer: Option, - owner: Option, system_program: Option, name: Option, uri: Option, @@ -149,13 +135,6 @@ impl CreateCollectionBuilder { self.payer = Some(payer); self } - /// `[optional account]` - /// The owner of the new asset. Defaults to the authority if not present. - #[inline(always)] - pub fn owner(&mut self, owner: Option) -> &mut Self { - self.owner = owner; - self - } /// `[optional account, default to '11111111111111111111111111111111']` /// The system program #[inline(always)] @@ -202,7 +181,6 @@ impl CreateCollectionBuilder { collection: self.collection.expect("collection is not set"), update_authority: self.update_authority, payer: self.payer.expect("payer is not set"), - owner: self.owner, system_program: self .system_program .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), @@ -225,8 +203,6 @@ pub struct CreateCollectionCpiAccounts<'a, 'b> { pub update_authority: 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 owner of the new asset. Defaults to the authority if not present. - pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, } @@ -241,8 +217,6 @@ pub struct CreateCollectionCpi<'a, 'b> { pub update_authority: 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 owner of the new asset. Defaults to the authority if not present. - pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program pub system_program: &'b solana_program::account_info::AccountInfo<'a>, /// The arguments for the instruction. @@ -260,7 +234,6 @@ impl<'a, 'b> CreateCollectionCpi<'a, 'b> { collection: accounts.collection, update_authority: accounts.update_authority, payer: accounts.payer, - owner: accounts.owner, system_program: accounts.system_program, __args: args, } @@ -298,7 +271,7 @@ impl<'a, 'b> CreateCollectionCpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.collection.key, true, @@ -318,16 +291,6 @@ impl<'a, 'b> CreateCollectionCpi<'a, 'b> { *self.payer.key, true, )); - if let Some(owner) = self.owner { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *owner.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, @@ -348,16 +311,13 @@ impl<'a, 'b> CreateCollectionCpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.collection.clone()); if let Some(update_authority) = self.update_authority { account_infos.push(update_authority.clone()); } account_infos.push(self.payer.clone()); - if let Some(owner) = self.owner { - account_infos.push(owner.clone()); - } account_infos.push(self.system_program.clone()); remaining_accounts .iter() @@ -378,8 +338,7 @@ impl<'a, 'b> CreateCollectionCpi<'a, 'b> { /// 0. `[writable, signer]` collection /// 1. `[optional]` update_authority /// 2. `[writable, signer]` payer -/// 3. `[optional]` owner -/// 4. `[]` system_program +/// 3. `[]` system_program pub struct CreateCollectionCpiBuilder<'a, 'b> { instruction: Box>, } @@ -391,7 +350,6 @@ impl<'a, 'b> CreateCollectionCpiBuilder<'a, 'b> { collection: None, update_authority: None, payer: None, - owner: None, system_program: None, name: None, uri: None, @@ -425,16 +383,6 @@ impl<'a, 'b> CreateCollectionCpiBuilder<'a, 'b> { self.instruction.payer = Some(payer); self } - /// `[optional account]` - /// The owner of the new asset. Defaults to the authority if not present. - #[inline(always)] - pub fn owner( - &mut self, - owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, - ) -> &mut Self { - self.instruction.owner = owner; - self - } /// The system program #[inline(always)] pub fn system_program( @@ -518,8 +466,6 @@ impl<'a, 'b> CreateCollectionCpiBuilder<'a, 'b> { payer: self.instruction.payer.expect("payer is not set"), - owner: self.instruction.owner, - system_program: self .instruction .system_program @@ -538,7 +484,6 @@ struct CreateCollectionCpiBuilderInstruction<'a, 'b> { collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, - owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, name: Option, uri: Option, diff --git a/idls/mpl_core.json b/idls/mpl_core.json index ac21832b..7fcc31df 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -116,15 +116,6 @@ "The account paying for the storage fees" ] }, - { - "name": "owner", - "isMut": false, - "isSigner": false, - "isOptional": true, - "docs": [ - "The owner of the new asset. Defaults to the authority if not present." - ] - }, { "name": "systemProgram", "isMut": false, diff --git a/programs/mpl-core/src/instruction.rs b/programs/mpl-core/src/instruction.rs index ff25b42e..2595c865 100644 --- a/programs/mpl-core/src/instruction.rs +++ b/programs/mpl-core/src/instruction.rs @@ -31,8 +31,7 @@ pub(crate) enum MplAssetInstruction { #[account(0, writable, signer, name="collection", desc = "The address of the new asset")] #[account(1, optional, name="update_authority", desc = "The authority of the new asset")] #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(3, optional, name="owner", desc = "The owner of the new asset. Defaults to the authority if not present.")] - #[account(4, name="system_program", desc = "The system program")] + #[account(3, name="system_program", desc = "The system program")] CreateCollection(CreateCollectionArgs), /// Add a plugin to an mpl-core.