From bd42fa834b69eef46973c9419f7c123d71e7fa03 Mon Sep 17 00:00:00 2001 From: requesco Date: Fri, 5 Jul 2024 10:42:19 +0300 Subject: [PATCH 1/6] feat: finalize tree with root and collection --- .../finalizeTreeWithRootAndCollection.ts | 261 +++++ .../js/src/generated/instructions/index.ts | 1 + .../finalize_tree_with_root_and_collection.rs | 1023 +++++++++++++++++ .../rust/src/generated/instructions/mod.rs | 2 + idls/bubblegum.json | 124 ++ programs/bubblegum/program/src/lib.rs | 21 + .../src/processor/finalize_tree_with_root.rs | 4 +- .../finalize_tree_with_root_and_collection.rs | 147 +++ .../bubblegum/program/src/processor/mod.rs | 2 + programs/bubblegum/program/tests/rollup.rs | 411 ++++++- .../bubblegum/program/tests/utils/tree.rs | 58 +- .../program/tests/utils/tx_builder.rs | 22 + 12 files changed, 2059 insertions(+), 17 deletions(-) create mode 100644 clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts create mode 100644 clients/rust/src/generated/instructions/finalize_tree_with_root_and_collection.rs create mode 100644 programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs diff --git a/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts b/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts new file mode 100644 index 0000000..99c5627 --- /dev/null +++ b/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts @@ -0,0 +1,261 @@ +/** + * 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; + incomingTreeDelegate: Signer; + staker: Signer; + registrar: PublicKey | Pda; + voter: PublicKey | Pda; + feeReceiver: PublicKey | Pda; + collectionAuthority?: Signer; + /** + * 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; + rightmostRoot: Uint8Array; + rightmostLeaf: Uint8Array; + rightmostIndex: number; + metadataUrl: string; + metadataHash: string; +}; + +export type FinalizeTreeWithRootAndCollectionInstructionDataArgs = { + rightmostRoot: 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 })], + ['rightmostRoot', 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 }, + incomingTreeDelegate: { + index: 3, + isWritable: false, + value: input.incomingTreeDelegate ?? null, + }, + staker: { index: 4, isWritable: false, value: input.staker ?? null }, + registrar: { index: 5, isWritable: false, value: input.registrar ?? null }, + voter: { index: 6, isWritable: false, value: input.voter ?? null }, + feeReceiver: { + index: 7, + isWritable: true, + value: input.feeReceiver ?? null, + }, + collectionAuthority: { + index: 8, + isWritable: false, + value: input.collectionAuthority ?? 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.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/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..ab7d03d --- /dev/null +++ b/clients/rust/src/generated/instructions/finalize_tree_with_root_and_collection.rs @@ -0,0 +1,1023 @@ +//! 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 incoming_tree_delegate: solana_program::pubkey::Pubkey, + + pub staker: solana_program::pubkey::Pubkey, + + pub registrar: solana_program::pubkey::Pubkey, + + pub voter: solana_program::pubkey::Pubkey, + + pub fee_receiver: solana_program::pubkey::Pubkey, + + pub collection_authority: 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.incoming_tree_delegate, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.staker, + 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, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.collection_authority, + true, + )); + 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 rightmost_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, + incoming_tree_delegate: Option, + staker: Option, + registrar: Option, + voter: Option, + fee_receiver: Option, + collection_authority: Option, + collection_authority_record_pda: Option, + collection_mint: Option, + collection_metadata: Option, + collection_edition: Option, + log_wrapper: Option, + compression_program: Option, + system_program: Option, + rightmost_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 incoming_tree_delegate( + &mut self, + incoming_tree_delegate: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.incoming_tree_delegate = Some(incoming_tree_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 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 + } + #[inline(always)] + pub fn collection_authority( + &mut self, + collection_authority: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.collection_authority = Some(collection_authority); + 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 rightmost_root(&mut self, rightmost_root: [u8; 32]) -> &mut Self { + self.rightmost_root = Some(rightmost_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"), + incoming_tree_delegate: self + .incoming_tree_delegate + .expect("incoming_tree_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"), + fee_receiver: self.fee_receiver.expect("fee_receiver is not set"), + collection_authority: self + .collection_authority + .expect("collection_authority 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 { + rightmost_root: self + .rightmost_root + .clone() + .expect("rightmost_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 incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + + pub staker: &'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>, + + pub collection_authority: &'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 incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + + pub staker: &'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>, + + pub collection_authority: &'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, + incoming_tree_delegate: accounts.incoming_tree_delegate, + staker: accounts.staker, + registrar: accounts.registrar, + voter: accounts.voter, + fee_receiver: accounts.fee_receiver, + collection_authority: accounts.collection_authority, + 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.incoming_tree_delegate.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.staker.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, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.collection_authority.key, + true, + )); + 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.incoming_tree_delegate.clone()); + account_infos.push(self.staker.clone()); + account_infos.push(self.registrar.clone()); + account_infos.push(self.voter.clone()); + account_infos.push(self.fee_receiver.clone()); + account_infos.push(self.collection_authority.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, + incoming_tree_delegate: None, + staker: None, + registrar: None, + voter: None, + fee_receiver: None, + collection_authority: None, + collection_authority_record_pda: None, + collection_mint: None, + collection_metadata: None, + collection_edition: None, + log_wrapper: None, + compression_program: None, + system_program: None, + rightmost_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 incoming_tree_delegate( + &mut self, + incoming_tree_delegate: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.incoming_tree_delegate = Some(incoming_tree_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 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 + } + #[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 + } + /// `[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 rightmost_root(&mut self, rightmost_root: [u8; 32]) -> &mut Self { + self.instruction.rightmost_root = Some(rightmost_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 { + rightmost_root: self + .instruction + .rightmost_root + .clone() + .expect("rightmost_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"), + + incoming_tree_delegate: self + .instruction + .incoming_tree_delegate + .expect("incoming_tree_delegate is not set"), + + staker: self.instruction.staker.expect("staker 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: self + .instruction + .collection_authority + .expect("collection_authority 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>>, + incoming_tree_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>>, + fee_receiver: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection_authority: 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>>, + rightmost_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/idls/bubblegum.json b/idls/bubblegum.json index 8a27cf5..0204c28 100644 --- a/idls/bubblegum.json +++ b/idls/bubblegum.json @@ -474,6 +474,127 @@ } ] }, + { + "name": "finalizeTreeWithRootAndCollection", + "accounts": [ + { + "name": "treeAuthority", + "isMut": true, + "isSigner": false + }, + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "incomingTreeDelegate", + "isMut": false, + "isSigner": true + }, + { + "name": "staker", + "isMut": false, + "isSigner": true + }, + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": false, + "isSigner": false + }, + { + "name": "feeReceiver", + "isMut": true, + "isSigner": false + }, + { + "name": "collectionAuthority", + "isMut": false, + "isSigner": true + }, + { + "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": "rightmostRoot", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "rightmostLeaf", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "rightmostIndex", + "type": "u32" + }, + { + "name": "metadataUrl", + "type": "string" + }, + { + "name": "metadataHash", + "type": "string" + } + ] + }, { "name": "decompressV1", "docs": [ @@ -2274,6 +2395,9 @@ { "name": "FinalizeTreeWithRoot" }, + { + "name": "FinalizeTreeWithRootAndCollection" + }, { "name": "PrepareTree" }, diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 0337f54..958d75c 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,8 @@ 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, + // TODO: add correct value + [77, 73, 220, 153, 126, 225, 64, 204] => 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, @@ -149,6 +152,24 @@ pub mod bubblegum { ) } + pub(crate) fn finalize_tree_with_root_and_collection<'info>( + ctx: Context<'_, '_, '_, 'info, FinalizeTreeWithRootAndCollection<'info>>, + rightmost_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, + rightmost_root, + rightmost_leaf, + rightmost_index, + metadata_url, + metadata_hash, + ) + } + /// Decompresses a leaf node from the tree. pub fn decompress_v1(ctx: Context, metadata: MetadataArgs) -> Result<()> { processor::decompress_v1(ctx, metadata) 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 2968fac..46fa3b5 100644 --- a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs @@ -104,7 +104,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>, @@ -196,7 +196,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..ab5f0f0 --- /dev/null +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs @@ -0,0 +1,147 @@ +use crate::asserts::assert_has_collection_authority; +use crate::processor::{check_stake, finalize_tree}; +use crate::state::metaplex_anchor::TokenMetadata; +use crate::{error::BubblegumError, state::TreeConfig}; +use anchor_lang::{prelude::*, system_program::System}; +use mpl_token_metadata::types::TokenStandard; +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 incoming_tree_delegate: Signer<'info>, + pub staker: Signer<'info>, + /// CHECK: + pub registrar: UncheckedAccount<'info>, + /// CHECK: + pub voter: UncheckedAccount<'info>, + /// CHECK: + #[account(mut)] + pub fee_receiver: UncheckedAccount<'info>, + pub collection_authority: Signer<'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<()> { + // TODO: charge protocol fees + + check_stake( + &ctx.accounts.staker.to_account_info(), + &ctx.accounts.registrar.to_account_info(), + &ctx.accounts.voter.to_account_info(), + )?; + + let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); + let seed = merkle_tree.key(); + let seeds = &[seed.as_ref(), &[ctx.bumps.tree_authority]]; + + let authority = &mut ctx.accounts.tree_authority; + authority.set_inner(TreeConfig { + tree_delegate: authority.tree_creator, + num_minted: (rightmost_index + 1) as u64, + ..**authority + }); + let authority_pda_signer = &[&seeds[..]]; + + validate_collection( + &ctx.accounts.collection_metadata, + &ctx.accounts.collection_authority.to_account_info(), + &ctx.accounts.collection_mint.to_account_info(), + &ctx.accounts + .collection_authority_record_pda + .to_account_info(), + &ctx.accounts.edition_account.to_account_info(), + )?; + + finalize_tree( + root, + rightmost_leaf, + rightmost_index, + &ctx.accounts.compression_program.to_account_info(), + &ctx.accounts.tree_authority.to_account_info(), + &merkle_tree, + &ctx.accounts.log_wrapper.to_account_info(), + authority_pda_signer, + ctx.remaining_accounts, + ) +} + +fn validate_collection( + collection_metadata: &Account, + collection_authority: &AccountInfo, + collection_mint: &AccountInfo, + collection_authority_record_pda: &AccountInfo, + edition_account: &AccountInfo, +) -> Result<()> { + let collection_authority_record = if collection_authority_record_pda.key() == crate::id() { + None + } else { + Some(collection_authority_record_pda) + }; + + // Verify correct account ownerships. + require!( + *collection_metadata.to_account_info().owner == mpl_token_metadata::ID, + BubblegumError::IncorrectOwner + ); + require!( + *collection_mint.owner == spl_token::id(), + BubblegumError::IncorrectOwner + ); + + assert_has_collection_authority( + collection_metadata, + collection_mint.key, + collection_authority.key, + collection_authority_record, + )?; + + let (expected, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(collection_mint.key); + + if edition_account.key != &expected { + return Err(BubblegumError::CollectionMasterEditionAccountInvalid.into()); + } + + let edition = mpl_token_metadata::accounts::MasterEdition::try_from(edition_account) + .map_err(|_err| BubblegumError::CollectionMustBeAUniqueMasterEdition)?; + + match collection_metadata.token_standard { + Some(TokenStandard::NonFungible) | Some(TokenStandard::ProgrammableNonFungible) => (), + _ => return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into()), + } + + if edition.max_supply != Some(0) { + return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into()); + } + + Ok(()) +} diff --git a/programs/bubblegum/program/src/processor/mod.rs b/programs/bubblegum/program/src/processor/mod.rs index 436fc25..500e35f 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::*; diff --git a/programs/bubblegum/program/tests/rollup.rs b/programs/bubblegum/program/tests/rollup.rs index 5b7e845..265cf16 100644 --- a/programs/bubblegum/program/tests/rollup.rs +++ b/programs/bubblegum/program/tests/rollup.rs @@ -983,20 +983,6 @@ async fn test_put_wrong_fee_receiver() { .fund_account(FEE_RECEIVER, 10_000_000_000) .await .unwrap(); - let start_fee_receiver_balance = program_context - .client() - .get_account(FEE_RECEIVER) - .await - .unwrap() - .unwrap() - .lamports; - let start_tree_creator = program_context - .client() - .get_account(tree.creator_pubkey()) - .await - .unwrap() - .unwrap() - .lamports; let mut tree_tx_builder = tree.prepare_tree_tx( &program_context.test_context().payer, @@ -1047,3 +1033,400 @@ async fn test_put_wrong_fee_receiver() { panic!("Should have failed"); } } + +#[tokio::test] +async fn test_prepare_tree_with_collection() { + // preinitialise offchain tree for rollups + let mut merkle_tree = MerkleTree::new(vec![Node::default(); 1 << MAX_DEPTH].as_slice()); + for (i, node) in MERKLE_TREE_NODES.iter().enumerate() { + merkle_tree.add_leaf(*node, i); + } + + assert_eq!(merkle_tree.get_root(), TREE_ROOT); + + // user + let tree_creator = Keypair::from_bytes(TREE_CREATOR.as_ref()).unwrap(); + + let tree_key = Keypair::from_bytes(TREE_KEY.as_ref()).unwrap(); + + // get test context + let mut program_context = BubblegumTestContext::new().await.unwrap(); + + let governance_program_id = + Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); + let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); + let voter_authority = program_context.test_context().payer.pubkey(); + + let mplx_mint_key = Pubkey::new_unique(); + let grant_authority = Pubkey::new_unique(); + + let registrar_key = Pubkey::find_program_address( + &[ + REALM.to_bytes().as_ref(), + b"registrar".as_ref(), + REALM_GOVERNING_MINT.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ) + .0; + + let (voter_key, voter_bump) = Pubkey::find_program_address( + &[ + registrar_key.to_bytes().as_ref(), + b"voter".as_ref(), + voter_authority.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ); + + // // init structs for Registrar and Voter and fill it in with data + let voting_mint_config = VotingMintConfig { + mint: mplx_mint_key, + grant_authority, + baseline_vote_weight_scaled_factor: 0, + max_extra_lockup_vote_weight_scaled_factor: 0, + lockup_saturation_secs: 0, + digit_shift: 0, + padding: [0, 0, 0, 0, 0, 0, 0], + }; + + let registrar = Registrar { + governance_program_id, + realm: REALM, + realm_governing_token_mint: REALM_GOVERNING_MINT, + realm_authority, + voting_mints: [ + voting_mint_config, + voting_mint_config, + voting_mint_config, + voting_mint_config, + ], + time_offset: 0, + bump: 0, + }; + + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let lockup = Lockup { + start_ts: 0, + end_ts: current_time + 100, + cooldown_ends_at: 0, + cooldown_requested: false, + kind: LockupKind::Constant, + period: LockupPeriod::ThreeMonths, + _reserved1: [0; 5], + }; + + let deposit_entry = DepositEntry { + lockup: lockup.clone(), + amount_deposited_native: 100000000, + voting_mint_config_idx: 0, + is_used: true, + _reserved1: [0; 6], + }; + + let deposit_entries = [deposit_entry; 32]; + + let voter = Voter { + deposits: deposit_entries, + voter_authority, + registrar: registrar_key, + voter_bump, + voter_weight_record_bump: 0, + _reserved1: [0; 14], + }; + + let registrar_acc_data = [ + REGISTRAR_DISCRIMINATOR.as_ref(), + bytemuck::bytes_of(®istrar), + ] + .concat(); + let voter_acc_data = [VOTER_DISCRIMINATOR.as_ref(), bytemuck::bytes_of(&voter)].concat(); + + // for next two accounts set arbitrary balance because it doesn't meter for test + let mut registrar_account = AccountSharedData::new( + 10000000000000000, + registrar_acc_data.len(), + &mplx_staking_states::ID, + ); + registrar_account.set_data_from_slice(registrar_acc_data.as_ref()); + + let mut voter_account = AccountSharedData::new( + 10000000000000000, + voter_acc_data.len(), + &mplx_staking_states::ID, + ); + voter_account.set_data_from_slice(voter_acc_data.as_ref()); + + program_context + .mut_test_context() + .set_account(®istrar_key, ®istrar_account); + program_context + .mut_test_context() + .set_account(&voter_key, &voter_account); + + let mut tree = Tree::::with_preinitialised_tree( + &tree_creator, + &tree_key, + program_context.client(), + merkle_tree, + 1000, + 0, + ); + + tree.alloc(&program_context.test_context().payer) + .await + .unwrap(); + 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_ROOT, + RIGHTMOST_LEAF, + 999, + "http://some-url.com".to_string(), + "fileHash".to_string(), + registrar_key, + voter_key, + FEE_RECEIVER, + ); + + for proof in RIGHTMOST_PROOFS { + 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() { + // preinitialise offchain tree for rollups + let mut merkle_tree = MerkleTree::new(vec![Node::default(); 1 << MAX_DEPTH].as_slice()); + for (i, node) in MERKLE_TREE_NODES.iter().enumerate() { + merkle_tree.add_leaf(*node, i); + } + + assert_eq!(merkle_tree.get_root(), TREE_ROOT); + + // user + let tree_creator = Keypair::from_bytes(TREE_CREATOR.as_ref()).unwrap(); + + let tree_key = Keypair::from_bytes(TREE_KEY.as_ref()).unwrap(); + + // get test context + let mut program_context = BubblegumTestContext::new().await.unwrap(); + + let governance_program_id = + Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); + let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); + let voter_authority = program_context.test_context().payer.pubkey(); + + let mplx_mint_key = Pubkey::new_unique(); + let grant_authority = Pubkey::new_unique(); + + let registrar_key = Pubkey::find_program_address( + &[ + REALM.to_bytes().as_ref(), + b"registrar".as_ref(), + REALM_GOVERNING_MINT.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ) + .0; + + let (voter_key, voter_bump) = Pubkey::find_program_address( + &[ + registrar_key.to_bytes().as_ref(), + b"voter".as_ref(), + voter_authority.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ); + + // // init structs for Registrar and Voter and fill it in with data + let voting_mint_config = VotingMintConfig { + mint: mplx_mint_key, + grant_authority, + baseline_vote_weight_scaled_factor: 0, + max_extra_lockup_vote_weight_scaled_factor: 0, + lockup_saturation_secs: 0, + digit_shift: 0, + padding: [0, 0, 0, 0, 0, 0, 0], + }; + + let registrar = Registrar { + governance_program_id, + realm: REALM, + realm_governing_token_mint: REALM_GOVERNING_MINT, + realm_authority, + voting_mints: [ + voting_mint_config, + voting_mint_config, + voting_mint_config, + voting_mint_config, + ], + time_offset: 0, + bump: 0, + }; + + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let lockup = Lockup { + start_ts: 0, + end_ts: current_time + 100, + cooldown_ends_at: 0, + cooldown_requested: false, + kind: LockupKind::Constant, + period: LockupPeriod::ThreeMonths, + _reserved1: [0; 5], + }; + + let deposit_entry = DepositEntry { + lockup: lockup.clone(), + amount_deposited_native: 100000000, + voting_mint_config_idx: 0, + is_used: true, + _reserved1: [0; 6], + }; + + let deposit_entries = [deposit_entry; 32]; + + let voter = Voter { + deposits: deposit_entries, + voter_authority, + registrar: registrar_key, + voter_bump, + voter_weight_record_bump: 0, + _reserved1: [0; 14], + }; + + let registrar_acc_data = [ + REGISTRAR_DISCRIMINATOR.as_ref(), + bytemuck::bytes_of(®istrar), + ] + .concat(); + let voter_acc_data = [VOTER_DISCRIMINATOR.as_ref(), bytemuck::bytes_of(&voter)].concat(); + + // for next two accounts set arbitrary balance because it doesn't meter for test + let mut registrar_account = AccountSharedData::new( + 10000000000000000, + registrar_acc_data.len(), + &mplx_staking_states::ID, + ); + registrar_account.set_data_from_slice(registrar_acc_data.as_ref()); + + let mut voter_account = AccountSharedData::new( + 10000000000000000, + voter_acc_data.len(), + &mplx_staking_states::ID, + ); + voter_account.set_data_from_slice(voter_acc_data.as_ref()); + + program_context + .mut_test_context() + .set_account(®istrar_key, ®istrar_account); + program_context + .mut_test_context() + .set_account(&voter_key, &voter_account); + + let mut tree = Tree::::with_preinitialised_tree( + &tree_creator, + &tree_key, + program_context.client(), + merkle_tree, + 1000, + 0, + ); + + tree.alloc(&program_context.test_context().payer) + .await + .unwrap(); + 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_ROOT, + RIGHTMOST_LEAF, + 999, + "http://some-url.com".to_string(), + "fileHash".to_string(), + registrar_key, + voter_key, + FEE_RECEIVER, + ); + + for proof in RIGHTMOST_PROOFS { + tree_tx_builder.additional_accounts.push(AccountMeta { + pubkey: Pubkey::new_from_array(proof), + is_signer: false, + is_writable: false, + }); + } + + 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 1a571d2..205f84a 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}, @@ -321,6 +322,61 @@ impl 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 + incoming_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 { + rightmost_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, From 50579ea193bfe92d26e9b5185c55fb3ba513702a Mon Sep 17 00:00:00 2001 From: requesco Date: Tue, 9 Jul 2024 13:01:47 +0300 Subject: [PATCH 2/6] feat: finalize tree with collection --- clients/rust/src/lib.rs | 2 ++ .../src/processor/finalize_tree_with_root_and_collection.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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/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 index ab5f0f0..64a1477 100644 --- 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 @@ -21,6 +21,7 @@ pub struct FinalizeTreeWithRootAndCollection<'info> { pub payer: Signer<'info>, pub incoming_tree_delegate: Signer<'info>, pub staker: Signer<'info>, + pub collection_authority: Signer<'info>, /// CHECK: pub registrar: UncheckedAccount<'info>, /// CHECK: @@ -28,7 +29,6 @@ pub struct FinalizeTreeWithRootAndCollection<'info> { /// CHECK: #[account(mut)] pub fee_receiver: UncheckedAccount<'info>, - pub collection_authority: Signer<'info>, /// CHECK: Optional collection authority record PDA. /// If there is no collecton authority record PDA then /// this must be the Bubblegum program address. From f330635926957a47fd69c5ddd6135f2519a328e3 Mon Sep 17 00:00:00 2001 From: requesco Date: Wed, 10 Jul 2024 10:15:11 +0300 Subject: [PATCH 3/6] comments --- .../instructions/finalizeTreeWithRoot.ts | 17 +- .../finalizeTreeWithRootAndCollection.ts | 33 ++-- .../instructions/finalize_tree_with_root.rs | 71 ++++---- .../finalize_tree_with_root_and_collection.rs | 157 +++++++++--------- idls/bubblegum.json | 18 +- programs/bubblegum/program/src/lib.rs | 11 +- .../src/processor/finalize_tree_with_root.rs | 4 +- .../finalize_tree_with_root_and_collection.rs | 2 +- .../bubblegum/program/tests/utils/tree.rs | 12 +- 9 files changed, 158 insertions(+), 167 deletions(-) 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 index 99c5627..6c503af 100644 --- a/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts +++ b/clients/js/src/generated/instructions/finalizeTreeWithRootAndCollection.ts @@ -41,12 +41,12 @@ export type FinalizeTreeWithRootAndCollectionInstructionAccounts = { treeConfig?: PublicKey | Pda; merkleTree: PublicKey | Pda; payer?: Signer; - incomingTreeDelegate: Signer; + treeCreatorOrDelegate?: Signer; staker: Signer; + collectionAuthority?: Signer; registrar: PublicKey | Pda; voter: PublicKey | Pda; feeReceiver: PublicKey | Pda; - collectionAuthority?: Signer; /** * If there is no collecton authority record PDA then * this must be the Bubblegum program address. @@ -64,7 +64,7 @@ export type FinalizeTreeWithRootAndCollectionInstructionAccounts = { // Data. export type FinalizeTreeWithRootAndCollectionInstructionData = { discriminator: Array; - rightmostRoot: Uint8Array; + root: Uint8Array; rightmostLeaf: Uint8Array; rightmostIndex: number; metadataUrl: string; @@ -72,7 +72,7 @@ export type FinalizeTreeWithRootAndCollectionInstructionData = { }; export type FinalizeTreeWithRootAndCollectionInstructionDataArgs = { - rightmostRoot: Uint8Array; + root: Uint8Array; rightmostLeaf: Uint8Array; rightmostIndex: number; metadataUrl: string; @@ -91,7 +91,7 @@ export function getFinalizeTreeWithRootAndCollectionInstructionDataSerializer(): struct( [ ['discriminator', array(u8(), { size: 8 })], - ['rightmostRoot', bytes({ size: 32 })], + ['root', bytes({ size: 32 })], ['rightmostLeaf', bytes({ size: 32 })], ['rightmostIndex', u32()], ['metadataUrl', string()], @@ -130,24 +130,24 @@ export function finalizeTreeWithRootAndCollection( 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 }, - voter: { index: 6, isWritable: false, value: input.voter ?? null }, - feeReceiver: { - index: 7, - isWritable: true, - value: input.feeReceiver ?? null, - }, collectionAuthority: { - index: 8, + 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, @@ -199,6 +199,9 @@ export function finalizeTreeWithRootAndCollection( 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; } 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 index ab7d03d..922dbc7 100644 --- 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 @@ -16,17 +16,17 @@ pub struct FinalizeTreeWithRootAndCollection { 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, + 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, - - pub collection_authority: 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, @@ -70,13 +70,17 @@ impl FinalizeTreeWithRootAndCollection { 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( 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, @@ -88,10 +92,6 @@ impl FinalizeTreeWithRootAndCollection { self.fee_receiver, false, )); - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - self.collection_authority, - true, - )); if let Some(collection_authority_record_pda) = self.collection_authority_record_pda { accounts.push(solana_program::instruction::AccountMeta::new_readonly( collection_authority_record_pda, @@ -158,7 +158,7 @@ impl FinalizeTreeWithRootAndCollectionInstructionData { #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FinalizeTreeWithRootAndCollectionInstructionArgs { - pub rightmost_root: [u8; 32], + pub root: [u8; 32], pub rightmost_leaf: [u8; 32], pub rightmost_index: u32, pub metadata_url: String, @@ -171,12 +171,12 @@ pub struct FinalizeTreeWithRootAndCollectionBuilder { tree_config: Option, merkle_tree: Option, payer: Option, - incoming_tree_delegate: Option, + tree_creator_or_delegate: Option, staker: Option, + collection_authority: Option, registrar: Option, voter: Option, fee_receiver: Option, - collection_authority: Option, collection_authority_record_pda: Option, collection_mint: Option, collection_metadata: Option, @@ -184,7 +184,7 @@ pub struct FinalizeTreeWithRootAndCollectionBuilder { 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, @@ -212,11 +212,11 @@ impl FinalizeTreeWithRootAndCollectionBuilder { 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)] @@ -225,6 +225,14 @@ impl FinalizeTreeWithRootAndCollectionBuilder { 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 @@ -239,14 +247,6 @@ impl FinalizeTreeWithRootAndCollectionBuilder { self.fee_receiver = Some(fee_receiver); self } - #[inline(always)] - pub fn collection_authority( - &mut self, - collection_authority: solana_program::pubkey::Pubkey, - ) -> &mut Self { - self.collection_authority = Some(collection_authority); - self - } /// `[optional account]` /// If there is no collecton authority record PDA then /// this must be the Bubblegum program address. @@ -304,8 +304,8 @@ impl FinalizeTreeWithRootAndCollectionBuilder { 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)] @@ -352,16 +352,16 @@ impl FinalizeTreeWithRootAndCollectionBuilder { 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"), - fee_receiver: self.fee_receiver.expect("fee_receiver 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 @@ -381,10 +381,7 @@ impl FinalizeTreeWithRootAndCollectionBuilder { .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), }; let args = FinalizeTreeWithRootAndCollectionInstructionArgs { - 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() @@ -412,17 +409,17 @@ pub struct FinalizeTreeWithRootAndCollectionCpiAccounts<'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>, + 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>, - - pub collection_authority: &'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>>, @@ -451,17 +448,17 @@ pub struct FinalizeTreeWithRootAndCollectionCpi<'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>, + 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>, - - pub collection_authority: &'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>>, @@ -492,12 +489,12 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpi<'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, + collection_authority: accounts.collection_authority, registrar: accounts.registrar, voter: accounts.voter, fee_receiver: accounts.fee_receiver, - collection_authority: accounts.collection_authority, collection_authority_record_pda: accounts.collection_authority_record_pda, collection_mint: accounts.collection_mint, collection_metadata: accounts.collection_metadata, @@ -555,13 +552,17 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpi<'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( *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, @@ -574,10 +575,6 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpi<'a, 'b> { *self.fee_receiver.key, false, )); - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *self.collection_authority.key, - true, - )); 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, @@ -636,12 +633,12 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpi<'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.collection_authority.clone()); account_infos.push(self.registrar.clone()); account_infos.push(self.voter.clone()); account_infos.push(self.fee_receiver.clone()); - account_infos.push(self.collection_authority.clone()); if let Some(collection_authority_record_pda) = self.collection_authority_record_pda { account_infos.push(collection_authority_record_pda.clone()); } @@ -675,12 +672,12 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { tree_config: None, merkle_tree: None, payer: None, - incoming_tree_delegate: None, + tree_creator_or_delegate: None, staker: None, + collection_authority: None, registrar: None, voter: None, fee_receiver: None, - collection_authority: None, collection_authority_record_pda: None, collection_mint: None, collection_metadata: None, @@ -688,7 +685,7 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'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, @@ -719,11 +716,11 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'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)] @@ -735,6 +732,14 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { 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>, @@ -755,14 +760,6 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { self.instruction.fee_receiver = Some(fee_receiver); 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 - } /// `[optional account]` /// If there is no collecton authority record PDA then /// this must be the Bubblegum program address. @@ -823,8 +820,8 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'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)] @@ -889,11 +886,7 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { let args = FinalizeTreeWithRootAndCollectionInstructionArgs { - 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 @@ -930,13 +923,18 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'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"), + 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"), @@ -946,11 +944,6 @@ impl<'a, 'b> FinalizeTreeWithRootAndCollectionCpiBuilder<'a, 'b> { .fee_receiver .expect("fee_receiver is not set"), - collection_authority: self - .instruction - .collection_authority - .expect("collection_authority is not set"), - collection_authority_record_pda: self.instruction.collection_authority_record_pda, collection_mint: self @@ -996,12 +989,12 @@ struct FinalizeTreeWithRootAndCollectionCpiBuilderInstruction<'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>>, + 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: 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>>, @@ -1009,7 +1002,7 @@ struct FinalizeTreeWithRootAndCollectionCpiBuilderInstruction<'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/idls/bubblegum.json b/idls/bubblegum.json index 0204c28..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,7 @@ ], "args": [ { - "name": "rightmostRoot", + "name": "root", "type": { "array": [ "u8", @@ -493,7 +493,7 @@ "isSigner": true }, { - "name": "incomingTreeDelegate", + "name": "treeDelegate", "isMut": false, "isSigner": true }, @@ -502,6 +502,11 @@ "isMut": false, "isSigner": true }, + { + "name": "collectionAuthority", + "isMut": false, + "isSigner": true + }, { "name": "registrar", "isMut": false, @@ -517,11 +522,6 @@ "isMut": true, "isSigner": false }, - { - "name": "collectionAuthority", - "isMut": false, - "isSigner": true - }, { "name": "collectionAuthorityRecordPda", "isMut": false, @@ -564,7 +564,7 @@ ], "args": [ { - "name": "rightmostRoot", + "name": "root", "type": { "array": [ "u8", diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 958d75c..27548d7 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -60,8 +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, - // TODO: add correct value - [77, 73, 220, 153, 126, 225, 64, 204] => InstructionName::FinalizeTreeWithRootAndCollection, + [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, @@ -136,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, @@ -144,7 +143,7 @@ pub mod bubblegum { ) -> Result<()> { processor::finalize_tree_with_root( ctx, - rightmost_root, + root, rightmost_leaf, rightmost_index, metadata_url, @@ -154,7 +153,7 @@ pub mod bubblegum { pub(crate) fn finalize_tree_with_root_and_collection<'info>( ctx: Context<'_, '_, '_, 'info, FinalizeTreeWithRootAndCollection<'info>>, - rightmost_root: [u8; 32], + root: [u8; 32], rightmost_leaf: [u8; 32], rightmost_index: u32, metadata_url: String, @@ -162,7 +161,7 @@ pub mod bubblegum { ) -> Result<()> { processor::finalize_tree_with_root_and_collection( ctx, - rightmost_root, + 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 46fa3b5..655be48 100644 --- a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs @@ -24,7 +24,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>, @@ -46,7 +46,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!( 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 index 64a1477..3896f57 100644 --- 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 @@ -19,7 +19,7 @@ pub struct FinalizeTreeWithRootAndCollection<'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>, pub collection_authority: Signer<'info>, /// CHECK: diff --git a/programs/bubblegum/program/tests/utils/tree.rs b/programs/bubblegum/program/tests/utils/tree.rs index 205f84a..2cab101 100644 --- a/programs/bubblegum/program/tests/utils/tree.rs +++ b/programs/bubblegum/program/tests/utils/tree.rs @@ -278,7 +278,7 @@ impl Tree Tree Tree Tree Tree Tree Date: Thu, 11 Jul 2024 10:34:23 +0100 Subject: [PATCH 4/6] reusing the existing code for the finalize tree with collection --- .../finalize_tree_with_root_and_collection.rs | 121 ++++++------------ .../src/processor/mint_to_collection.rs | 2 +- .../bubblegum/program/src/processor/mod.rs | 8 +- 3 files changed, 42 insertions(+), 89 deletions(-) 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 index 3896f57..e1fab68 100644 --- 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 @@ -1,9 +1,9 @@ -use crate::asserts::assert_has_collection_authority; -use crate::processor::{check_stake, finalize_tree}; +use crate::processor::process_collection_verification_mpl_only; +use crate::state::metaplex_adapter::Collection; use crate::state::metaplex_anchor::TokenMetadata; -use crate::{error::BubblegumError, state::TreeConfig}; +use crate::state::TreeConfig; +use crate::{FinalizeTreeWithRoot, FinalizeTreeWithRootBumps}; use anchor_lang::{prelude::*, system_program::System}; -use mpl_token_metadata::types::TokenStandard; use spl_account_compression::{program::SplAccountCompression, Noop}; #[derive(Accounts)] @@ -52,96 +52,49 @@ pub(crate) fn finalize_tree_with_root_and_collection<'info>( _metadata_url: String, _metadata_hash: String, ) -> Result<()> { - // TODO: charge protocol fees - - check_stake( - &ctx.accounts.staker.to_account_info(), - &ctx.accounts.registrar.to_account_info(), - &ctx.accounts.voter.to_account_info(), - )?; - - let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); - let seed = merkle_tree.key(); - let seeds = &[seed.as_ref(), &[ctx.bumps.tree_authority]]; - - let authority = &mut ctx.accounts.tree_authority; - authority.set_inner(TreeConfig { - tree_delegate: authority.tree_creator, - num_minted: (rightmost_index + 1) as u64, - ..**authority + let mut collection = Some(Collection { + verified: false, + key: ctx.accounts.collection_mint.key(), }); - let authority_pda_signer = &[&seeds[..]]; - - validate_collection( + process_collection_verification_mpl_only( &ctx.accounts.collection_metadata, - &ctx.accounts.collection_authority.to_account_info(), &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, )?; - - finalize_tree( + let mut accs: FinalizeTreeWithRoot<'info> = FinalizeTreeWithRoot { + tree_authority: ctx.accounts.tree_authority.to_owned(), + merkle_tree: ctx.accounts.merkle_tree.to_owned(), + payer: ctx.accounts.payer.to_owned(), + tree_delegate: ctx.accounts.tree_delegate.to_owned(), + staker: ctx.accounts.staker.to_owned(), + registrar: ctx.accounts.registrar.to_owned(), + voter: ctx.accounts.voter.to_owned(), + fee_receiver: ctx.accounts.fee_receiver.to_owned(), + log_wrapper: ctx.accounts.log_wrapper.to_owned(), + compression_program: ctx.accounts.compression_program.to_owned(), + system_program: ctx.accounts.system_program.to_owned(), + }; + let bumps = FinalizeTreeWithRootBumps { + tree_authority: ctx.bumps.tree_authority, + }; + 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, - &ctx.accounts.compression_program.to_account_info(), - &ctx.accounts.tree_authority.to_account_info(), - &merkle_tree, - &ctx.accounts.log_wrapper.to_account_info(), - authority_pda_signer, - ctx.remaining_accounts, + _metadata_url, + _metadata_hash, ) } - -fn validate_collection( - collection_metadata: &Account, - collection_authority: &AccountInfo, - collection_mint: &AccountInfo, - collection_authority_record_pda: &AccountInfo, - edition_account: &AccountInfo, -) -> Result<()> { - let collection_authority_record = if collection_authority_record_pda.key() == crate::id() { - None - } else { - Some(collection_authority_record_pda) - }; - - // Verify correct account ownerships. - require!( - *collection_metadata.to_account_info().owner == mpl_token_metadata::ID, - BubblegumError::IncorrectOwner - ); - require!( - *collection_mint.owner == spl_token::id(), - BubblegumError::IncorrectOwner - ); - - assert_has_collection_authority( - collection_metadata, - collection_mint.key, - collection_authority.key, - collection_authority_record, - )?; - - let (expected, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(collection_mint.key); - - if edition_account.key != &expected { - return Err(BubblegumError::CollectionMasterEditionAccountInvalid.into()); - } - - let edition = mpl_token_metadata::accounts::MasterEdition::try_from(edition_account) - .map_err(|_err| BubblegumError::CollectionMustBeAUniqueMasterEdition)?; - - match collection_metadata.token_standard { - Some(TokenStandard::NonFungible) | Some(TokenStandard::ProgrammableNonFungible) => (), - _ => return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into()), - } - - if edition.max_supply != Some(0) { - return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into()); - } - - Ok(()) -} 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 500e35f..fad2fb7 100644 --- a/programs/bubblegum/program/src/processor/mod.rs +++ b/programs/bubblegum/program/src/processor/mod.rs @@ -167,11 +167,11 @@ 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() { + let collection_authority_record: Option<&AccountInfo<'info>> = if collection_authority_record_pda.key() == crate::id() { None } else { Some(collection_authority_record_pda) @@ -192,7 +192,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, @@ -273,7 +273,7 @@ fn process_collection_verification<'info>( &collection_authority, &collection_authority_record_pda, &edition_account, - &mut message, + &mut message.collection, verify, )?; From f041a3c9c7b9e1487a44c96ac1829220a159e982 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 11 Jul 2024 12:39:03 +0100 Subject: [PATCH 5/6] nit: using more of From and marking varable as used as it's passed down the call stack --- .../finalize_tree_with_root_and_collection.rs | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) 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 index e1fab68..2e4d344 100644 --- 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 @@ -49,8 +49,8 @@ pub(crate) fn finalize_tree_with_root_and_collection<'info>( root: [u8; 32], rightmost_leaf: [u8; 32], rightmost_index: u32, - _metadata_url: String, - _metadata_hash: String, + metadata_url: String, + metadata_hash: String, ) -> Result<()> { let mut collection = Some(Collection { verified: false, @@ -67,22 +67,8 @@ pub(crate) fn finalize_tree_with_root_and_collection<'info>( &mut collection, true, )?; - let mut accs: FinalizeTreeWithRoot<'info> = FinalizeTreeWithRoot { - tree_authority: ctx.accounts.tree_authority.to_owned(), - merkle_tree: ctx.accounts.merkle_tree.to_owned(), - payer: ctx.accounts.payer.to_owned(), - tree_delegate: ctx.accounts.tree_delegate.to_owned(), - staker: ctx.accounts.staker.to_owned(), - registrar: ctx.accounts.registrar.to_owned(), - voter: ctx.accounts.voter.to_owned(), - fee_receiver: ctx.accounts.fee_receiver.to_owned(), - log_wrapper: ctx.accounts.log_wrapper.to_owned(), - compression_program: ctx.accounts.compression_program.to_owned(), - system_program: ctx.accounts.system_program.to_owned(), - }; - let bumps = FinalizeTreeWithRootBumps { - tree_authority: ctx.bumps.tree_authority, - }; + 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, @@ -94,7 +80,33 @@ pub(crate) fn finalize_tree_with_root_and_collection<'info>( root, rightmost_leaf, rightmost_index, - _metadata_url, - _metadata_hash, + 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, + } + } +} From b369e2ff7df48ad3589c6ea49307c9a90428c624 Mon Sep 17 00:00:00 2001 From: requesco Date: Tue, 16 Jul 2024 10:59:15 +0300 Subject: [PATCH 6/6] comments --- clients/rust/Cargo.lock | 10 +- programs/bubblegum/Cargo.lock | 2 +- .../bubblegum/program/src/processor/mod.rs | 11 +- programs/bubblegum/program/tests/rollup.rs | 314 ++---------------- 4 files changed, 43 insertions(+), 294 deletions(-) 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/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/processor/mod.rs b/programs/bubblegum/program/src/processor/mod.rs index fad2fb7..010e86a 100644 --- a/programs/bubblegum/program/src/processor/mod.rs +++ b/programs/bubblegum/program/src/processor/mod.rs @@ -171,11 +171,12 @@ fn process_collection_verification_mpl_only<'info>( verify: bool, ) -> Result<()> { // See if a collection authority record PDA was provided. - let collection_authority_record: Option<&AccountInfo<'info>> = 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!( diff --git a/programs/bubblegum/program/tests/rollup.rs b/programs/bubblegum/program/tests/rollup.rs index 1e2b847..f9f557e 100644 --- a/programs/bubblegum/program/tests/rollup.rs +++ b/programs/bubblegum/program/tests/rollup.rs @@ -670,150 +670,28 @@ async fn test_put_wrong_fee_receiver() { #[tokio::test] async fn test_prepare_tree_with_collection() { - // preinitialise offchain tree for rollups - let mut merkle_tree = MerkleTree::new(vec![Node::default(); 1 << MAX_DEPTH].as_slice()); - for (i, node) in MERKLE_TREE_NODES.iter().enumerate() { - merkle_tree.add_leaf(*node, i); - } - - assert_eq!(merkle_tree.get_root(), TREE_ROOT); + let tree_creator = Keypair::new(); - // user - let tree_creator = Keypair::from_bytes(TREE_CREATOR.as_ref()).unwrap(); + let asset_owner = Keypair::new(); - let tree_key = Keypair::from_bytes(TREE_KEY.as_ref()).unwrap(); + let num_of_assets_to_mint = 1000; - // get test context let mut program_context = BubblegumTestContext::new().await.unwrap(); - let governance_program_id = - Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); - let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); - let voter_authority = program_context.test_context().payer.pubkey(); - - let mplx_mint_key = Pubkey::new_unique(); - let grant_authority = Pubkey::new_unique(); - - let registrar_key = Pubkey::find_program_address( - &[ - REALM.to_bytes().as_ref(), - b"registrar".as_ref(), - REALM_GOVERNING_MINT.to_bytes().as_ref(), - ], - &mplx_staking_states::ID, + let mut tree = preinitialize_merkle_tree( + &program_context, + &tree_creator, + &asset_owner, + None, + num_of_assets_to_mint, ) - .0; - - let (voter_key, voter_bump) = Pubkey::find_program_address( - &[ - registrar_key.to_bytes().as_ref(), - b"voter".as_ref(), - voter_authority.to_bytes().as_ref(), - ], - &mplx_staking_states::ID, - ); - - // // init structs for Registrar and Voter and fill it in with data - let voting_mint_config = VotingMintConfig { - mint: mplx_mint_key, - grant_authority, - baseline_vote_weight_scaled_factor: 0, - max_extra_lockup_vote_weight_scaled_factor: 0, - lockup_saturation_secs: 0, - digit_shift: 0, - padding: [0, 0, 0, 0, 0, 0, 0], - }; - - let registrar = Registrar { - governance_program_id, - realm: REALM, - realm_governing_token_mint: REALM_GOVERNING_MINT, - realm_authority, - voting_mints: [ - voting_mint_config, - voting_mint_config, - voting_mint_config, - voting_mint_config, - ], - time_offset: 0, - bump: 0, - }; - - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - - let lockup = Lockup { - start_ts: 0, - end_ts: current_time + 100, - cooldown_ends_at: 0, - cooldown_requested: false, - kind: LockupKind::Constant, - period: LockupPeriod::ThreeMonths, - _reserved1: [0; 5], - }; - - let deposit_entry = DepositEntry { - lockup: lockup.clone(), - amount_deposited_native: 100000000, - voting_mint_config_idx: 0, - is_used: true, - _reserved1: [0; 6], - }; - - let deposit_entries = [deposit_entry; 32]; - - let voter = Voter { - deposits: deposit_entries, - voter_authority, - registrar: registrar_key, - voter_bump, - voter_weight_record_bump: 0, - _reserved1: [0; 14], - }; - - let registrar_acc_data = [ - REGISTRAR_DISCRIMINATOR.as_ref(), - bytemuck::bytes_of(®istrar), - ] - .concat(); - let voter_acc_data = [VOTER_DISCRIMINATOR.as_ref(), bytemuck::bytes_of(&voter)].concat(); - - // for next two accounts set arbitrary balance because it doesn't meter for test - let mut registrar_account = AccountSharedData::new( - 10000000000000000, - registrar_acc_data.len(), - &mplx_staking_states::ID, - ); - registrar_account.set_data_from_slice(registrar_acc_data.as_ref()); - - let mut voter_account = AccountSharedData::new( - 10000000000000000, - voter_acc_data.len(), - &mplx_staking_states::ID, - ); - voter_account.set_data_from_slice(voter_acc_data.as_ref()); + .await; - program_context - .mut_test_context() - .set_account(®istrar_key, ®istrar_account); - program_context - .mut_test_context() - .set_account(&voter_key, &voter_account); + 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 mut tree = Tree::::with_preinitialised_tree( - &tree_creator, - &tree_key, - program_context.client(), - merkle_tree, - 1000, - 0, - ); + let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; - tree.alloc(&program_context.test_context().payer) - .await - .unwrap(); program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) .await @@ -838,8 +716,8 @@ async fn test_prepare_tree_with_collection() { &program_context.default_collection, &program_context.test_context().payer, &tree_creator, - TREE_ROOT, - RIGHTMOST_LEAF, + tree.expected_root(), + rightmost_leaf, 999, "http://some-url.com".to_string(), "fileHash".to_string(), @@ -848,7 +726,7 @@ async fn test_prepare_tree_with_collection() { FEE_RECEIVER, ); - for proof in RIGHTMOST_PROOFS { + for proof in rightmost_proof { tree_tx_builder.additional_accounts.push(AccountMeta { pubkey: Pubkey::new_from_array(proof), is_signer: false, @@ -861,150 +739,28 @@ async fn test_prepare_tree_with_collection() { #[tokio::test] async fn test_prepare_tree_with_collection_wrong_authority() { - // preinitialise offchain tree for rollups - let mut merkle_tree = MerkleTree::new(vec![Node::default(); 1 << MAX_DEPTH].as_slice()); - for (i, node) in MERKLE_TREE_NODES.iter().enumerate() { - merkle_tree.add_leaf(*node, i); - } - - assert_eq!(merkle_tree.get_root(), TREE_ROOT); + let tree_creator = Keypair::new(); - // user - let tree_creator = Keypair::from_bytes(TREE_CREATOR.as_ref()).unwrap(); + let asset_owner = Keypair::new(); - let tree_key = Keypair::from_bytes(TREE_KEY.as_ref()).unwrap(); + let num_of_assets_to_mint = 1000; - // get test context let mut program_context = BubblegumTestContext::new().await.unwrap(); - let governance_program_id = - Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); - let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); - let voter_authority = program_context.test_context().payer.pubkey(); - - let mplx_mint_key = Pubkey::new_unique(); - let grant_authority = Pubkey::new_unique(); - - let registrar_key = Pubkey::find_program_address( - &[ - REALM.to_bytes().as_ref(), - b"registrar".as_ref(), - REALM_GOVERNING_MINT.to_bytes().as_ref(), - ], - &mplx_staking_states::ID, + let mut tree = preinitialize_merkle_tree( + &program_context, + &tree_creator, + &asset_owner, + None, + num_of_assets_to_mint, ) - .0; - - let (voter_key, voter_bump) = Pubkey::find_program_address( - &[ - registrar_key.to_bytes().as_ref(), - b"voter".as_ref(), - voter_authority.to_bytes().as_ref(), - ], - &mplx_staking_states::ID, - ); - - // // init structs for Registrar and Voter and fill it in with data - let voting_mint_config = VotingMintConfig { - mint: mplx_mint_key, - grant_authority, - baseline_vote_weight_scaled_factor: 0, - max_extra_lockup_vote_weight_scaled_factor: 0, - lockup_saturation_secs: 0, - digit_shift: 0, - padding: [0, 0, 0, 0, 0, 0, 0], - }; - - let registrar = Registrar { - governance_program_id, - realm: REALM, - realm_governing_token_mint: REALM_GOVERNING_MINT, - realm_authority, - voting_mints: [ - voting_mint_config, - voting_mint_config, - voting_mint_config, - voting_mint_config, - ], - time_offset: 0, - bump: 0, - }; - - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - - let lockup = Lockup { - start_ts: 0, - end_ts: current_time + 100, - cooldown_ends_at: 0, - cooldown_requested: false, - kind: LockupKind::Constant, - period: LockupPeriod::ThreeMonths, - _reserved1: [0; 5], - }; - - let deposit_entry = DepositEntry { - lockup: lockup.clone(), - amount_deposited_native: 100000000, - voting_mint_config_idx: 0, - is_used: true, - _reserved1: [0; 6], - }; - - let deposit_entries = [deposit_entry; 32]; - - let voter = Voter { - deposits: deposit_entries, - voter_authority, - registrar: registrar_key, - voter_bump, - voter_weight_record_bump: 0, - _reserved1: [0; 14], - }; - - let registrar_acc_data = [ - REGISTRAR_DISCRIMINATOR.as_ref(), - bytemuck::bytes_of(®istrar), - ] - .concat(); - let voter_acc_data = [VOTER_DISCRIMINATOR.as_ref(), bytemuck::bytes_of(&voter)].concat(); - - // for next two accounts set arbitrary balance because it doesn't meter for test - let mut registrar_account = AccountSharedData::new( - 10000000000000000, - registrar_acc_data.len(), - &mplx_staking_states::ID, - ); - registrar_account.set_data_from_slice(registrar_acc_data.as_ref()); - - let mut voter_account = AccountSharedData::new( - 10000000000000000, - voter_acc_data.len(), - &mplx_staking_states::ID, - ); - voter_account.set_data_from_slice(voter_acc_data.as_ref()); + .await; - program_context - .mut_test_context() - .set_account(®istrar_key, ®istrar_account); - program_context - .mut_test_context() - .set_account(&voter_key, &voter_account); + 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 mut tree = Tree::::with_preinitialised_tree( - &tree_creator, - &tree_key, - program_context.client(), - merkle_tree, - 1000, - 0, - ); + let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; - tree.alloc(&program_context.test_context().payer) - .await - .unwrap(); program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) .await @@ -1029,8 +785,8 @@ async fn test_prepare_tree_with_collection_wrong_authority() { &program_context.default_collection, &program_context.test_context().payer, &tree_creator, - TREE_ROOT, - RIGHTMOST_LEAF, + tree.expected_root(), + rightmost_leaf, 999, "http://some-url.com".to_string(), "fileHash".to_string(), @@ -1039,14 +795,6 @@ async fn test_prepare_tree_with_collection_wrong_authority() { FEE_RECEIVER, ); - for proof in RIGHTMOST_PROOFS { - tree_tx_builder.additional_accounts.push(AccountMeta { - pubkey: Pubkey::new_from_array(proof), - is_signer: false, - is_writable: false, - }); - } - let res = tree_tx_builder.execute().await; if let Err(err) = res { if let BanksClient(BanksClientError::TransactionError(e)) = *err {