diff --git a/clients/js/src/generated/instructions/finalizeTreeWithRoot.ts b/clients/js/src/generated/instructions/finalizeTreeWithRoot.ts index a33a537..5ab38be 100644 --- a/clients/js/src/generated/instructions/finalizeTreeWithRoot.ts +++ b/clients/js/src/generated/instructions/finalizeTreeWithRoot.ts @@ -37,7 +37,7 @@ export type FinalizeTreeWithRootInstructionAccounts = { treeConfig?: PublicKey | Pda; merkleTree: PublicKey | Pda; payer?: Signer; - incomingTreeDelegate: Signer; + treeCreatorOrDelegate?: Signer; staker: Signer; registrar: PublicKey | Pda; voter: PublicKey | Pda; @@ -50,7 +50,7 @@ export type FinalizeTreeWithRootInstructionAccounts = { // Data. export type FinalizeTreeWithRootInstructionData = { discriminator: Array; - rightmostRoot: Uint8Array; + root: Uint8Array; rightmostLeaf: Uint8Array; rightmostIndex: number; metadataUrl: string; @@ -58,7 +58,7 @@ export type FinalizeTreeWithRootInstructionData = { }; export type FinalizeTreeWithRootInstructionDataArgs = { - rightmostRoot: Uint8Array; + root: Uint8Array; rightmostLeaf: Uint8Array; rightmostIndex: number; metadataUrl: string; @@ -77,7 +77,7 @@ export function getFinalizeTreeWithRootInstructionDataSerializer(): Serializer< struct( [ ['discriminator', array(u8(), { size: 8 })], - ['rightmostRoot', bytes({ size: 32 })], + ['root', bytes({ size: 32 })], ['rightmostLeaf', bytes({ size: 32 })], ['rightmostIndex', u32()], ['metadataUrl', string()], @@ -101,7 +101,7 @@ export type FinalizeTreeWithRootInstructionArgs = // Instruction. export function finalizeTreeWithRoot( - context: Pick, + context: Pick, input: FinalizeTreeWithRootInstructionAccounts & FinalizeTreeWithRootInstructionArgs ): TransactionBuilder { @@ -116,10 +116,10 @@ export function finalizeTreeWithRoot( treeConfig: { index: 0, isWritable: true, value: input.treeConfig ?? null }, merkleTree: { index: 1, isWritable: true, value: input.merkleTree ?? null }, payer: { index: 2, isWritable: true, value: input.payer ?? null }, - incomingTreeDelegate: { + treeCreatorOrDelegate: { index: 3, isWritable: false, - value: input.incomingTreeDelegate ?? null, + value: input.treeCreatorOrDelegate ?? null, }, staker: { index: 4, isWritable: false, value: input.staker ?? null }, registrar: { index: 5, isWritable: false, value: input.registrar ?? null }, @@ -158,6 +158,9 @@ export function finalizeTreeWithRoot( if (!resolvedAccounts.payer.value) { resolvedAccounts.payer.value = context.payer; } + if (!resolvedAccounts.treeCreatorOrDelegate.value) { + resolvedAccounts.treeCreatorOrDelegate.value = context.identity; + } if (!resolvedAccounts.logWrapper.value) { resolvedAccounts.logWrapper.value = context.programs.getPublicKey( 'splNoop', diff --git a/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts b/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts new file mode 100644 index 0000000..6c503af --- /dev/null +++ b/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts @@ -0,0 +1,264 @@ +/** + * 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 { + findMasterEditionPda, + findMetadataPda, +} from '@metaplex-foundation/mpl-token-metadata'; +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + bytes, + mapSerializer, + string, + struct, + u32, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { findTreeConfigPda } from '../accounts'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + expectPublicKey, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type FinalizeTreeWithRootAndCollectionInstructionAccounts = { + treeConfig?: PublicKey | Pda; + merkleTree: PublicKey | Pda; + payer?: Signer; + treeCreatorOrDelegate?: Signer; + staker: Signer; + collectionAuthority?: Signer; + registrar: PublicKey | Pda; + voter: PublicKey | Pda; + feeReceiver: PublicKey | Pda; + /** + * If there is no collecton authority record PDA then + * this must be the Bubblegum program address. + */ + + collectionAuthorityRecordPda?: PublicKey | Pda; + collectionMint: PublicKey | Pda; + collectionMetadata?: PublicKey | Pda; + collectionEdition?: PublicKey | Pda; + logWrapper?: PublicKey | Pda; + compressionProgram?: PublicKey | Pda; + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type FinalizeTreeWithRootAndCollectionInstructionData = { + discriminator: Array; + root: Uint8Array; + rightmostLeaf: Uint8Array; + rightmostIndex: number; + metadataUrl: string; + metadataHash: string; +}; + +export type FinalizeTreeWithRootAndCollectionInstructionDataArgs = { + root: Uint8Array; + rightmostLeaf: Uint8Array; + rightmostIndex: number; + metadataUrl: string; + metadataHash: string; +}; + +export function getFinalizeTreeWithRootAndCollectionInstructionDataSerializer(): Serializer< + FinalizeTreeWithRootAndCollectionInstructionDataArgs, + FinalizeTreeWithRootAndCollectionInstructionData +> { + return mapSerializer< + FinalizeTreeWithRootAndCollectionInstructionDataArgs, + any, + FinalizeTreeWithRootAndCollectionInstructionData + >( + struct( + [ + ['discriminator', array(u8(), { size: 8 })], + ['root', bytes({ size: 32 })], + ['rightmostLeaf', bytes({ size: 32 })], + ['rightmostIndex', u32()], + ['metadataUrl', string()], + ['metadataHash', string()], + ], + { description: 'FinalizeTreeWithRootAndCollectionInstructionData' } + ), + (value) => ({ + ...value, + discriminator: [194, 98, 45, 168, 183, 72, 67, 155], + }) + ) as Serializer< + FinalizeTreeWithRootAndCollectionInstructionDataArgs, + FinalizeTreeWithRootAndCollectionInstructionData + >; +} + +// Args. +export type FinalizeTreeWithRootAndCollectionInstructionArgs = + FinalizeTreeWithRootAndCollectionInstructionDataArgs; + +// Instruction. +export function finalizeTreeWithRootAndCollection( + context: Pick, + input: FinalizeTreeWithRootAndCollectionInstructionAccounts & + FinalizeTreeWithRootAndCollectionInstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplBubblegum', + 'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + treeConfig: { index: 0, isWritable: true, value: input.treeConfig ?? null }, + merkleTree: { index: 1, isWritable: true, value: input.merkleTree ?? null }, + payer: { index: 2, isWritable: true, value: input.payer ?? null }, + treeCreatorOrDelegate: { + index: 3, + isWritable: false, + value: input.treeCreatorOrDelegate ?? null, + }, + staker: { index: 4, isWritable: false, value: input.staker ?? null }, + collectionAuthority: { + index: 5, + isWritable: false, + value: input.collectionAuthority ?? null, + }, + registrar: { index: 6, isWritable: false, value: input.registrar ?? null }, + voter: { index: 7, isWritable: false, value: input.voter ?? null }, + feeReceiver: { + index: 8, + isWritable: true, + value: input.feeReceiver ?? null, + }, + collectionAuthorityRecordPda: { + index: 9, + isWritable: false, + value: input.collectionAuthorityRecordPda ?? null, + }, + collectionMint: { + index: 10, + isWritable: false, + value: input.collectionMint ?? null, + }, + collectionMetadata: { + index: 11, + isWritable: true, + value: input.collectionMetadata ?? null, + }, + collectionEdition: { + index: 12, + isWritable: false, + value: input.collectionEdition ?? null, + }, + logWrapper: { + index: 13, + isWritable: false, + value: input.logWrapper ?? null, + }, + compressionProgram: { + index: 14, + isWritable: false, + value: input.compressionProgram ?? null, + }, + systemProgram: { + index: 15, + isWritable: false, + value: input.systemProgram ?? null, + }, + }; + + // Arguments. + const resolvedArgs: FinalizeTreeWithRootAndCollectionInstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.treeConfig.value) { + resolvedAccounts.treeConfig.value = findTreeConfigPda(context, { + merkleTree: expectPublicKey(resolvedAccounts.merkleTree.value), + }); + } + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.treeCreatorOrDelegate.value) { + resolvedAccounts.treeCreatorOrDelegate.value = context.identity; + } + if (!resolvedAccounts.collectionAuthority.value) { + resolvedAccounts.collectionAuthority.value = context.identity; + } + if (!resolvedAccounts.collectionMetadata.value) { + resolvedAccounts.collectionMetadata.value = findMetadataPda(context, { + mint: expectPublicKey(resolvedAccounts.collectionMint.value), + }); + } + if (!resolvedAccounts.collectionEdition.value) { + resolvedAccounts.collectionEdition.value = findMasterEditionPda(context, { + mint: expectPublicKey(resolvedAccounts.collectionMint.value), + }); + } + if (!resolvedAccounts.logWrapper.value) { + resolvedAccounts.logWrapper.value = context.programs.getPublicKey( + 'splNoop', + 'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV' + ); + resolvedAccounts.logWrapper.isWritable = false; + } + if (!resolvedAccounts.compressionProgram.value) { + resolvedAccounts.compressionProgram.value = context.programs.getPublicKey( + 'splAccountCompression', + 'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK' + ); + resolvedAccounts.compressionProgram.isWritable = false; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getFinalizeTreeWithRootAndCollectionInstructionDataSerializer().serialize( + resolvedArgs as FinalizeTreeWithRootAndCollectionInstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index bcfdd4a..13483a3 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -13,6 +13,7 @@ export * from './createTreeConfig'; export * from './decompressV1'; export * from './delegate'; export * from './finalizeTreeWithRoot'; +export * from './finalizeTreeWithRootAndCollection'; export * from './mintToCollectionV1'; export * from './mintV1'; export * from './prepareTree'; diff --git a/clients/rust/Cargo.lock b/clients/rust/Cargo.lock index d1b4bf4..aa80f32 100644 --- a/clients/rust/Cargo.lock +++ b/clients/rust/Cargo.lock @@ -2401,7 +2401,7 @@ dependencies = [ "solana-sdk", "spl-account-compression", "spl-associated-token-account 3.0.2", - "spl-concurrent-merkle-tree 0.2.0", + "spl-concurrent-merkle-tree 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "spl-merkle-tree-reference", "spl-noop", "spl-token", @@ -4858,7 +4858,7 @@ dependencies = [ "anchor-lang", "bytemuck", "solana-program", - "spl-concurrent-merkle-tree 0.3.0", + "spl-concurrent-merkle-tree 0.2.0", "spl-noop", ] @@ -4897,8 +4897,6 @@ dependencies = [ [[package]] name = "spl-concurrent-merkle-tree" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141eaea58588beae81b71d101373a53f096737739873de42d6b1368bc2b8fc30" dependencies = [ "bytemuck", "solana-program", @@ -4907,7 +4905,9 @@ dependencies = [ [[package]] name = "spl-concurrent-merkle-tree" -version = "0.3.0" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141eaea58588beae81b71d101373a53f096737739873de42d6b1368bc2b8fc30" dependencies = [ "bytemuck", "solana-program", diff --git a/clients/rust/src/generated/instructions/finalize_tree_with_root.rs b/clients/rust/src/generated/instructions/finalize_tree_with_root.rs index c2e89d5..31e46ea 100644 --- a/clients/rust/src/generated/instructions/finalize_tree_with_root.rs +++ b/clients/rust/src/generated/instructions/finalize_tree_with_root.rs @@ -16,7 +16,7 @@ pub struct FinalizeTreeWithRoot { pub payer: solana_program::pubkey::Pubkey, - pub incoming_tree_delegate: solana_program::pubkey::Pubkey, + pub tree_creator_or_delegate: solana_program::pubkey::Pubkey, pub staker: solana_program::pubkey::Pubkey, @@ -59,7 +59,7 @@ impl FinalizeTreeWithRoot { self.payer, true, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( - self.incoming_tree_delegate, + self.tree_creator_or_delegate, true, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( @@ -120,7 +120,7 @@ impl FinalizeTreeWithRootInstructionData { #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FinalizeTreeWithRootInstructionArgs { - pub rightmost_root: [u8; 32], + pub root: [u8; 32], pub rightmost_leaf: [u8; 32], pub rightmost_index: u32, pub metadata_url: String, @@ -133,7 +133,7 @@ pub struct FinalizeTreeWithRootBuilder { tree_config: Option, merkle_tree: Option, payer: Option, - incoming_tree_delegate: Option, + tree_creator_or_delegate: Option, staker: Option, registrar: Option, voter: Option, @@ -141,7 +141,7 @@ pub struct FinalizeTreeWithRootBuilder { log_wrapper: Option, compression_program: Option, system_program: Option, - rightmost_root: Option<[u8; 32]>, + root: Option<[u8; 32]>, rightmost_leaf: Option<[u8; 32]>, rightmost_index: Option, metadata_url: Option, @@ -169,11 +169,11 @@ impl FinalizeTreeWithRootBuilder { self } #[inline(always)] - pub fn incoming_tree_delegate( + pub fn tree_creator_or_delegate( &mut self, - incoming_tree_delegate: solana_program::pubkey::Pubkey, + tree_creator_or_delegate: solana_program::pubkey::Pubkey, ) -> &mut Self { - self.incoming_tree_delegate = Some(incoming_tree_delegate); + self.tree_creator_or_delegate = Some(tree_creator_or_delegate); self } #[inline(always)] @@ -218,8 +218,8 @@ impl FinalizeTreeWithRootBuilder { self } #[inline(always)] - pub fn rightmost_root(&mut self, rightmost_root: [u8; 32]) -> &mut Self { - self.rightmost_root = Some(rightmost_root); + pub fn root(&mut self, root: [u8; 32]) -> &mut Self { + self.root = Some(root); self } #[inline(always)] @@ -266,9 +266,9 @@ impl FinalizeTreeWithRootBuilder { tree_config: self.tree_config.expect("tree_config is not set"), merkle_tree: self.merkle_tree.expect("merkle_tree is not set"), payer: self.payer.expect("payer is not set"), - incoming_tree_delegate: self - .incoming_tree_delegate - .expect("incoming_tree_delegate is not set"), + tree_creator_or_delegate: self + .tree_creator_or_delegate + .expect("tree_creator_or_delegate is not set"), staker: self.staker.expect("staker is not set"), registrar: self.registrar.expect("registrar is not set"), voter: self.voter.expect("voter is not set"), @@ -284,10 +284,7 @@ impl FinalizeTreeWithRootBuilder { .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), }; let args = FinalizeTreeWithRootInstructionArgs { - rightmost_root: self - .rightmost_root - .clone() - .expect("rightmost_root is not set"), + root: self.root.clone().expect("root is not set"), rightmost_leaf: self .rightmost_leaf .clone() @@ -315,7 +312,7 @@ pub struct FinalizeTreeWithRootCpiAccounts<'a, 'b> { pub payer: &'b solana_program::account_info::AccountInfo<'a>, - pub incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + pub tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, pub staker: &'b solana_program::account_info::AccountInfo<'a>, @@ -343,7 +340,7 @@ pub struct FinalizeTreeWithRootCpi<'a, 'b> { pub payer: &'b solana_program::account_info::AccountInfo<'a>, - pub incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + pub tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, pub staker: &'b solana_program::account_info::AccountInfo<'a>, @@ -373,7 +370,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpi<'a, 'b> { tree_config: accounts.tree_config, merkle_tree: accounts.merkle_tree, payer: accounts.payer, - incoming_tree_delegate: accounts.incoming_tree_delegate, + tree_creator_or_delegate: accounts.tree_creator_or_delegate, staker: accounts.staker, registrar: accounts.registrar, voter: accounts.voter, @@ -431,7 +428,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpi<'a, 'b> { true, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *self.incoming_tree_delegate.key, + *self.tree_creator_or_delegate.key, true, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( @@ -485,7 +482,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpi<'a, 'b> { account_infos.push(self.tree_config.clone()); account_infos.push(self.merkle_tree.clone()); account_infos.push(self.payer.clone()); - account_infos.push(self.incoming_tree_delegate.clone()); + account_infos.push(self.tree_creator_or_delegate.clone()); account_infos.push(self.staker.clone()); account_infos.push(self.registrar.clone()); account_infos.push(self.voter.clone()); @@ -517,7 +514,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { tree_config: None, merkle_tree: None, payer: None, - incoming_tree_delegate: None, + tree_creator_or_delegate: None, staker: None, registrar: None, voter: None, @@ -525,7 +522,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { log_wrapper: None, compression_program: None, system_program: None, - rightmost_root: None, + root: None, rightmost_leaf: None, rightmost_index: None, metadata_url: None, @@ -556,11 +553,11 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { self } #[inline(always)] - pub fn incoming_tree_delegate( + pub fn tree_creator_or_delegate( &mut self, - incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, ) -> &mut Self { - self.instruction.incoming_tree_delegate = Some(incoming_tree_delegate); + self.instruction.tree_creator_or_delegate = Some(tree_creator_or_delegate); self } #[inline(always)] @@ -617,8 +614,8 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { self } #[inline(always)] - pub fn rightmost_root(&mut self, rightmost_root: [u8; 32]) -> &mut Self { - self.instruction.rightmost_root = Some(rightmost_root); + pub fn root(&mut self, root: [u8; 32]) -> &mut Self { + self.instruction.root = Some(root); self } #[inline(always)] @@ -683,11 +680,7 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { let args = FinalizeTreeWithRootInstructionArgs { - rightmost_root: self - .instruction - .rightmost_root - .clone() - .expect("rightmost_root is not set"), + root: self.instruction.root.clone().expect("root is not set"), rightmost_leaf: self .instruction .rightmost_leaf @@ -724,10 +717,10 @@ impl<'a, 'b> FinalizeTreeWithRootCpiBuilder<'a, 'b> { payer: self.instruction.payer.expect("payer is not set"), - incoming_tree_delegate: self + tree_creator_or_delegate: self .instruction - .incoming_tree_delegate - .expect("incoming_tree_delegate is not set"), + .tree_creator_or_delegate + .expect("tree_creator_or_delegate is not set"), staker: self.instruction.staker.expect("staker is not set"), @@ -768,7 +761,7 @@ struct FinalizeTreeWithRootCpiBuilderInstruction<'a, 'b> { tree_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, merkle_tree: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, - incoming_tree_delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tree_creator_or_delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, staker: Option<&'b solana_program::account_info::AccountInfo<'a>>, registrar: Option<&'b solana_program::account_info::AccountInfo<'a>>, voter: Option<&'b solana_program::account_info::AccountInfo<'a>>, @@ -776,7 +769,7 @@ struct FinalizeTreeWithRootCpiBuilderInstruction<'a, 'b> { log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, compression_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, - rightmost_root: Option<[u8; 32]>, + root: Option<[u8; 32]>, rightmost_leaf: Option<[u8; 32]>, rightmost_index: Option, metadata_url: Option, diff --git a/clients/rust/src/generated/instructions/finalize_tree_with_root_and_collection.rs b/clients/rust/src/generated/instructions/finalize_tree_with_root_and_collection.rs new file mode 100644 index 0000000..922dbc7 --- /dev/null +++ b/clients/rust/src/generated/instructions/finalize_tree_with_root_and_collection.rs @@ -0,0 +1,1016 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct FinalizeTreeWithRootAndCollection { + pub tree_config: solana_program::pubkey::Pubkey, + + pub merkle_tree: solana_program::pubkey::Pubkey, + + pub payer: solana_program::pubkey::Pubkey, + + pub tree_creator_or_delegate: solana_program::pubkey::Pubkey, + + pub staker: solana_program::pubkey::Pubkey, + + pub collection_authority: solana_program::pubkey::Pubkey, + + pub registrar: solana_program::pubkey::Pubkey, + + pub voter: solana_program::pubkey::Pubkey, + + pub fee_receiver: solana_program::pubkey::Pubkey, + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + pub collection_authority_record_pda: Option, + + pub collection_mint: solana_program::pubkey::Pubkey, + + pub collection_metadata: solana_program::pubkey::Pubkey, + + pub collection_edition: solana_program::pubkey::Pubkey, + + pub log_wrapper: solana_program::pubkey::Pubkey, + + pub compression_program: solana_program::pubkey::Pubkey, + + pub system_program: solana_program::pubkey::Pubkey, +} + +impl FinalizeTreeWithRootAndCollection { + pub fn instruction( + &self, + args: FinalizeTreeWithRootAndCollectionInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: FinalizeTreeWithRootAndCollectionInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(16 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.tree_config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.merkle_tree, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.tree_creator_or_delegate, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.staker, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.collection_authority, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.registrar, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.voter, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.fee_receiver, + false, + )); + if let Some(collection_authority_record_pda) = self.collection_authority_record_pda { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + collection_authority_record_pda, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_BUBBLEGUM_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.collection_mint, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection_metadata, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.collection_edition, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.log_wrapper, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.compression_program, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = FinalizeTreeWithRootAndCollectionInstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_BUBBLEGUM_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct FinalizeTreeWithRootAndCollectionInstructionData { + discriminator: [u8; 8], +} + +impl FinalizeTreeWithRootAndCollectionInstructionData { + fn new() -> Self { + Self { + discriminator: [194, 98, 45, 168, 183, 72, 67, 155], + } + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FinalizeTreeWithRootAndCollectionInstructionArgs { + pub root: [u8; 32], + pub rightmost_leaf: [u8; 32], + pub rightmost_index: u32, + pub metadata_url: String, + pub metadata_hash: String, +} + +/// Instruction builder. +#[derive(Default)] +pub struct FinalizeTreeWithRootAndCollectionBuilder { + tree_config: Option, + merkle_tree: Option, + payer: Option, + tree_creator_or_delegate: Option, + staker: Option, + collection_authority: Option, + registrar: Option, + voter: Option, + fee_receiver: Option, + collection_authority_record_pda: Option, + collection_mint: Option, + collection_metadata: Option, + collection_edition: Option, + log_wrapper: Option, + compression_program: Option, + system_program: Option, + root: Option<[u8; 32]>, + rightmost_leaf: Option<[u8; 32]>, + rightmost_index: Option, + metadata_url: Option, + metadata_hash: Option, + __remaining_accounts: Vec, +} + +impl FinalizeTreeWithRootAndCollectionBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn tree_config(&mut self, tree_config: solana_program::pubkey::Pubkey) -> &mut Self { + self.tree_config = Some(tree_config); + self + } + #[inline(always)] + pub fn merkle_tree(&mut self, merkle_tree: solana_program::pubkey::Pubkey) -> &mut Self { + self.merkle_tree = Some(merkle_tree); + self + } + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + #[inline(always)] + pub fn tree_creator_or_delegate( + &mut self, + tree_creator_or_delegate: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.tree_creator_or_delegate = Some(tree_creator_or_delegate); + self + } + #[inline(always)] + pub fn staker(&mut self, staker: solana_program::pubkey::Pubkey) -> &mut Self { + self.staker = Some(staker); + self + } + #[inline(always)] + pub fn collection_authority( + &mut self, + collection_authority: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.collection_authority = Some(collection_authority); + self + } + #[inline(always)] + pub fn registrar(&mut self, registrar: solana_program::pubkey::Pubkey) -> &mut Self { + self.registrar = Some(registrar); + self + } + #[inline(always)] + pub fn voter(&mut self, voter: solana_program::pubkey::Pubkey) -> &mut Self { + self.voter = Some(voter); + self + } + #[inline(always)] + pub fn fee_receiver(&mut self, fee_receiver: solana_program::pubkey::Pubkey) -> &mut Self { + self.fee_receiver = Some(fee_receiver); + self + } + /// `[optional account]` + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + #[inline(always)] + pub fn collection_authority_record_pda( + &mut self, + collection_authority_record_pda: Option, + ) -> &mut Self { + self.collection_authority_record_pda = collection_authority_record_pda; + self + } + #[inline(always)] + pub fn collection_mint( + &mut self, + collection_mint: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.collection_mint = Some(collection_mint); + self + } + #[inline(always)] + pub fn collection_metadata( + &mut self, + collection_metadata: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.collection_metadata = Some(collection_metadata); + self + } + #[inline(always)] + pub fn collection_edition( + &mut self, + collection_edition: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.collection_edition = Some(collection_edition); + self + } + /// `[optional account, default to 'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV']` + #[inline(always)] + pub fn log_wrapper(&mut self, log_wrapper: solana_program::pubkey::Pubkey) -> &mut Self { + self.log_wrapper = Some(log_wrapper); + self + } + /// `[optional account, default to 'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK']` + #[inline(always)] + pub fn compression_program( + &mut self, + compression_program: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.compression_program = Some(compression_program); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn root(&mut self, root: [u8; 32]) -> &mut Self { + self.root = Some(root); + self + } + #[inline(always)] + pub fn rightmost_leaf(&mut self, rightmost_leaf: [u8; 32]) -> &mut Self { + self.rightmost_leaf = Some(rightmost_leaf); + self + } + #[inline(always)] + pub fn rightmost_index(&mut self, rightmost_index: u32) -> &mut Self { + self.rightmost_index = Some(rightmost_index); + self + } + #[inline(always)] + pub fn metadata_url(&mut self, metadata_url: String) -> &mut Self { + self.metadata_url = Some(metadata_url); + self + } + #[inline(always)] + pub fn metadata_hash(&mut self, metadata_hash: String) -> &mut Self { + self.metadata_hash = Some(metadata_hash); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = FinalizeTreeWithRootAndCollection { + tree_config: self.tree_config.expect("tree_config is not set"), + merkle_tree: self.merkle_tree.expect("merkle_tree is not set"), + payer: self.payer.expect("payer is not set"), + tree_creator_or_delegate: self + .tree_creator_or_delegate + .expect("tree_creator_or_delegate is not set"), + staker: self.staker.expect("staker is not set"), + collection_authority: self + .collection_authority + .expect("collection_authority is not set"), + registrar: self.registrar.expect("registrar is not set"), + voter: self.voter.expect("voter is not set"), + fee_receiver: self.fee_receiver.expect("fee_receiver is not set"), + collection_authority_record_pda: self.collection_authority_record_pda, + collection_mint: self.collection_mint.expect("collection_mint is not set"), + collection_metadata: self + .collection_metadata + .expect("collection_metadata is not set"), + collection_edition: self + .collection_edition + .expect("collection_edition is not set"), + log_wrapper: self.log_wrapper.unwrap_or(solana_program::pubkey!( + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV" + )), + compression_program: self.compression_program.unwrap_or(solana_program::pubkey!( + "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" + )), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = FinalizeTreeWithRootAndCollectionInstructionArgs { + root: self.root.clone().expect("root is not set"), + rightmost_leaf: self + .rightmost_leaf + .clone() + .expect("rightmost_leaf is not set"), + rightmost_index: self + .rightmost_index + .clone() + .expect("rightmost_index is not set"), + metadata_url: self.metadata_url.clone().expect("metadata_url is not set"), + metadata_hash: self + .metadata_hash + .clone() + .expect("metadata_hash is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `finalize_tree_with_root_and_collection` CPI accounts. +pub struct FinalizeTreeWithRootAndCollectionCpiAccounts<'a, 'b> { + pub tree_config: &'b solana_program::account_info::AccountInfo<'a>, + + pub merkle_tree: &'b solana_program::account_info::AccountInfo<'a>, + + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + + pub tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, + + pub staker: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_authority: &'b solana_program::account_info::AccountInfo<'a>, + + pub registrar: &'b solana_program::account_info::AccountInfo<'a>, + + pub voter: &'b solana_program::account_info::AccountInfo<'a>, + + pub fee_receiver: &'b solana_program::account_info::AccountInfo<'a>, + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + pub collection_authority_record_pda: Option<&'b solana_program::account_info::AccountInfo<'a>>, + + pub collection_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_metadata: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_edition: &'b solana_program::account_info::AccountInfo<'a>, + + pub log_wrapper: &'b solana_program::account_info::AccountInfo<'a>, + + pub compression_program: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `finalize_tree_with_root_and_collection` CPI instruction. +pub struct FinalizeTreeWithRootAndCollectionCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub tree_config: &'b solana_program::account_info::AccountInfo<'a>, + + pub merkle_tree: &'b solana_program::account_info::AccountInfo<'a>, + + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + + pub tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, + + pub staker: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_authority: &'b solana_program::account_info::AccountInfo<'a>, + + pub registrar: &'b solana_program::account_info::AccountInfo<'a>, + + pub voter: &'b solana_program::account_info::AccountInfo<'a>, + + pub fee_receiver: &'b solana_program::account_info::AccountInfo<'a>, + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + pub collection_authority_record_pda: Option<&'b solana_program::account_info::AccountInfo<'a>>, + + pub collection_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_metadata: &'b solana_program::account_info::AccountInfo<'a>, + + pub collection_edition: &'b solana_program::account_info::AccountInfo<'a>, + + pub log_wrapper: &'b solana_program::account_info::AccountInfo<'a>, + + pub compression_program: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: FinalizeTreeWithRootAndCollectionInstructionArgs, +} + +impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: FinalizeTreeWithRootAndCollectionCpiAccounts<'a, 'b>, + args: FinalizeTreeWithRootAndCollectionInstructionArgs, + ) -> Self { + Self { + __program: program, + tree_config: accounts.tree_config, + merkle_tree: accounts.merkle_tree, + payer: accounts.payer, + tree_creator_or_delegate: accounts.tree_creator_or_delegate, + staker: accounts.staker, + collection_authority: accounts.collection_authority, + registrar: accounts.registrar, + voter: accounts.voter, + fee_receiver: accounts.fee_receiver, + collection_authority_record_pda: accounts.collection_authority_record_pda, + collection_mint: accounts.collection_mint, + collection_metadata: accounts.collection_metadata, + collection_edition: accounts.collection_edition, + log_wrapper: accounts.log_wrapper, + compression_program: accounts.compression_program, + system_program: accounts.system_program, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(16 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.tree_config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.merkle_tree.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.tree_creator_or_delegate.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.staker.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.collection_authority.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.registrar.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.voter.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.fee_receiver.key, + false, + )); + if let Some(collection_authority_record_pda) = self.collection_authority_record_pda { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *collection_authority_record_pda.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_BUBBLEGUM_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.collection_mint.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection_metadata.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.collection_edition.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.log_wrapper.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.compression_program.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = FinalizeTreeWithRootAndCollectionInstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_BUBBLEGUM_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(16 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.tree_config.clone()); + account_infos.push(self.merkle_tree.clone()); + account_infos.push(self.payer.clone()); + account_infos.push(self.tree_creator_or_delegate.clone()); + account_infos.push(self.staker.clone()); + account_infos.push(self.collection_authority.clone()); + account_infos.push(self.registrar.clone()); + account_infos.push(self.voter.clone()); + account_infos.push(self.fee_receiver.clone()); + if let Some(collection_authority_record_pda) = self.collection_authority_record_pda { + account_infos.push(collection_authority_record_pda.clone()); + } + account_infos.push(self.collection_mint.clone()); + account_infos.push(self.collection_metadata.clone()); + account_infos.push(self.collection_edition.clone()); + account_infos.push(self.log_wrapper.clone()); + account_infos.push(self.compression_program.clone()); + account_infos.push(self.system_program.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `finalize_tree_with_root_and_collection` CPI instruction builder. +pub struct FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(FinalizeTreeWithRootAndCollectionCpiBuilderInstruction { + __program: program, + tree_config: None, + merkle_tree: None, + payer: None, + tree_creator_or_delegate: None, + staker: None, + collection_authority: None, + registrar: None, + voter: None, + fee_receiver: None, + collection_authority_record_pda: None, + collection_mint: None, + collection_metadata: None, + collection_edition: None, + log_wrapper: None, + compression_program: None, + system_program: None, + root: None, + rightmost_leaf: None, + rightmost_index: None, + metadata_url: None, + metadata_hash: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn tree_config( + &mut self, + tree_config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.tree_config = Some(tree_config); + self + } + #[inline(always)] + pub fn merkle_tree( + &mut self, + merkle_tree: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.merkle_tree = Some(merkle_tree); + self + } + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + #[inline(always)] + pub fn tree_creator_or_delegate( + &mut self, + tree_creator_or_delegate: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.tree_creator_or_delegate = Some(tree_creator_or_delegate); + self + } + #[inline(always)] + pub fn staker( + &mut self, + staker: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.staker = Some(staker); + self + } + #[inline(always)] + pub fn collection_authority( + &mut self, + collection_authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection_authority = Some(collection_authority); + self + } + #[inline(always)] + pub fn registrar( + &mut self, + registrar: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.registrar = Some(registrar); + self + } + #[inline(always)] + pub fn voter(&mut self, voter: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.voter = Some(voter); + self + } + #[inline(always)] + pub fn fee_receiver( + &mut self, + fee_receiver: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.fee_receiver = Some(fee_receiver); + self + } + /// `[optional account]` + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + #[inline(always)] + pub fn collection_authority_record_pda( + &mut self, + collection_authority_record_pda: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection_authority_record_pda = collection_authority_record_pda; + self + } + #[inline(always)] + pub fn collection_mint( + &mut self, + collection_mint: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection_mint = Some(collection_mint); + self + } + #[inline(always)] + pub fn collection_metadata( + &mut self, + collection_metadata: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection_metadata = Some(collection_metadata); + self + } + #[inline(always)] + pub fn collection_edition( + &mut self, + collection_edition: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection_edition = Some(collection_edition); + self + } + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.log_wrapper = Some(log_wrapper); + self + } + #[inline(always)] + pub fn compression_program( + &mut self, + compression_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.compression_program = Some(compression_program); + self + } + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn root(&mut self, root: [u8; 32]) -> &mut Self { + self.instruction.root = Some(root); + self + } + #[inline(always)] + pub fn rightmost_leaf(&mut self, rightmost_leaf: [u8; 32]) -> &mut Self { + self.instruction.rightmost_leaf = Some(rightmost_leaf); + self + } + #[inline(always)] + pub fn rightmost_index(&mut self, rightmost_index: u32) -> &mut Self { + self.instruction.rightmost_index = Some(rightmost_index); + self + } + #[inline(always)] + pub fn metadata_url(&mut self, metadata_url: String) -> &mut Self { + self.instruction.metadata_url = Some(metadata_url); + self + } + #[inline(always)] + pub fn metadata_hash(&mut self, metadata_hash: String) -> &mut Self { + self.instruction.metadata_hash = Some(metadata_hash); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = FinalizeTreeWithRootAndCollectionInstructionArgs { + root: self.instruction.root.clone().expect("root is not set"), + rightmost_leaf: self + .instruction + .rightmost_leaf + .clone() + .expect("rightmost_leaf is not set"), + rightmost_index: self + .instruction + .rightmost_index + .clone() + .expect("rightmost_index is not set"), + metadata_url: self + .instruction + .metadata_url + .clone() + .expect("metadata_url is not set"), + metadata_hash: self + .instruction + .metadata_hash + .clone() + .expect("metadata_hash is not set"), + }; + let instruction = FinalizeTreeWithRootAndCollectionCpi { + __program: self.instruction.__program, + + tree_config: self + .instruction + .tree_config + .expect("tree_config is not set"), + + merkle_tree: self + .instruction + .merkle_tree + .expect("merkle_tree is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + tree_creator_or_delegate: self + .instruction + .tree_creator_or_delegate + .expect("tree_creator_or_delegate is not set"), + + staker: self.instruction.staker.expect("staker is not set"), + + collection_authority: self + .instruction + .collection_authority + .expect("collection_authority is not set"), + + registrar: self.instruction.registrar.expect("registrar is not set"), + + voter: self.instruction.voter.expect("voter is not set"), + + fee_receiver: self + .instruction + .fee_receiver + .expect("fee_receiver is not set"), + + collection_authority_record_pda: self.instruction.collection_authority_record_pda, + + collection_mint: self + .instruction + .collection_mint + .expect("collection_mint is not set"), + + collection_metadata: self + .instruction + .collection_metadata + .expect("collection_metadata is not set"), + + collection_edition: self + .instruction + .collection_edition + .expect("collection_edition is not set"), + + log_wrapper: self + .instruction + .log_wrapper + .expect("log_wrapper is not set"), + + compression_program: self + .instruction + .compression_program + .expect("compression_program is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct FinalizeTreeWithRootAndCollectionCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + tree_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + merkle_tree: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tree_creator_or_delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, + staker: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + registrar: Option<&'b solana_program::account_info::AccountInfo<'a>>, + voter: Option<&'b solana_program::account_info::AccountInfo<'a>>, + fee_receiver: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_authority_record_pda: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_mint: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_metadata: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + compression_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + root: Option<[u8; 32]>, + rightmost_leaf: Option<[u8; 32]>, + rightmost_index: Option, + metadata_url: Option, + metadata_hash: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index 90cb69b..ab3f9e8 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -12,6 +12,7 @@ pub(crate) mod create_tree_config; pub(crate) mod decompress_v1; pub(crate) mod delegate; pub(crate) mod finalize_tree_with_root; +pub(crate) mod finalize_tree_with_root_and_collection; pub(crate) mod mint_to_collection_v1; pub(crate) mod mint_v1; pub(crate) mod prepare_tree; @@ -34,6 +35,7 @@ pub use self::create_tree_config::*; pub use self::decompress_v1::*; pub use self::delegate::*; pub use self::finalize_tree_with_root::*; +pub use self::finalize_tree_with_root_and_collection::*; pub use self::mint_to_collection_v1::*; pub use self::mint_v1::*; pub use self::prepare_tree::*; diff --git a/clients/rust/src/lib.rs b/clients/rust/src/lib.rs index 3b44f53..2e04ab1 100644 --- a/clients/rust/src/lib.rs +++ b/clients/rust/src/lib.rs @@ -29,6 +29,7 @@ pub enum InstructionName { SetDecompressibleState, UpdateMetadata, FinalizeTreeWithRoot, + FinalizeTreeWithRootAndCollection, PrepareTree, AddCanopy, } @@ -60,6 +61,7 @@ pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName { [18, 135, 238, 168, 246, 195, 61, 115] => InstructionName::SetDecompressibleState, [170, 182, 43, 239, 97, 78, 225, 186] => InstructionName::UpdateMetadata, [77, 73, 220, 153, 126, 225, 64, 204] => InstructionName::FinalizeTreeWithRoot, + [194, 98, 45, 168, 183, 72, 67, 155] => InstructionName::FinalizeTreeWithRootAndCollection, [41, 56, 189, 77, 58, 12, 142, 71] => InstructionName::PrepareTree, [247, 118, 145, 92, 84, 66, 207, 25] => InstructionName::AddCanopy, _ => InstructionName::Unknown, diff --git a/idls/bubblegum.json b/idls/bubblegum.json index 8a27cf5..5951a1d 100644 --- a/idls/bubblegum.json +++ b/idls/bubblegum.json @@ -401,7 +401,7 @@ "isSigner": true }, { - "name": "incomingTreeDelegate", + "name": "treeDelegate", "isMut": false, "isSigner": true }, @@ -443,7 +443,128 @@ ], "args": [ { - "name": "rightmostRoot", + "name": "root", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "rightmostLeaf", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "rightmostIndex", + "type": "u32" + }, + { + "name": "metadataUrl", + "type": "string" + }, + { + "name": "metadataHash", + "type": "string" + } + ] + }, + { + "name": "finalizeTreeWithRootAndCollection", + "accounts": [ + { + "name": "treeAuthority", + "isMut": true, + "isSigner": false + }, + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "treeDelegate", + "isMut": false, + "isSigner": true + }, + { + "name": "staker", + "isMut": false, + "isSigner": true + }, + { + "name": "collectionAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": false, + "isSigner": false + }, + { + "name": "feeReceiver", + "isMut": true, + "isSigner": false + }, + { + "name": "collectionAuthorityRecordPda", + "isMut": false, + "isSigner": false, + "docs": [ + "If there is no collecton authority record PDA then", + "this must be the Bubblegum program address." + ] + }, + { + "name": "collectionMint", + "isMut": false, + "isSigner": false + }, + { + "name": "collectionMetadata", + "isMut": true, + "isSigner": false + }, + { + "name": "editionAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false + }, + { + "name": "compressionProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "root", "type": { "array": [ "u8", @@ -2274,6 +2395,9 @@ { "name": "FinalizeTreeWithRoot" }, + { + "name": "FinalizeTreeWithRootAndCollection" + }, { "name": "PrepareTree" }, diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index d56e9cc..2fcd75f 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -5185,7 +5185,7 @@ dependencies = [ [[package]] name = "spl-concurrent-merkle-tree" -version = "0.3.0" +version = "0.2.0" dependencies = [ "bytemuck", "solana-program", diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 0337f54..27548d7 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -38,6 +38,7 @@ pub enum InstructionName { SetDecompressibleState, UpdateMetadata, FinalizeTreeWithRoot, + FinalizeTreeWithRootAndCollection, PrepareTree, AddCanopy, } @@ -59,6 +60,7 @@ pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName { [116, 110, 29, 56, 107, 219, 42, 93] => InstructionName::Burn, [82, 193, 176, 117, 176, 21, 115, 253] => InstructionName::Compress, [77, 73, 220, 153, 126, 225, 64, 204] => InstructionName::FinalizeTreeWithRoot, + [194, 98, 45, 168, 183, 72, 67, 155] => InstructionName::FinalizeTreeWithRootAndCollection, [165, 83, 136, 142, 89, 202, 47, 220] => InstructionName::CreateTree, [52, 17, 96, 132, 71, 4, 85, 194] => InstructionName::VerifyCreator, [107, 178, 57, 39, 105, 115, 112, 152] => InstructionName::UnverifyCreator, @@ -133,7 +135,7 @@ pub mod bubblegum { pub(crate) fn finalize_tree_with_root<'info>( ctx: Context<'_, '_, '_, 'info, FinalizeTreeWithRoot<'info>>, - rightmost_root: [u8; 32], + root: [u8; 32], rightmost_leaf: [u8; 32], rightmost_index: u32, metadata_url: String, @@ -141,7 +143,25 @@ pub mod bubblegum { ) -> Result<()> { processor::finalize_tree_with_root( ctx, - rightmost_root, + root, + rightmost_leaf, + rightmost_index, + metadata_url, + metadata_hash, + ) + } + + pub(crate) fn finalize_tree_with_root_and_collection<'info>( + ctx: Context<'_, '_, '_, 'info, FinalizeTreeWithRootAndCollection<'info>>, + root: [u8; 32], + rightmost_leaf: [u8; 32], + rightmost_index: u32, + metadata_url: String, + metadata_hash: String, + ) -> Result<()> { + processor::finalize_tree_with_root_and_collection( + ctx, + root, rightmost_leaf, rightmost_index, metadata_url, diff --git a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs index e94c5af..95839b8 100644 --- a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs @@ -25,7 +25,7 @@ pub struct FinalizeTreeWithRoot<'info> { pub merkle_tree: UncheckedAccount<'info>, #[account(mut)] pub payer: Signer<'info>, - pub incoming_tree_delegate: Signer<'info>, + pub tree_delegate: Signer<'info>, pub staker: Signer<'info>, /// CHECK: pub registrar: UncheckedAccount<'info>, @@ -47,7 +47,7 @@ pub(crate) fn finalize_tree_with_root<'info>( _metadata_url: String, _metadata_hash: String, ) -> Result<()> { - let incoming_tree_delegate = ctx.accounts.incoming_tree_delegate.key(); + let incoming_tree_delegate = ctx.accounts.tree_delegate.key(); let authority = &mut ctx.accounts.tree_authority; require!( @@ -105,7 +105,7 @@ pub(crate) fn finalize_tree_with_root<'info>( ) } -fn check_stake<'info>( +pub(crate) fn check_stake<'info>( staker_acc: &AccountInfo<'info>, registrar_acc: &AccountInfo<'info>, voter_acc: &AccountInfo<'info>, @@ -197,7 +197,7 @@ fn check_stake<'info>( Ok(()) } -fn finalize_tree<'info>( +pub(crate) fn finalize_tree<'info>( root: [u8; 32], rightmost_leaf: [u8; 32], rightmost_index: u32, diff --git a/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs b/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs new file mode 100644 index 0000000..2e4d344 --- /dev/null +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs @@ -0,0 +1,112 @@ +use crate::processor::process_collection_verification_mpl_only; +use crate::state::metaplex_adapter::Collection; +use crate::state::metaplex_anchor::TokenMetadata; +use crate::state::TreeConfig; +use crate::{FinalizeTreeWithRoot, FinalizeTreeWithRootBumps}; +use anchor_lang::{prelude::*, system_program::System}; +use spl_account_compression::{program::SplAccountCompression, Noop}; + +#[derive(Accounts)] +pub struct FinalizeTreeWithRootAndCollection<'info> { + #[account( + mut, + seeds = [merkle_tree.key().as_ref()], + bump, + )] + pub tree_authority: Account<'info, TreeConfig>, + #[account(mut)] + /// CHECK: + pub merkle_tree: UncheckedAccount<'info>, + #[account(mut)] + pub payer: Signer<'info>, + pub tree_delegate: Signer<'info>, + pub staker: Signer<'info>, + pub collection_authority: Signer<'info>, + /// CHECK: + pub registrar: UncheckedAccount<'info>, + /// CHECK: + pub voter: UncheckedAccount<'info>, + /// CHECK: + #[account(mut)] + pub fee_receiver: UncheckedAccount<'info>, + /// CHECK: Optional collection authority record PDA. + /// If there is no collecton authority record PDA then + /// this must be the Bubblegum program address. + pub collection_authority_record_pda: UncheckedAccount<'info>, + /// CHECK: This account is checked in the instruction + pub collection_mint: UncheckedAccount<'info>, + #[account(mut)] + pub collection_metadata: Box>, + /// CHECK: This account is checked in the instruction + pub edition_account: UncheckedAccount<'info>, + pub log_wrapper: Program<'info, Noop>, + pub compression_program: Program<'info, SplAccountCompression>, + pub system_program: Program<'info, System>, +} + +pub(crate) fn finalize_tree_with_root_and_collection<'info>( + ctx: Context<'_, '_, '_, 'info, FinalizeTreeWithRootAndCollection<'info>>, + root: [u8; 32], + rightmost_leaf: [u8; 32], + rightmost_index: u32, + metadata_url: String, + metadata_hash: String, +) -> Result<()> { + let mut collection = Some(Collection { + verified: false, + key: ctx.accounts.collection_mint.key(), + }); + process_collection_verification_mpl_only( + &ctx.accounts.collection_metadata, + &ctx.accounts.collection_mint.to_account_info(), + &ctx.accounts.collection_authority.to_account_info(), + &ctx.accounts + .collection_authority_record_pda + .to_account_info(), + &ctx.accounts.edition_account.to_account_info(), + &mut collection, + true, + )?; + let mut accs: FinalizeTreeWithRoot<'info> = ctx.accounts.into(); + let bumps: FinalizeTreeWithRootBumps = ctx.bumps.into(); + let ctx = Context::<'_, '_, '_, 'info, FinalizeTreeWithRoot<'info>>::new( + ctx.program_id, + &mut accs, + ctx.remaining_accounts, + bumps, + ); + crate::processor::finalize_tree_with_root( + ctx, + root, + rightmost_leaf, + rightmost_index, + metadata_url, + metadata_hash, + ) +} + +impl<'info> From<&mut FinalizeTreeWithRootAndCollection<'info>> for FinalizeTreeWithRoot<'info> { + fn from(value: &mut FinalizeTreeWithRootAndCollection<'info>) -> Self { + Self { + tree_authority: value.tree_authority.to_owned(), + merkle_tree: value.merkle_tree.to_owned(), + payer: value.payer.to_owned(), + tree_delegate: value.tree_delegate.to_owned(), + staker: value.staker.to_owned(), + registrar: value.registrar.to_owned(), + voter: value.voter.to_owned(), + fee_receiver: value.fee_receiver.to_owned(), + log_wrapper: value.log_wrapper.to_owned(), + compression_program: value.compression_program.to_owned(), + system_program: value.system_program.to_owned(), + } + } +} + +impl From for FinalizeTreeWithRootBumps { + fn from(value: FinalizeTreeWithRootAndCollectionBumps) -> Self { + Self { + tree_authority: value.tree_authority, + } + } +} diff --git a/programs/bubblegum/program/src/processor/mint_to_collection.rs b/programs/bubblegum/program/src/processor/mint_to_collection.rs index 3f59d0d..bb89ae5 100644 --- a/programs/bubblegum/program/src/processor/mint_to_collection.rs +++ b/programs/bubblegum/program/src/processor/mint_to_collection.rs @@ -105,7 +105,7 @@ pub(crate) fn mint_to_collection_v1( &collection_authority, &collection_authority_record_pda, &edition_account, - &mut message, + &mut message.collection, true, )?; diff --git a/programs/bubblegum/program/src/processor/mod.rs b/programs/bubblegum/program/src/processor/mod.rs index 436fc25..010e86a 100644 --- a/programs/bubblegum/program/src/processor/mod.rs +++ b/programs/bubblegum/program/src/processor/mod.rs @@ -21,6 +21,7 @@ mod create_tree; mod decompress; mod delegate; mod finalize_tree_with_root; +mod finalize_tree_with_root_and_collection; mod mint; mod mint_to_collection; mod prepare_tree; @@ -43,6 +44,7 @@ pub(crate) use create_tree::*; pub(crate) use decompress::*; pub(crate) use delegate::*; pub(crate) use finalize_tree_with_root::*; +pub(crate) use finalize_tree_with_root_and_collection::*; pub(crate) use mint::*; pub(crate) use mint_to_collection::*; pub(crate) use prepare_tree::*; @@ -165,15 +167,16 @@ fn process_collection_verification_mpl_only<'info>( collection_authority: &AccountInfo<'info>, collection_authority_record_pda: &AccountInfo<'info>, edition_account: &AccountInfo<'info>, - message: &mut MetadataArgs, + mut message_collection: &mut Option, verify: bool, ) -> Result<()> { // See if a collection authority record PDA was provided. - let collection_authority_record = if collection_authority_record_pda.key() == crate::id() { - None - } else { - Some(collection_authority_record_pda) - }; + let collection_authority_record: Option<&AccountInfo<'info>> = + if collection_authority_record_pda.key() == crate::id() { + None + } else { + Some(collection_authority_record_pda) + }; // Verify correct account ownerships. require!( @@ -190,7 +193,7 @@ fn process_collection_verification_mpl_only<'info>( ); // If the NFT has collection data, we set it to the correct value after doing some validation. - if let Some(collection) = &mut message.collection { + if let Some(collection) = &mut message_collection { assert_collection_membership( &Some(collection.adapt()), collection_metadata, @@ -271,7 +274,7 @@ fn process_collection_verification<'info>( &collection_authority, &collection_authority_record_pda, &edition_account, - &mut message, + &mut message.collection, verify, )?; diff --git a/programs/bubblegum/program/tests/rollup.rs b/programs/bubblegum/program/tests/rollup.rs index f130f63..f9f557e 100644 --- a/programs/bubblegum/program/tests/rollup.rs +++ b/programs/bubblegum/program/tests/rollup.rs @@ -667,3 +667,148 @@ async fn test_put_wrong_fee_receiver() { panic!("Should have failed"); } } + +#[tokio::test] +async fn test_prepare_tree_with_collection() { + let tree_creator = Keypair::new(); + + let asset_owner = Keypair::new(); + + let num_of_assets_to_mint = 1000; + + let mut program_context = BubblegumTestContext::new().await.unwrap(); + + let mut tree = preinitialize_merkle_tree( + &program_context, + &tree_creator, + &asset_owner, + None, + num_of_assets_to_mint, + ) + .await; + + let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); + let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); + + let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + + program_context + .fund_account(tree.creator_pubkey(), 10_000_000_000) + .await + .unwrap(); + program_context + .fund_account(FEE_RECEIVER, 10_000_000_000) + .await + .unwrap(); + + let mut tree_tx_builder = tree.prepare_tree_tx( + &program_context.test_context().payer, + &tree_creator, + false, + MAX_DEPTH as u32, + MAX_BUF_SIZE as u32, + ); + + tree_tx_builder.execute_without_root_check().await.unwrap(); + + let mut tree_tx_builder = tree.finalize_tree_with_root_and_collection_tx( + &program_context.payer(), + &program_context.default_collection, + &program_context.test_context().payer, + &tree_creator, + tree.expected_root(), + rightmost_leaf, + 999, + "http://some-url.com".to_string(), + "fileHash".to_string(), + registrar_key, + voter_key, + FEE_RECEIVER, + ); + + for proof in rightmost_proof { + tree_tx_builder.additional_accounts.push(AccountMeta { + pubkey: Pubkey::new_from_array(proof), + is_signer: false, + is_writable: false, + }); + } + + tree_tx_builder.execute().await.unwrap(); +} + +#[tokio::test] +async fn test_prepare_tree_with_collection_wrong_authority() { + let tree_creator = Keypair::new(); + + let asset_owner = Keypair::new(); + + let num_of_assets_to_mint = 1000; + + let mut program_context = BubblegumTestContext::new().await.unwrap(); + + let mut tree = preinitialize_merkle_tree( + &program_context, + &tree_creator, + &asset_owner, + None, + num_of_assets_to_mint, + ) + .await; + + let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); + let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); + + let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + + program_context + .fund_account(tree.creator_pubkey(), 10_000_000_000) + .await + .unwrap(); + program_context + .fund_account(FEE_RECEIVER, 10_000_000_000) + .await + .unwrap(); + + let mut tree_tx_builder = tree.prepare_tree_tx( + &program_context.test_context().payer, + &tree_creator, + false, + MAX_DEPTH as u32, + MAX_BUF_SIZE as u32, + ); + + tree_tx_builder.execute_without_root_check().await.unwrap(); + + let mut tree_tx_builder = tree.finalize_tree_with_root_and_collection_tx( + &tree_creator, + &program_context.default_collection, + &program_context.test_context().payer, + &tree_creator, + tree.expected_root(), + rightmost_leaf, + 999, + "http://some-url.com".to_string(), + "fileHash".to_string(), + registrar_key, + voter_key, + FEE_RECEIVER, + ); + + let res = tree_tx_builder.execute().await; + if let Err(err) = res { + if let BanksClient(BanksClientError::TransactionError(e)) = *err { + assert_eq!( + e, + TransactionError::InstructionError( + 0, + InstructionError::Custom(BubblegumError::InvalidCollectionAuthority.into()), + ) + ); + } else { + panic!("Wrong variant"); + } + } else { + panic!("Should have failed"); + } +} diff --git a/programs/bubblegum/program/tests/utils/tree.rs b/programs/bubblegum/program/tests/utils/tree.rs index 3bfd38b..77893af 100644 --- a/programs/bubblegum/program/tests/utils/tree.rs +++ b/programs/bubblegum/program/tests/utils/tree.rs @@ -10,7 +10,8 @@ use super::{ }, Error, LeafArgs, Result, }; -use crate::utils::tx_builder::DecompressV1Builder; +use crate::utils::digital_asset::DigitalAsset; +use crate::utils::tx_builder::{DecompressV1Builder, FinalizeWithRootAndCollectionBuilder}; use anchor_lang::{self, AccountDeserialize}; use bubblegum::{ state::{leaf_schema::LeafSchema, DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX}, @@ -277,7 +278,7 @@ impl Tree Tree Tree Tree FinalizeWithRootAndCollectionBuilder { + let tree_authority = + Pubkey::find_program_address(&[self.tree_pubkey().as_ref()], &bubblegum::id()).0; + + let accounts = bubblegum::accounts::FinalizeTreeWithRootAndCollection { + tree_authority, + merkle_tree: self.tree_pubkey(), + staker: payer.pubkey(), // TODO: this should be a separate account in a general case + tree_delegate: tree_delegate.pubkey(), + payer: payer.pubkey(), + registrar, + voter, + collection_authority: collection_authority.pubkey(), + collection_authority_record_pda: bubblegum::id(), + collection_mint: collection.mint.pubkey(), + fee_receiver, + log_wrapper: spl_noop::id(), + compression_program: spl_account_compression::id(), + system_program: system_program::id(), + collection_metadata: collection.metadata, + edition_account: collection.edition.unwrap(), + }; + + let data = bubblegum::instruction::FinalizeTreeWithRootAndCollection { + root, + rightmost_leaf, + rightmost_index, + metadata_url, + metadata_hash, + }; + + self.tx_builder( + accounts, + data, + None, + (), + payer.pubkey(), + &[payer, tree_delegate, collection_authority], + ) + } + pub fn add_canopy_tx( &mut self, payer: &Keypair, diff --git a/programs/bubblegum/program/tests/utils/tx_builder.rs b/programs/bubblegum/program/tests/utils/tx_builder.rs index 7a45c7f..4071407 100644 --- a/programs/bubblegum/program/tests/utils/tx_builder.rs +++ b/programs/bubblegum/program/tests/utils/tx_builder.rs @@ -173,6 +173,19 @@ pub type FinalizeWithRootBuilder<'a, const MAX_DEPTH: usize, const MAX_BUFFER_SI MAX_BUFFER_SIZE, >; +pub type FinalizeWithRootAndCollectionBuilder< + 'a, + const MAX_DEPTH: usize, + const MAX_BUFFER_SIZE: usize, +> = TxBuilder< + 'a, + bubblegum::accounts::FinalizeTreeWithRootAndCollection, + bubblegum::instruction::FinalizeTreeWithRootAndCollection, + (), + MAX_DEPTH, + MAX_BUFFER_SIZE, +>; + impl<'a, const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize> OnSuccessfulTxExec for FinalizeWithRootBuilder<'a, MAX_DEPTH, MAX_BUFFER_SIZE> { @@ -182,6 +195,15 @@ impl<'a, const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize> OnSuccessfulTxExe } } +impl<'a, const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize> OnSuccessfulTxExec + for FinalizeWithRootAndCollectionBuilder<'a, MAX_DEPTH, MAX_BUFFER_SIZE> +{ + fn on_successful_execute(&mut self) -> Result<()> { + // Do nothing here. + Ok(()) + } +} + pub type AddCanopyBuilder<'a, const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize> = TxBuilder< 'a, bubblegum::accounts::AddCanopy,