diff --git a/clients/js/src/defaultGuards/freezeSolPayment.ts b/clients/js/src/defaultGuards/freezeSolPayment.ts index b51a455..872da55 100644 --- a/clients/js/src/defaultGuards/freezeSolPayment.ts +++ b/clients/js/src/defaultGuards/freezeSolPayment.ts @@ -1,5 +1,4 @@ import { - findAssociatedTokenPda, getSplSystemProgramId, } from '@metaplex-foundation/mpl-toolbox'; import { PublicKey, Signer } from '@metaplex-foundation/umi'; @@ -43,19 +42,10 @@ export const freezeSolPaymentGuardManifest: GuardManifest< candyGuard: mintContext.candyGuard, }); - // TODO actually freeze asset - const [nftAta] = findAssociatedTokenPda(context, { - mint: mintContext.asset, - owner: mintContext.minter.publicKey, - }); return { data: new Uint8Array(), remainingAccounts: [ { publicKey: freezeEscrow, isWritable: true }, - { publicKey: nftAta, isWritable: false }, - ...(args.nftRuleSet - ? [{ publicKey: args.nftRuleSet, isWritable: false }] - : []), ], }; }, @@ -81,8 +71,6 @@ export type FreezeSolPaymentMintArgs = Omit< FreezeSolPaymentArgs, 'lamports' > & { - /** The ruleSet of the minted NFT, if any. */ - nftRuleSet?: PublicKey; }; /** @@ -143,9 +131,8 @@ export type FreezeSolPaymentRouteArgsInitialize = Omit< * routeArgs: { * path: 'thaw', * destination, - * nftMint, - * nftOwner, - * nftTokenStandard: candyMachine.tokenStandard, + * asset, + * collection, * }, * }); * ``` @@ -161,7 +148,7 @@ export type FreezeSolPaymentRouteArgsThaw = Omit< asset: PublicKey; /** The owner address of the NFT to thaw. */ - owner: PublicKey; + collection: PublicKey; }; /** @@ -227,7 +214,7 @@ const thawRouteInstruction: RouteParser = ( const remainingAccounts: GuardRemainingAccount[] = [ { publicKey: freezeEscrow, isWritable: true }, { publicKey: args.asset, isWritable: true }, - { publicKey: args.owner, isWritable: false }, + { publicKey: args.collection, isWritable: false }, { publicKey: getMplCoreProgramId(context), isWritable: false }, { publicKey: getSplSystemProgramId(context), isWritable: false }, ]; diff --git a/clients/js/src/defaultGuards/freezeTokenPayment.ts b/clients/js/src/defaultGuards/freezeTokenPayment.ts index b181dc4..0ebf8a0 100644 --- a/clients/js/src/defaultGuards/freezeTokenPayment.ts +++ b/clients/js/src/defaultGuards/freezeTokenPayment.ts @@ -45,11 +45,6 @@ export const freezeTokenPaymentGuardManifest: GuardManifest< candyGuard: mintContext.candyGuard, }); - // TODO actually freeze asset - const [nftAta] = findAssociatedTokenPda(context, { - mint: mintContext.asset, - owner: mintContext.minter.publicKey, - }); const [tokenAddress] = findAssociatedTokenPda(context, { mint: args.mint, owner: mintContext.minter.publicKey, @@ -62,12 +57,8 @@ export const freezeTokenPaymentGuardManifest: GuardManifest< data: new Uint8Array(), remainingAccounts: [ { publicKey: freezeEscrow, isWritable: true }, - { publicKey: nftAta, isWritable: false }, { publicKey: tokenAddress, isWritable: true }, { publicKey: freezeAta, isWritable: true }, - ...(args.nftRuleSet - ? [{ publicKey: args.nftRuleSet, isWritable: false }] - : []), ], }; }, @@ -93,8 +84,6 @@ export type FreezeTokenPaymentMintArgs = Omit< FreezeTokenPaymentArgs, 'amount' > & { - /** The ruleSet of the minted NFT, if any. */ - nftRuleSet?: PublicKey; }; /** @@ -157,9 +146,8 @@ export type FreezeTokenPaymentRouteArgsInitialize = Omit< * path: 'thaw', * mint: tokenMint.publicKey, * destinationAta, - * nftMint, - * nftOwner, - * nftTokenStandard: candyMachine.tokenStandard, + * asset, + * collection, * }, * }); * ``` @@ -175,7 +163,7 @@ export type FreezeTokenPaymentRouteArgsThaw = Omit< asset: PublicKey; /** The owner address of the NFT to thaw. */ - owner: PublicKey; + collection: PublicKey; }; /** @@ -250,7 +238,7 @@ const thawRouteInstruction: RouteParser = ( const remainingAccounts: GuardRemainingAccount[] = [ { publicKey: freezeEscrow, isWritable: true }, { publicKey: args.asset, isWritable: true }, - { publicKey: args.owner, isWritable: false }, + { publicKey: args.collection, isWritable: false }, { publicKey: getMplCoreProgramId(context), isWritable: false }, { publicKey: getSplSystemProgramId(context), isWritable: false }, ]; diff --git a/clients/js/src/generated/errors/mplCandyMachineCore.ts b/clients/js/src/generated/errors/mplCandyMachineCore.ts index a63342b..c1ad615 100644 --- a/clients/js/src/generated/errors/mplCandyMachineCore.ts +++ b/clients/js/src/generated/errors/mplCandyMachineCore.ts @@ -480,6 +480,19 @@ export class CmInvalidAccountVersionError extends ProgramError { codeToErrorMap.set(0x178d, CmInvalidAccountVersionError); nameToErrorMap.set('InvalidAccountVersion', CmInvalidAccountVersionError); +/** IncorrectPluginAuthority: Invalid plugin authority */ +export class CmIncorrectPluginAuthorityError extends ProgramError { + readonly name: string = 'IncorrectPluginAuthority'; + + readonly code: number = 0x178e; // 6030 + + constructor(program: Program, cause?: Error) { + super('Invalid plugin authority', program, cause); + } +} +codeToErrorMap.set(0x178e, CmIncorrectPluginAuthorityError); +nameToErrorMap.set('IncorrectPluginAuthority', CmIncorrectPluginAuthorityError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index ec294f9..c881a18 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -93,7 +93,7 @@ export const createCollection = async ( ): Promise => { const mint = generateSigner(umi); await baseCreateCollection(umi, { - collectionAddress: mint, + collection: mint, ...defaultAssetData(), ...input, }).sendAndConfirm(umi); diff --git a/clients/js/test/defaultGuards/freezeSolPayment.test.ts b/clients/js/test/defaultGuards/freezeSolPayment.test.ts index c903acb..a4d4c9a 100644 --- a/clients/js/test/defaultGuards/freezeSolPayment.test.ts +++ b/clients/js/test/defaultGuards/freezeSolPayment.test.ts @@ -95,7 +95,7 @@ test('it transfers SOL to an escrow account and freezes the NFT', async (t) => { t.is(isFrozen(asset), true, 'NFT is frozen'); // And cannot be thawed since not all NFTs have been minted. - const promise = thawNft(umi, candyMachine, destination, mint.publicKey); + const promise = thawNft(umi, candyMachine, destination, mint.publicKey, collection); await t.throwsAsync(promise, { message: /ThawNotEnabled/ }); // And the treasury escrow received SOLs. @@ -236,7 +236,7 @@ test('it can thaw an NFT once all NFTs are minted', async (t) => { t.is(isFrozen(asset), true, 'NFT is frozen'); // When we thaw the NFT. - await thawNft(umi, candyMachine, destination, mint.publicKey); + await thawNft(umi, candyMachine, destination, mint.publicKey, collection); // Then the NFT is thawed. asset = await fetchAssetWithPlugins(umi, mint.publicKey); @@ -259,7 +259,7 @@ test('it can unlock funds once all NFTs have been thawed', async (t) => { // And given all NFTs have been minted and thawed. const mint = await mintNft(umi, candyMachine, destination, collection); - await thawNft(umi, candyMachine, destination, mint.publicKey); + await thawNft(umi, candyMachine, destination, mint.publicKey, collection); // When the authority unlocks the funds. await transactionBuilder() @@ -472,15 +472,15 @@ test('it can have multiple freeze escrow and reuse the same ones', async (t) => ]); }; await assertFrozenCounts(2, 1); - await thawNft(umi, cm, destinationAB, mintD.publicKey, 'GROUPA'); // Not frozen. + await thawNft(umi, cm, destinationAB, mintD.publicKey, collection, 'GROUPA'); // Not frozen. await assertFrozenCounts(2, 1); // No change. - await thawNft(umi, cm, destinationAB, mintA.publicKey, 'GROUPA'); + await thawNft(umi, cm, destinationAB, mintA.publicKey, collection, 'GROUPA'); await assertFrozenCounts(1, 1); // AB decreased. - await thawNft(umi, cm, destinationAB, mintA.publicKey, 'GROUPA'); // Already thawed. + await thawNft(umi, cm, destinationAB, mintA.publicKey, collection, 'GROUPA'); // Already thawed. await assertFrozenCounts(1, 1); // No change. - await thawNft(umi, cm, destinationAB, mintB.publicKey, 'GROUPB'); + await thawNft(umi, cm, destinationAB, mintB.publicKey, collection, 'GROUPB'); await assertFrozenCounts(0, 1); // AB decreased. - await thawNft(umi, cm, destinationC, mintC.publicKey, 'GROUPC'); + await thawNft(umi, cm, destinationC, mintC.publicKey, collection, 'GROUPC'); await assertFrozenCounts(0, 0); // C decreased. // And when the authority unlocks the funds of both freeze escrows. @@ -683,7 +683,7 @@ test('it transfers SOL to an escrow account and locks the Programmable NFT', asy t.is(isFrozen(asset), true); // And cannot be thawed since not all NFTs have been minted. - const promise = thawNft(umi, candyMachine, destination, mint.publicKey); + const promise = thawNft(umi, candyMachine, destination, mint.publicKey, collection); await t.throwsAsync(promise, { message: /ThawNotEnabled/ }); // And the treasury escrow received SOLs. @@ -765,7 +765,7 @@ test('it can thaw a Programmable NFT once all NFTs are minted', async (t) => { routeArgs: { path: 'thaw', asset: mint.publicKey, - owner: umi.identity.publicKey, + collection, destination, }, }) @@ -874,8 +874,8 @@ const thawNft = async ( candyMachine: PublicKey, destination: PublicKey, asset: PublicKey, + collection: PublicKey, group?: string, - nftOwner?: PublicKey ) => { await route(umi, { candyMachine, @@ -884,7 +884,7 @@ const thawNft = async ( routeArgs: { path: 'thaw', asset, - owner: nftOwner ?? umi.identity.publicKey, + collection, destination, }, }).sendAndConfirm(umi); diff --git a/clients/js/test/defaultGuards/freezeTokenPayment.test.ts b/clients/js/test/defaultGuards/freezeTokenPayment.test.ts index f5a464d..3e1cd98 100644 --- a/clients/js/test/defaultGuards/freezeTokenPayment.test.ts +++ b/clients/js/test/defaultGuards/freezeTokenPayment.test.ts @@ -113,7 +113,7 @@ test('it transfers tokens to an escrow account and freezes the NFT', async (t) = // And cannot be thawed since not all NFTs have been minted. const cm = candyMachine; - const promise = thawNft(umi, cm, tokenMint, destinationAta, mint.publicKey); + const promise = thawNft(umi, cm, tokenMint, destinationAta, mint.publicKey, collection); await t.throwsAsync(promise, { message: /ThawNotEnabled/ }); // And the treasury escrow received tokens. @@ -234,7 +234,7 @@ test('it can thaw an NFT once all NFTs are minted', async (t) => { t.is(isFrozen(asset), true); // When we thaw the NFT. - await thawNft(umi, candyMachine, tokenMint, destinationAta, mint.publicKey); + await thawNft(umi, candyMachine, tokenMint, destinationAta, mint.publicKey, collection); // Then the NFT is thawed. asset = await fetchAssetWithPlugins(umi, mint.publicKey); @@ -275,7 +275,7 @@ test('it can unlock funds once all NFTs have been thawed', async (t) => { destinationAta, collection ); - await thawNft(umi, candyMachine, tokenMint, destinationAta, mint.publicKey); + await thawNft(umi, candyMachine, tokenMint, destinationAta, mint.publicKey, collection); // When the authority unlocks the funds. await transactionBuilder() @@ -531,15 +531,15 @@ test('it can have multiple freeze escrow and reuse the same ones', async (t) => ]); }; await assertFrozenCounts(2, 1); - await thawNft(umi, cm, mintAB, destinationAtaAB, nftD.publicKey, 'GROUPA'); // Not frozen. + await thawNft(umi, cm, mintAB, destinationAtaAB, nftD.publicKey, collection, 'GROUPA'); // Not frozen. await assertFrozenCounts(2, 1); // No change. - await thawNft(umi, cm, mintAB, destinationAtaAB, nftA.publicKey, 'GROUPA'); + await thawNft(umi, cm, mintAB, destinationAtaAB, nftA.publicKey, collection, 'GROUPA'); await assertFrozenCounts(1, 1); // AB decreased. - await thawNft(umi, cm, mintAB, destinationAtaAB, nftA.publicKey, 'GROUPA'); // Already thawed. + await thawNft(umi, cm, mintAB, destinationAtaAB, nftA.publicKey, collection, 'GROUPA'); // Already thawed. await assertFrozenCounts(1, 1); // No change. - await thawNft(umi, cm, mintAB, destinationAtaAB, nftB.publicKey, 'GROUPB'); + await thawNft(umi, cm, mintAB, destinationAtaAB, nftB.publicKey, collection, 'GROUPB'); await assertFrozenCounts(0, 1); // AB decreased. - await thawNft(umi, cm, mintC, destinationAtaC, nftC.publicKey, 'GROUPC'); + await thawNft(umi, cm, mintC, destinationAtaC, nftC.publicKey, collection, 'GROUPC'); await assertFrozenCounts(0, 0); // C decreased. // And when the authority unlocks the funds of both freeze escrows. @@ -809,7 +809,8 @@ test('it transfers tokens to an escrow account and locks the Programmable NFT', candyMachine, tokenMint, destinationAta, - mint.publicKey + mint.publicKey, + collection ); await t.throwsAsync(promise, { message: /ThawNotEnabled/ }); @@ -896,7 +897,7 @@ test('it can thaw a Programmable NFT once all NFTs are minted', async (t) => { routeArgs: { path: 'thaw', asset: mint.publicKey, - owner: umi.identity.publicKey, + collection, mint: tokenMint.publicKey, destinationAta, }, @@ -1029,8 +1030,8 @@ const thawNft = async ( tokenMint: PublicKey | Signer, destinationAta: PublicKey, asset: PublicKey, + collection: PublicKey, group?: string, - nftOwner?: PublicKey ) => { await route(umi, { candyMachine, @@ -1039,7 +1040,7 @@ const thawNft = async ( routeArgs: { path: 'thaw', asset, - owner: nftOwner ?? umi.identity.publicKey, + collection, mint: publicKey(tokenMint), destinationAta, }, diff --git a/clients/js/test/setCollectionV2.test.ts b/clients/js/test/setCollectionV2.test.ts index a868d46..d91def7 100644 --- a/clients/js/test/setCollectionV2.test.ts +++ b/clients/js/test/setCollectionV2.test.ts @@ -31,6 +31,7 @@ test('it can update the collection of a candy machine v2', async (t) => { const collectionB = await createCollection(umi, { updateAuthority: collectionUpdateAuthorityB.publicKey, }); + await setCollectionV2(umi, { candyMachine: candyMachine.publicKey, collection: collectionA.publicKey, diff --git a/configs/program-scripts/build.sh b/configs/program-scripts/build.sh index 57d6b26..74c0c1c 100755 --- a/configs/program-scripts/build.sh +++ b/configs/program-scripts/build.sh @@ -3,7 +3,8 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) OUTPUT="./programs/.bin" # saves external programs binaries to the output directory -source ${SCRIPT_DIR}/dump.sh ${OUTPUT} +# TODO remove this +# source ${SCRIPT_DIR}/dump.sh ${OUTPUT} # FIXME TODO Remove this cp ~/src/mpl-core/programs/.bin/mpl_core_program.so ${OUTPUT}/mpl_core_program.so diff --git a/idls/candy_machine_core.json b/idls/candy_machine_core.json index 814bff9..937079c 100644 --- a/idls/candy_machine_core.json +++ b/idls/candy_machine_core.json @@ -1001,6 +1001,11 @@ "code": 6029, "name": "InvalidAccountVersion", "msg": "Invalid account version" + }, + { + "code": 6030, + "name": "IncorrectPluginAuthority", + "msg": "Invalid plugin authority" } ], "metadata": { diff --git a/programs/candy-guard/program/src/guards/freeze_sol_payment.rs b/programs/candy-guard/program/src/guards/freeze_sol_payment.rs index c666cbf..cf75dcd 100644 --- a/programs/candy-guard/program/src/guards/freeze_sol_payment.rs +++ b/programs/candy-guard/program/src/guards/freeze_sol_payment.rs @@ -2,7 +2,14 @@ use super::*; use anchor_lang::AccountsClose; use mpl_candy_machine_core_asset::CandyMachine; -use mpl_core::{accounts::Asset, instructions::{AddAuthorityCpiBuilder, AddPluginCpiBuilder, RemoveAuthorityCpiBuilder, UpdatePluginCpiBuilder}, types::{Authority, Freeze, Plugin, PluginType}}; +use mpl_core::{ + accounts::Asset, + instructions::{ + AddPluginAuthorityCpiBuilder, AddPluginCpiBuilder, RemovePluginAuthorityCpiBuilder, + UpdatePluginCpiBuilder, + }, + types::{Authority, Freeze, Plugin, PluginType}, +}; use solana_program::{ program::{invoke, invoke_signed}, @@ -100,7 +107,7 @@ impl Guard for FreezeSolPayment { // 0. `[writable]` Freeze PDA to receive the funds (seeds `["freeze_escrow", // destination pubkey, candy guard pubkey, candy machine pubkey]`). // 1. `[writable]` Mint account for the Asset. - // 2. `[]` Address of the owner of the NFT. + // 2. `[]` Collection account for the Asset. // 3. `[]` MPL Core program ID. // 4. `[]` System program. FreezeInstruction::Thaw => { @@ -155,13 +162,6 @@ impl Condition for FreezeSolPayment { return err!(CandyGuardError::FreezeNotInitialized); } - // TOOD stop using this value - let nft_ata = try_get_account_info(ctx.accounts.remaining, index + 1)?; - ctx.account_cursor += 1; - - let candy_machine_info = ctx.accounts.candy_machine.to_account_info(); - let account_data = candy_machine_info.data.borrow_mut(); - ctx.indices.insert("freeze_sol_payment", index); if ctx.accounts.payer.lamports() < self.total_lamports() { @@ -327,7 +327,7 @@ pub fn freeze_nft( ]; let (_, bump) = Pubkey::find_program_address(&seeds, &crate::ID); - // TODO maybe remove this + // TODO maybe remove let signer = [ FreezeEscrow::PREFIX_SEED, destination.as_ref(), @@ -336,31 +336,40 @@ pub fn freeze_nft( &[bump], ]; - // TODO remove ata usage - let nft_ata = try_get_account_info(ctx.accounts.remaining, account_index + 1)?; - // approves a delegate to lock and transfer the token - AddPluginCpiBuilder::new(&ctx.accounts.mpl_core_program) - .asset_address(&ctx.accounts.asset) + .asset(&ctx.accounts.asset) + .collection(Some(&ctx.accounts.collection)) .authority(&ctx.accounts.minter) .payer(Some(&ctx.accounts.payer)) .system_program(&ctx.accounts.system_program) .plugin(Plugin::Freeze(Freeze { frozen: true })) .invoke()?; - AddAuthorityCpiBuilder::new(&ctx.accounts.mpl_core_program) + AddPluginAuthorityCpiBuilder::new(&ctx.accounts.mpl_core_program) .authority(&ctx.accounts.minter) - .asset_address(&ctx.accounts.asset) + .asset(&ctx.accounts.asset) + .collection(Some(&ctx.accounts.collection)) .new_authority(Authority::Pubkey { - address: freeze_pda.key() + address: freeze_pda.key(), }) .plugin_type(PluginType::Freeze) .payer(Some(&ctx.accounts.payer)) .system_program(&ctx.accounts.system_program) + .invoke()?; + + // remove owner from authorities when freezing + RemovePluginAuthorityCpiBuilder::new(&ctx.accounts.mpl_core_program) + .authority(&ctx.accounts.minter) + .asset(&ctx.accounts.asset) + .plugin_type(PluginType::Freeze) + .authority_to_remove(Authority::Pubkey { + address: owner.key(), + }) + .payer(Some(&ctx.accounts.payer)) + .system_program(&ctx.accounts.system_program) .invoke() .map_err(|error| error.into()) - } /// Helper function to initialize the freeze pda. @@ -485,7 +494,7 @@ pub fn thaw_nft<'info>( } let asset = try_get_account_info(ctx.remaining_accounts, 1)?; - let nft_owner = try_get_account_info(ctx.remaining_accounts, 2)?; + let collection = try_get_account_info(ctx.remaining_accounts, 2)?; let mpl_core_program = try_get_account_info(ctx.remaining_accounts, 3)?; let system_program = try_get_account_info(ctx.remaining_accounts, 4)?; @@ -514,7 +523,7 @@ pub fn thaw_nft<'info>( ]; let maybe_freeze_plugin = mpl_core::fetch_plugin::(&asset, PluginType::Freeze); - + let is_frozen = match maybe_freeze_plugin { Ok((_, freeze_plugin, _)) => freeze_plugin.frozen, _ => false, @@ -523,19 +532,21 @@ pub fn thaw_nft<'info>( if is_frozen { UpdatePluginCpiBuilder::new(mpl_core_program) .authority(freeze_pda) + .collection(Some(collection)) .payer(Some(&ctx.accounts.payer)) - .asset_address(asset) + .asset(asset) .plugin(Plugin::Freeze(Freeze { frozen: false })) .system_program(system_program) .invoke_signed(&[&signer])?; // TODO remove the plugin if there will be only the owner authority left? - RemoveAuthorityCpiBuilder::new(mpl_core_program) + RemovePluginAuthorityCpiBuilder::new(mpl_core_program) .authority(freeze_pda) - .asset_address(asset) + .collection(Some(collection)) + .asset(asset) .plugin_type(PluginType::Freeze) .authority_to_remove(Authority::Pubkey { - address: freeze_pda.key() + address: freeze_pda.key(), }) .payer(Some(&ctx.accounts.payer)) .system_program(system_program) diff --git a/programs/candy-guard/program/src/guards/freeze_token_payment.rs b/programs/candy-guard/program/src/guards/freeze_token_payment.rs index 3b729fe..56e0649 100644 --- a/programs/candy-guard/program/src/guards/freeze_token_payment.rs +++ b/programs/candy-guard/program/src/guards/freeze_token_payment.rs @@ -159,24 +159,10 @@ impl Guard for FreezeTokenPayment { // // 0. `[writable]` Freeze PDA to receive the funds (seeds `["freeze_escrow", // destination_ata pubkey, candy guard pubkey, candy machine pubkey]`). - // 1. `[]` Mint account for the NFT. - // 2. `[]` Address of the owner of the NFT. - // 3. `[writable]` Associate token account of the NFT. - // 4. `[]` Master Edition account of the NFT. - // 5. `[]` spl-token program ID. - // 6. `[]` Metaplex Token Metadata program. - // - // Remaining accounts required for Programmable NFTs: - // - // 7. `[writable]` Metadata account of the NFT. - // 8. `[writable]` Freeze PDA associated token account of the NFT. - // 9. `[]` System program. - // 10. `[]` Sysvar instructions account. - // 11. `[]` SPL Associated Token Account program. - // 12. `[optional, writable]` Owner token record account. - // 13. `[optional, writable]` Freeze PDA token record account. - // 14. `[optional]` Token Authorization Rules program. - // 15. `[optional]` Token Authorization Rules account. + // 1. `[writable]` Mint account for the Asset. + // 2. `[]` Collection address of the Asset. + // 3. `[]` mpl-core program ID. + // 4. `[]` System program. FreezeInstruction::Thaw => { msg!("Instruction: Thaw (FreezeTokenPayment guard)"); thaw_nft(ctx, route_context, data) @@ -232,12 +218,9 @@ impl Condition for FreezeTokenPayment { return err!(CandyGuardError::FreezeNotInitialized); } - let nft_ata = try_get_account_info(ctx.accounts.remaining, index + 1)?; - ctx.account_cursor += 1; - - let token_account_info = try_get_account_info(ctx.accounts.remaining, index + 2)?; + let token_account_info = try_get_account_info(ctx.accounts.remaining, index + 1)?; // validate freeze_pda ata - let destination_ata = try_get_account_info(ctx.accounts.remaining, index + 3)?; + let destination_ata = try_get_account_info(ctx.accounts.remaining, index + 2)?; assert_is_ata(destination_ata, &freeze_pda.key(), &self.mint)?; ctx.account_cursor += 2; @@ -249,24 +232,6 @@ impl Condition for FreezeTokenPayment { return err!(CandyGuardError::NotEnoughTokens); } - let candy_machine_info = ctx.accounts.candy_machine.to_account_info(); - let account_data = candy_machine_info.data.borrow_mut(); - - // TODO validate freeze plugin - // let collection_metadata = - // Metadata::try_from(&ctx.accounts.collection_metadata.to_account_info())?; - - // let rule_set = ctx - // .accounts - // .candy_machine - // .get_rule_set(&account_data, &collection_metadata)?; - - // if let Some(rule_set) = rule_set { - // let mint_rule_set = try_get_account_info(ctx.accounts.remaining, index + 4)?; - // assert_keys_equal(mint_rule_set.key, &rule_set)?; - // ctx.account_cursor += 1; - // } - if ctx.accounts.payer.lamports() < FREEZE_SOL_FEE { msg!( "Require {} lamports, accounts has {} lamports", diff --git a/programs/candy-machine-core/program/src/errors.rs b/programs/candy-machine-core/program/src/errors.rs index be3c9ef..dffd1da 100644 --- a/programs/candy-machine-core/program/src/errors.rs +++ b/programs/candy-machine-core/program/src/errors.rs @@ -91,4 +91,7 @@ pub enum CandyError { #[msg("Invalid account version")] InvalidAccountVersion, + + #[msg("Invalid plugin authority")] + IncorrectPluginAuthority, } diff --git a/programs/candy-machine-core/program/src/instructions/initialize_v2.rs b/programs/candy-machine-core/program/src/instructions/initialize_v2.rs index 0e95fe6..1a9c5da 100644 --- a/programs/candy-machine-core/program/src/instructions/initialize_v2.rs +++ b/programs/candy-machine-core/program/src/instructions/initialize_v2.rs @@ -2,7 +2,7 @@ use anchor_lang::{prelude::*, solana_program::sysvar, Discriminator}; use mpl_token_metadata::MAX_SYMBOL_LENGTH; use crate::{ - approve_asset_delegate, constants::{ + approve_asset_collection_delegate, constants::{ AUTHORITY_SEED, HIDDEN_SECTION, }, state::{CandyMachine, CandyMachineData}, utils::fixed_length_string, AccountVersion, ApproveAssetDelegateHelperAccounts, ApproveMetadataDelegateHelperAccounts }; @@ -51,7 +51,7 @@ pub fn initialize_v2( mpl_core_program: ctx.accounts.mpl_core_program.to_account_info(), }; - approve_asset_delegate(delegate_accounts) + approve_asset_collection_delegate(delegate_accounts) } /// Initializes a new candy machine. diff --git a/programs/candy-machine-core/program/src/instructions/mint_asset.rs b/programs/candy-machine-core/program/src/instructions/mint_asset.rs index 403dec5..6eda350 100644 --- a/programs/candy-machine-core/program/src/instructions/mint_asset.rs +++ b/programs/candy-machine-core/program/src/instructions/mint_asset.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; use arrayref::array_ref; -use mpl_core::{self, instructions::CreateCpiBuilder}; +use mpl_core::{self, accounts::Collection, fetch_plugin, instructions::CreateCpiBuilder, types::{PluginType, UpdateDelegate}}; use solana_program::sysvar; use crate::{ @@ -68,18 +68,12 @@ pub(crate) fn process_mint_asset( // collection metadata must be owner by mpl core if !cmp_pubkeys(accounts.collection.owner, &mpl_core::ID) { + return err!(CandyError::IncorrectOwner); } - // TODO check collection stuff - // let collection_metadata: Metadata = - // Metadata::try_from(&collection_metadata_info.to_account_info())?; - // // check that the update authority matches the collection update authority - // if !cmp_pubkeys( - // &collection_metadata.update_authority, - // &accounts.collection_update_authority.key(), - // ) { - // return err!(CandyError::IncorrectCollectionAuthority); - // } + let (auths, _, _) = fetch_plugin::(&accounts.collection, PluginType::UpdateDelegate)?; + + assert_plugin_pubkey_authority(&auths, &accounts.authority_pda.key())?; // (2) selecting an item to mint @@ -228,7 +222,7 @@ fn create_and_mint( CreateCpiBuilder::new(&accounts.mpl_core_program) .payer(&accounts.payer) - .asset_address(&accounts.asset) + .asset(&accounts.asset) .owner(Some(&accounts.asset_owner)) .name(config_line.name) .uri(config_line.uri) diff --git a/programs/candy-machine-core/program/src/instructions/set_collection_v2.rs b/programs/candy-machine-core/program/src/instructions/set_collection_v2.rs index 52b2396..575edf8 100644 --- a/programs/candy-machine-core/program/src/instructions/set_collection_v2.rs +++ b/programs/candy-machine-core/program/src/instructions/set_collection_v2.rs @@ -1,9 +1,9 @@ use anchor_lang::{prelude::*, solana_program::sysvar}; use crate::{ - approve_asset_delegate, cmp_pubkeys, + approve_asset_collection_delegate, cmp_pubkeys, constants::AUTHORITY_SEED, - revoke_asset_delegate, + revoke_asset_collection_delegate, CandyError, CandyMachine, ApproveAssetDelegateHelperAccounts, RevokeAssetDelegateHelperAccounts, }; @@ -39,7 +39,7 @@ pub fn set_collection_v2(ctx: Context) -> Result<()> { sysvar_instructions: accounts.sysvar_instructions.to_account_info(), }; - revoke_asset_delegate( + revoke_asset_collection_delegate( revoke_accounts, candy_machine.key(), *ctx.bumps.get("authority_pda").unwrap(), @@ -48,14 +48,14 @@ pub fn set_collection_v2(ctx: Context) -> Result<()> { let delegate_accounts = ApproveAssetDelegateHelperAccounts { payer: accounts.payer.to_account_info(), authority_pda: accounts.authority_pda.to_account_info(), - collection: accounts.collection.to_account_info(), - collection_update_authority: accounts.collection_update_authority.to_account_info(), + collection: accounts.new_collection.to_account_info(), + collection_update_authority: accounts.new_collection_update_authority.to_account_info(), system_program: accounts.system_program.to_account_info(), sysvar_instructions: accounts.sysvar_instructions.to_account_info(), mpl_core_program: accounts.mpl_core_program.to_account_info(), }; - approve_asset_delegate(delegate_accounts) + approve_asset_collection_delegate(delegate_accounts) } /// Sets the collection PDA for the candy machine. diff --git a/programs/candy-machine-core/program/src/utils.rs b/programs/candy-machine-core/program/src/utils.rs index 5829d2d..0c104b1 100644 --- a/programs/candy-machine-core/program/src/utils.rs +++ b/programs/candy-machine-core/program/src/utils.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; use arrayref::array_ref; -use mpl_core::{instructions::{AddAuthorityCpiBuilder, AddPluginCpiBuilder, RemoveAuthorityCpiBuilder }, types::{Authority, Plugin, PluginType, UpdateDelegate}}; +use mpl_core::{accounts::Collection, fetch_plugin, instructions::{AddCollectionPluginAuthorityCpiBuilder, AddCollectionPluginCpiBuilder, RemoveCollectionPluginAuthorityCpiBuilder }, types::{Authority, Plugin, PluginType, UpdateDelegate}}; use mpl_token_metadata::{ accounts::Metadata, instructions::{ @@ -275,41 +275,67 @@ pub fn revoke_collection_authority_helper( } } -pub fn approve_asset_delegate(accounts: ApproveAssetDelegateHelperAccounts) -> Result<()> { - //TODO check whether UpdateDelegate plugin exists +pub fn assert_plugin_pubkey_authority( + auths: &Vec, + authority: &Pubkey, +) -> Result<()> { + if auths.iter().any(|auth| { + match auth { + Authority::Pubkey { address } => cmp_pubkeys(address, &authority), + _ => false + } + }) { + return Ok(()) + } + err!(CandyError::IncorrectPluginAuthority) +} - let add_plugin_res = AddPluginCpiBuilder::new(&accounts.mpl_core_program) - .asset_address(&accounts.collection) - .authority(&accounts.collection_update_authority) - .plugin(Plugin::UpdateDelegate(UpdateDelegate {})) - .payer(Some(&accounts.payer)) - .system_program(&accounts.system_program) - .invoke()?; - // .map_err(|error| error.into())?; - - // TODO check whether authority exists +pub fn approve_asset_collection_delegate(accounts: ApproveAssetDelegateHelperAccounts) -> Result<()> { + // add UpdateDelegate plugin if it does not exist on the Collection + let maybe_update_plugin = fetch_plugin::(&accounts.collection, PluginType::UpdateDelegate); + if maybe_update_plugin.is_err() { + AddCollectionPluginCpiBuilder::new(&accounts.mpl_core_program) + .collection(&accounts.collection) + .authority(&accounts.collection_update_authority) + .plugin(Plugin::UpdateDelegate(UpdateDelegate {})) + .payer(Some(&accounts.payer)) + .system_program(&accounts.system_program) + .invoke()?; + } - AddAuthorityCpiBuilder::new(&accounts.mpl_core_program) - .asset_address(&accounts.collection) + // add CM authority to collection if it doesn't exist + let (auths, _, _) = fetch_plugin::(&accounts.collection, PluginType::UpdateDelegate)?; + + if !auths.iter().any(|auth| { + match auth { + Authority::Pubkey { address } => cmp_pubkeys(address, &accounts.authority_pda.key()), + _ => false + } + }) { + AddCollectionPluginAuthorityCpiBuilder::new(&accounts.mpl_core_program) + .collection(&accounts.collection) .authority(&accounts.collection_update_authority) .plugin_type(PluginType::UpdateDelegate) - .system_program(&accounts.system_program) .new_authority(Authority::Pubkey { address: accounts.authority_pda.key() - }) + }) + .system_program(&accounts.system_program) .payer(Some(&accounts.payer)) .invoke() .map_err(|error| error.into()) + } else { + Ok(()) + } } -pub fn revoke_asset_delegate( +pub fn revoke_asset_collection_delegate( accounts: RevokeAssetDelegateHelperAccounts, candy_machine: Pubkey, signer_bump: u8, ) -> Result<()> { - RemoveAuthorityCpiBuilder::new(&accounts.mpl_core_program) - .asset_address(&accounts.collection) + RemoveCollectionPluginAuthorityCpiBuilder::new(&accounts.mpl_core_program) + .collection(&accounts.collection) .authority(&accounts.authority_pda) .plugin_type(PluginType::UpdateDelegate) .system_program(&accounts.system_program) @@ -323,8 +349,6 @@ pub fn revoke_asset_delegate( &[signer_bump], ]]) .map_err(|error| error.into()) - - // TODO remove plugin if empty } pub fn approve_metadata_delegate(accounts: ApproveMetadataDelegateHelperAccounts) -> Result<()> {