From 9f3d0d42fd7fcf1174ecd158ccdbad75a2ad9b8e Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 14 Oct 2024 03:47:45 -0700 Subject: [PATCH 01/15] Check compression programs at runtime --- programs/bubblegum/program/Cargo.toml | 4 +- programs/bubblegum/program/src/error.rs | 4 + .../bubblegum/program/src/processor/burn.rs | 15 +- .../program/src/processor/cancel_redeem.rs | 16 +- .../program/src/processor/compress.rs | 7 +- .../program/src/processor/create_tree.rs | 73 ++++--- .../program/src/processor/decompress.rs | 8 +- .../program/src/processor/delegate.rs | 20 +- .../bubblegum/program/src/processor/mint.rs | 26 ++- .../src/processor/mint_to_collection.rs | 17 +- .../bubblegum/program/src/processor/mod.rs | 11 +- .../bubblegum/program/src/processor/redeem.rs | 16 +- .../processor/set_and_verify_collection.rs | 7 + .../program/src/processor/transfer.rs | 20 +- .../src/processor/unverify_collection.rs | 7 + .../program/src/processor/unverify_creator.rs | 7 + .../program/src/processor/update_metadata.rs | 21 +- .../src/processor/verify_collection.rs | 18 +- .../program/src/processor/verify_creator.rs | 14 +- .../program/src/state/leaf_schema.rs | 3 +- programs/bubblegum/program/src/utils.rs | 199 +++++++++++++++--- 21 files changed, 393 insertions(+), 120 deletions(-) diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index bc35227a..300afbb9 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -23,11 +23,14 @@ default = [] anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } anchor-spl = "0.29.0" bytemuck = "1.13.0" +mpl-account-compression = { path = "/home/danenbm/Metaplex-Workspace/mpl-account-compression/programs/account-compression", features = ["cpi"] } +mpl-noop = { path = "/home/danenbm/Metaplex-Workspace/mpl-account-compression/programs/noop", features = ["no-entrypoint"] } mpl-token-metadata = "4.1.2" num-traits = "0.2.15" solana-program = "~1.18.15" spl-account-compression = { version = "0.3.1", features = ["cpi"] } spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } +spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } [dev-dependencies] @@ -37,4 +40,3 @@ solana-program-test = "~1.18.15" solana-sdk = "~1.18.15" spl-concurrent-merkle-tree = "0.3.0" spl-merkle-tree-reference = "0.1.0" -spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } diff --git a/programs/bubblegum/program/src/error.rs b/programs/bubblegum/program/src/error.rs index 8b4fdce5..71ed4c56 100644 --- a/programs/bubblegum/program/src/error.rs +++ b/programs/bubblegum/program/src/error.rs @@ -88,6 +88,10 @@ pub enum BubblegumError { InvalidTokenStandard, #[msg("Canopy size should be set bigger for this tree")] InvalidCanopySize, + #[msg("Invalid log wrapper program")] + InvalidLogWrapper, + #[msg("Invalid compression program")] + InvalidCompressionProgram, } // Converts certain Token Metadata errors into Bubblegum equivalents diff --git a/programs/bubblegum/program/src/processor/burn.rs b/programs/bubblegum/program/src/processor/burn.rs index 2bde60e3..3e20b44a 100644 --- a/programs/bubblegum/program/src/processor/burn.rs +++ b/programs/bubblegum/program/src/processor/burn.rs @@ -1,10 +1,10 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Node, Noop}; +use spl_account_compression::Node; use crate::{ error::BubblegumError, state::{leaf_schema::LeafSchema, TreeConfig}, - utils::{get_asset_id, replace_leaf}, + utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -21,8 +21,10 @@ pub struct Burn<'info> { #[account(mut)] /// CHECK: This account is modified in the downstream program pub merkle_tree: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -34,6 +36,11 @@ pub(crate) fn burn<'info>( nonce: u64, index: u32, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; let owner = ctx.accounts.leaf_owner.to_account_info(); let delegate = ctx.accounts.leaf_delegate.to_account_info(); diff --git a/programs/bubblegum/program/src/processor/cancel_redeem.rs b/programs/bubblegum/program/src/processor/cancel_redeem.rs index 67862d9e..70373059 100644 --- a/programs/bubblegum/program/src/processor/cancel_redeem.rs +++ b/programs/bubblegum/program/src/processor/cancel_redeem.rs @@ -1,11 +1,10 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop}; use crate::{ asserts::assert_pubkey_equal, error::BubblegumError, state::{leaf_schema::LeafSchema, TreeConfig, Voucher, VOUCHER_PREFIX}, - utils::replace_leaf, + utils::{replace_leaf, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -32,8 +31,10 @@ pub struct CancelRedeem<'info> { bump )] pub voucher: Account<'info, Voucher>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -41,6 +42,11 @@ pub(crate) fn cancel_redeem<'info>( ctx: Context<'_, '_, '_, 'info, CancelRedeem<'info>>, root: [u8; 32], ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; let voucher = &ctx.accounts.voucher; match ctx.accounts.voucher.leaf_schema { LeafSchema::V1 { owner, .. } => assert_pubkey_equal( @@ -51,7 +57,7 @@ pub(crate) fn cancel_redeem<'info>( }?; let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); - wrap_application_data_v1( + crate::utils::wrap_application_data_v1( voucher.leaf_schema.to_event().try_to_vec()?, &ctx.accounts.log_wrapper, )?; diff --git a/programs/bubblegum/program/src/processor/compress.rs b/programs/bubblegum/program/src/processor/compress.rs index ca7d9bd6..563e4b01 100644 --- a/programs/bubblegum/program/src/processor/compress.rs +++ b/programs/bubblegum/program/src/processor/compress.rs @@ -1,5 +1,4 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Noop}; use crate::state::metaplex_anchor::{MasterEdition, TokenMetadata}; @@ -30,8 +29,10 @@ pub struct Compress<'info> { pub master_edition: Box>, #[account(mut)] pub payer: Signer<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: + pub compression_program: UncheckedAccount<'info>, /// CHECK: pub token_program: UncheckedAccount<'info>, /// CHECK: diff --git a/programs/bubblegum/program/src/processor/create_tree.rs b/programs/bubblegum/program/src/processor/create_tree.rs index 20151618..73de376c 100644 --- a/programs/bubblegum/program/src/processor/create_tree.rs +++ b/programs/bubblegum/program/src/processor/create_tree.rs @@ -1,17 +1,10 @@ -use bytemuck::cast_slice; - use anchor_lang::{prelude::*, system_program::System}; -use spl_account_compression::{ - program::SplAccountCompression, - state::{ - merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1, - }, - Node, Noop, -}; +use bytemuck::cast_slice; use crate::{ error::BubblegumError, state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, + utils::validate_ownership_and_programs, }; pub const MAX_ACC_PROOFS_SIZE: u32 = 17; @@ -32,8 +25,10 @@ pub struct CreateTree<'info> { #[account(mut)] pub payer: Signer<'info>, pub tree_creator: Signer<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -43,11 +38,17 @@ pub(crate) fn create_tree( max_buffer_size: u32, public: Option, ) -> Result<()> { - let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); - + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + + // Note this uses spl-account-compression to check the canopy size, and is assumed + // to be a valid check for mpl-account-compression. check_canopy_size(&ctx, max_depth, max_buffer_size)?; - let seed = merkle_tree.key(); + let seed = ctx.accounts.merkle_tree.key(); let seeds = &[seed.as_ref(), &[ctx.bumps.tree_authority]]; let authority = &mut ctx.accounts.tree_authority; authority.set_inner(TreeConfig { @@ -59,16 +60,32 @@ pub(crate) fn create_tree( is_decompressible: DecompressibleState::Disabled, }); let authority_pda_signer = &[&seeds[..]]; - let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), - spl_account_compression::cpi::accounts::Initialize { - authority: ctx.accounts.tree_authority.to_account_info(), - merkle_tree, - noop: ctx.accounts.log_wrapper.to_account_info(), - }, - authority_pda_signer, - ); - spl_account_compression::cpi::init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size) + + if ctx.accounts.compression_program.key + == &spl_account_compression::program::SplAccountCompression::id() + { + let cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.compression_program.to_account_info(), + spl_account_compression::cpi::accounts::Initialize { + authority: ctx.accounts.tree_authority.to_account_info(), + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), + noop: ctx.accounts.log_wrapper.to_account_info(), + }, + authority_pda_signer, + ); + spl_account_compression::cpi::init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size) + } else { + let cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.compression_program.to_account_info(), + mpl_account_compression::cpi::accounts::Initialize { + authority: ctx.accounts.tree_authority.to_account_info(), + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), + noop: ctx.accounts.log_wrapper.to_account_info(), + }, + authority_pda_signer, + ); + mpl_account_compression::cpi::init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size) + } } fn check_canopy_size( @@ -76,6 +93,10 @@ fn check_canopy_size( max_depth: u32, max_buffer_size: u32, ) -> Result<()> { + use spl_account_compression::state::{ + merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1, + }; + let merkle_tree_bytes = ctx.accounts.merkle_tree.data.borrow(); let (header_bytes, rest) = merkle_tree_bytes.split_at(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); @@ -92,7 +113,7 @@ fn check_canopy_size( let (_tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size); - let canopy = cast_slice::(canopy_bytes); + let canopy = cast_slice::(canopy_bytes); let cached_path_len = get_cached_path_length(canopy, max_depth)?; @@ -108,7 +129,7 @@ fn check_canopy_size( // Method is taken from account-compression Solana program #[inline(always)] -fn get_cached_path_length(canopy: &[Node], max_depth: u32) -> Result { +fn get_cached_path_length(canopy: &[spl_account_compression::Node], max_depth: u32) -> Result { // The offset of 2 is applied because the canopy is a full binary tree without the root node // Size: (2^n - 2) -> Size + 2 must be a power of 2 let closest_power_of_2 = (canopy.len() + 2) as u32; diff --git a/programs/bubblegum/program/src/processor/decompress.rs b/programs/bubblegum/program/src/processor/decompress.rs index 7ac03803..f155ff67 100644 --- a/programs/bubblegum/program/src/processor/decompress.rs +++ b/programs/bubblegum/program/src/processor/decompress.rs @@ -9,7 +9,6 @@ use solana_program::{ program_pack::Pack, system_instruction, }; -use spl_account_compression::Noop; use spl_token::state::Mint; use crate::{ @@ -20,7 +19,7 @@ use crate::{ metaplex_anchor::MplTokenMetadata, Voucher, ASSET_PREFIX, VOUCHER_PREFIX, }, - utils::{cmp_bytes, cmp_pubkeys, hash_metadata}, + utils::{cmp_bytes, cmp_pubkeys, hash_metadata, validate_log_wrapper_program}, }; #[derive(Accounts)] @@ -71,10 +70,13 @@ pub struct DecompressV1<'info> { pub token_metadata_program: Program<'info, MplTokenMetadata>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, - pub log_wrapper: Program<'info, Noop>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, } pub(crate) fn decompress_v1(ctx: Context, metadata: MetadataArgs) -> Result<()> { + validate_log_wrapper_program(&ctx.accounts.log_wrapper)?; + // Validate the incoming metadata match ctx.accounts.voucher.leaf_schema { diff --git a/programs/bubblegum/program/src/processor/delegate.rs b/programs/bubblegum/program/src/processor/delegate.rs index 8cd70244..8fe5a440 100644 --- a/programs/bubblegum/program/src/processor/delegate.rs +++ b/programs/bubblegum/program/src/processor/delegate.rs @@ -1,9 +1,8 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop}; use crate::{ state::{leaf_schema::LeafSchema, TreeConfig}, - utils::{get_asset_id, replace_leaf}, + utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -22,8 +21,10 @@ pub struct Delegate<'info> { #[account(mut)] /// CHECK: This account is modified in the downstream program pub merkle_tree: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -35,6 +36,12 @@ pub(crate) fn delegate<'info>( nonce: u64, index: u32, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); let owner = ctx.accounts.leaf_owner.key(); let previous_delegate = ctx.accounts.previous_leaf_delegate.key(); @@ -57,7 +64,10 @@ pub(crate) fn delegate<'info>( creator_hash, ); - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + crate::utils::wrap_application_data_v1( + new_leaf.to_event().try_to_vec()?, + &ctx.accounts.log_wrapper, + )?; replace_leaf( &merkle_tree.key(), diff --git a/programs/bubblegum/program/src/processor/mint.rs b/programs/bubblegum/program/src/processor/mint.rs index 67a865b2..c1604023 100644 --- a/programs/bubblegum/program/src/processor/mint.rs +++ b/programs/bubblegum/program/src/processor/mint.rs @@ -1,13 +1,12 @@ use anchor_lang::prelude::*; use solana_program::keccak; -use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop}; use std::collections::HashSet; use crate::{ asserts::{assert_metadata_is_mpl_compatible, assert_metadata_token_standard}, error::BubblegumError, state::{leaf_schema::LeafSchema, metaplex_adapter::MetadataArgs, TreeConfig}, - utils::{append_leaf, get_asset_id}, + utils::{append_leaf, get_asset_id, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -27,13 +26,20 @@ pub struct MintV1<'info> { pub merkle_tree: UncheckedAccount<'info>, pub payer: Signer<'info>, pub tree_delegate: Signer<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } pub(crate) fn mint_v1(ctx: Context, message: MetadataArgs) -> Result { - // TODO -> Separate V1 / V1 into seperate instructions + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + let payer = ctx.accounts.payer.key(); let incoming_tree_delegate = ctx.accounts.tree_delegate.key(); let owner = ctx.accounts.leaf_owner.key(); @@ -94,7 +100,7 @@ pub(crate) fn process_mint_v1<'info>( authority_bump: u8, authority: &mut Account<'info, TreeConfig>, merkle_tree: &AccountInfo<'info>, - wrapper: &Program<'info, Noop>, + wrapper: &AccountInfo<'info>, compression_program: &AccountInfo<'info>, allow_verified_collection: bool, ) -> Result { @@ -150,15 +156,15 @@ pub(crate) fn process_mint_v1<'info>( creator_hash.to_bytes(), ); - wrap_application_data_v1(leaf.to_event().try_to_vec()?, wrapper)?; + crate::utils::wrap_application_data_v1(leaf.to_event().try_to_vec()?, wrapper)?; append_leaf( &merkle_tree.key(), authority_bump, - &compression_program.to_account_info(), + compression_program, &authority.to_account_info(), - &merkle_tree.to_account_info(), - &wrapper.to_account_info(), + merkle_tree, + wrapper, leaf.to_node(), )?; diff --git a/programs/bubblegum/program/src/processor/mint_to_collection.rs b/programs/bubblegum/program/src/processor/mint_to_collection.rs index 3f59d0d6..0e5e9d87 100644 --- a/programs/bubblegum/program/src/processor/mint_to_collection.rs +++ b/programs/bubblegum/program/src/processor/mint_to_collection.rs @@ -1,7 +1,5 @@ -use std::collections::HashSet; - use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Noop}; +use std::collections::HashSet; use crate::{ error::BubblegumError, @@ -9,6 +7,7 @@ use crate::{ leaf_schema::LeafSchema, metaplex_adapter::MetadataArgs, metaplex_anchor::TokenMetadata, TreeConfig, }, + utils::validate_ownership_and_programs, }; use super::{mint::process_mint_v1, process_collection_verification_mpl_only}; @@ -43,8 +42,10 @@ pub struct MintToCollectionV1<'info> { pub edition_account: UncheckedAccount<'info>, /// CHECK: This is no longer needed but kept for backwards compatibility. pub bubblegum_signer: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, /// CHECK: This is no longer needed but kept for backwards compatibility. pub token_metadata_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, @@ -54,6 +55,12 @@ pub(crate) fn mint_to_collection_v1( ctx: Context, metadata_args: MetadataArgs, ) -> Result { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + let mut message = metadata_args; // TODO -> Separate V1 / V1 into seperate instructions let payer = ctx.accounts.payer.key(); diff --git a/programs/bubblegum/program/src/processor/mod.rs b/programs/bubblegum/program/src/processor/mod.rs index e3d5e113..b77c0174 100644 --- a/programs/bubblegum/program/src/processor/mod.rs +++ b/programs/bubblegum/program/src/processor/mod.rs @@ -1,7 +1,6 @@ use anchor_lang::prelude::*; use mpl_token_metadata::types::MetadataDelegateRole; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; -use spl_account_compression::wrap_application_data_v1; use crate::{ asserts::{assert_collection_membership, assert_has_collection_authority}, @@ -136,7 +135,10 @@ fn process_creator_verification<'info>( updated_creator_hash, ); - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + crate::utils::wrap_application_data_v1( + new_leaf.to_event().try_to_vec()?, + &ctx.accounts.log_wrapper, + )?; replace_leaf( &merkle_tree.key(), @@ -293,7 +295,10 @@ fn process_collection_verification<'info>( creator_hash, ); - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + crate::utils::wrap_application_data_v1( + new_leaf.to_event().try_to_vec()?, + &ctx.accounts.log_wrapper, + )?; replace_leaf( &merkle_tree.key(), diff --git a/programs/bubblegum/program/src/processor/redeem.rs b/programs/bubblegum/program/src/processor/redeem.rs index e01605f1..34a79353 100644 --- a/programs/bubblegum/program/src/processor/redeem.rs +++ b/programs/bubblegum/program/src/processor/redeem.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Node, Noop}; +use spl_account_compression::Node; use crate::{ error::BubblegumError, @@ -7,7 +7,7 @@ use crate::{ leaf_schema::LeafSchema, DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX, VOUCHER_SIZE, }, - utils::{get_asset_id, replace_leaf}, + utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -44,8 +44,10 @@ pub struct Redeem<'info> { bump )] pub voucher: Account<'info, Voucher>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -57,6 +59,12 @@ pub(crate) fn redeem<'info>( nonce: u64, index: u32, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + if ctx.accounts.tree_authority.is_decompressible == DecompressibleState::Disabled { return Err(BubblegumError::DecompressionDisabled.into()); } diff --git a/programs/bubblegum/program/src/processor/set_and_verify_collection.rs b/programs/bubblegum/program/src/processor/set_and_verify_collection.rs index 07195923..a751caaf 100644 --- a/programs/bubblegum/program/src/processor/set_and_verify_collection.rs +++ b/programs/bubblegum/program/src/processor/set_and_verify_collection.rs @@ -4,6 +4,7 @@ use crate::{ error::BubblegumError, processor::{process_collection_verification, verify_collection::CollectionVerification}, state::metaplex_adapter::MetadataArgs, + utils::validate_ownership_and_programs, }; pub(crate) fn set_and_verify_collection<'info>( @@ -16,6 +17,12 @@ pub(crate) fn set_and_verify_collection<'info>( message: MetadataArgs, collection: Pubkey, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + let incoming_tree_delegate = &ctx.accounts.tree_delegate; let tree_creator = ctx.accounts.tree_authority.tree_creator; let tree_delegate = ctx.accounts.tree_authority.tree_delegate; diff --git a/programs/bubblegum/program/src/processor/transfer.rs b/programs/bubblegum/program/src/processor/transfer.rs index 1f1d7d24..3a38358c 100644 --- a/programs/bubblegum/program/src/processor/transfer.rs +++ b/programs/bubblegum/program/src/processor/transfer.rs @@ -1,10 +1,9 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop}; use crate::{ error::BubblegumError, state::{leaf_schema::LeafSchema, TreeConfig}, - utils::{get_asset_id, replace_leaf}, + utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; #[derive(Accounts)] @@ -24,8 +23,10 @@ pub struct Transfer<'info> { #[account(mut)] /// CHECK: This account is modified in the downstream program pub merkle_tree: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -37,6 +38,12 @@ pub(crate) fn transfer<'info>( nonce: u64, index: u32, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + // TODO add back version to select hash schema let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); let owner = ctx.accounts.leaf_owner.to_account_info(); @@ -67,7 +74,10 @@ pub(crate) fn transfer<'info>( creator_hash, ); - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + crate::utils::wrap_application_data_v1( + new_leaf.to_event().try_to_vec()?, + &ctx.accounts.log_wrapper, + )?; replace_leaf( &merkle_tree.key(), diff --git a/programs/bubblegum/program/src/processor/unverify_collection.rs b/programs/bubblegum/program/src/processor/unverify_collection.rs index 3f769afe..2d4baf2f 100644 --- a/programs/bubblegum/program/src/processor/unverify_collection.rs +++ b/programs/bubblegum/program/src/processor/unverify_collection.rs @@ -3,6 +3,7 @@ use anchor_lang::prelude::*; use crate::{ processor::{process_collection_verification, verify_collection::CollectionVerification}, state::metaplex_adapter::MetadataArgs, + utils::validate_ownership_and_programs, }; pub(crate) fn unverify_collection<'info>( @@ -14,6 +15,12 @@ pub(crate) fn unverify_collection<'info>( index: u32, message: MetadataArgs, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + process_collection_verification( ctx, root, diff --git a/programs/bubblegum/program/src/processor/unverify_creator.rs b/programs/bubblegum/program/src/processor/unverify_creator.rs index 0c7c4252..08da9165 100644 --- a/programs/bubblegum/program/src/processor/unverify_creator.rs +++ b/programs/bubblegum/program/src/processor/unverify_creator.rs @@ -3,6 +3,7 @@ use anchor_lang::prelude::*; use crate::{ processor::{process_creator_verification, verify_creator::CreatorVerification}, state::metaplex_adapter::MetadataArgs, + utils::validate_ownership_and_programs, }; pub(crate) fn unverify_creator<'info>( @@ -14,6 +15,12 @@ pub(crate) fn unverify_creator<'info>( index: u32, message: MetadataArgs, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + process_creator_verification( ctx, root, diff --git a/programs/bubblegum/program/src/processor/update_metadata.rs b/programs/bubblegum/program/src/processor/update_metadata.rs index 9a7db643..556f3266 100644 --- a/programs/bubblegum/program/src/processor/update_metadata.rs +++ b/programs/bubblegum/program/src/processor/update_metadata.rs @@ -1,6 +1,5 @@ use anchor_lang::prelude::*; use mpl_token_metadata::types::MetadataDelegateRole; -use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop}; use crate::{ asserts::{assert_has_collection_authority, assert_metadata_is_mpl_compatible}, @@ -11,7 +10,9 @@ use crate::{ metaplex_anchor::TokenMetadata, TreeConfig, }, - utils::{get_asset_id, hash_creators, hash_metadata, replace_leaf}, + utils::{ + get_asset_id, hash_creators, hash_metadata, replace_leaf, validate_ownership_and_programs, + }, }; #[derive(Accounts)] @@ -40,8 +41,10 @@ pub struct UpdateMetadata<'info> { #[account(mut)] /// CHECK: This account is modified in the downstream program pub merkle_tree: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, /// CHECK: This is no longer needed but kept for backwards compatibility. pub token_metadata_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, @@ -110,7 +113,7 @@ fn process_update_metadata<'info>( compression_program: &AccountInfo<'info>, tree_authority: &AccountInfo<'info>, tree_authority_bump: u8, - log_wrapper: &Program<'info, Noop>, + log_wrapper: &AccountInfo<'info>, remaining_accounts: &[AccountInfo<'info>], root: [u8; 32], current_metadata: MetadataArgs, @@ -203,7 +206,7 @@ fn process_update_metadata<'info>( updated_creator_hash, ); - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, log_wrapper)?; + crate::utils::wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, log_wrapper)?; replace_leaf( &merkle_tree.key(), @@ -228,6 +231,12 @@ pub fn update_metadata<'info>( current_metadata: MetadataArgs, update_args: UpdateArgs, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + match ¤t_metadata.collection { // Verified collection case. Some(collection) if collection.verified => { diff --git a/programs/bubblegum/program/src/processor/verify_collection.rs b/programs/bubblegum/program/src/processor/verify_collection.rs index 58143754..030e34ba 100644 --- a/programs/bubblegum/program/src/processor/verify_collection.rs +++ b/programs/bubblegum/program/src/processor/verify_collection.rs @@ -1,7 +1,9 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Noop}; -use crate::state::{metaplex_adapter::MetadataArgs, metaplex_anchor::TokenMetadata, TreeConfig}; +use crate::{ + state::{metaplex_adapter::MetadataArgs, metaplex_anchor::TokenMetadata, TreeConfig}, + utils::validate_ownership_and_programs, +}; use super::process_collection_verification; @@ -38,8 +40,10 @@ pub struct CollectionVerification<'info> { pub edition_account: UncheckedAccount<'info>, /// CHECK: This is no longer needed but kept for backwards compatibility. pub bubblegum_signer: UncheckedAccount<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, /// CHECK: This is no longer needed but kept for backwards compatibility. pub token_metadata_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, @@ -54,6 +58,12 @@ pub(crate) fn verify_collection<'info>( index: u32, message: MetadataArgs, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + process_collection_verification( ctx, root, diff --git a/programs/bubblegum/program/src/processor/verify_creator.rs b/programs/bubblegum/program/src/processor/verify_creator.rs index f34c7be8..83e7d4e6 100644 --- a/programs/bubblegum/program/src/processor/verify_creator.rs +++ b/programs/bubblegum/program/src/processor/verify_creator.rs @@ -1,9 +1,9 @@ use anchor_lang::prelude::*; -use spl_account_compression::{program::SplAccountCompression, Noop}; use crate::{ processor::process_creator_verification, state::{metaplex_adapter::MetadataArgs, TreeConfig}, + utils::validate_ownership_and_programs, }; #[derive(Accounts)] @@ -22,8 +22,10 @@ pub struct CreatorVerification<'info> { pub merkle_tree: UncheckedAccount<'info>, pub payer: Signer<'info>, pub creator: Signer<'info>, - pub log_wrapper: Program<'info, Noop>, - pub compression_program: Program<'info, SplAccountCompression>, + /// CHECK: Program is verified in the instruction + pub log_wrapper: UncheckedAccount<'info>, + /// CHECK: Program is verified in the instruction + pub compression_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } @@ -36,6 +38,12 @@ pub(crate) fn verify_creator<'info>( index: u32, message: MetadataArgs, ) -> Result<()> { + validate_ownership_and_programs( + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + )?; + process_creator_verification( ctx, root, diff --git a/programs/bubblegum/program/src/state/leaf_schema.rs b/programs/bubblegum/program/src/state/leaf_schema.rs index 6709efd3..f118c83a 100644 --- a/programs/bubblegum/program/src/state/leaf_schema.rs +++ b/programs/bubblegum/program/src/state/leaf_schema.rs @@ -1,7 +1,6 @@ use crate::state::BubblegumEventType; use anchor_lang::{prelude::*, solana_program::keccak}; use borsh::{BorshDeserialize, BorshSerialize}; -use spl_account_compression::Node; #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct LeafSchemaEvent { @@ -126,7 +125,7 @@ impl LeafSchema { LeafSchemaEvent::new(self.version(), self.clone(), self.to_node()) } - pub fn to_node(&self) -> Node { + pub fn to_node(&self) -> spl_account_compression::Node { let hashed_leaf = match self { LeafSchema::V1 { id, diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index 3dfc915a..76eb513e 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -1,13 +1,17 @@ -use crate::state::{ - metaplex_adapter::{Creator, MetadataArgs}, - ASSET_PREFIX, -}; use anchor_lang::{ prelude::*, - solana_program::{program_memory::sol_memcmp, pubkey::PUBKEY_BYTES}, + solana_program::{program::invoke, program_memory::sol_memcmp, pubkey::PUBKEY_BYTES}, }; use solana_program::keccak; -use spl_account_compression::Node; +use std::str::FromStr; + +use crate::{ + error::BubblegumError, + state::{ + metaplex_adapter::{Creator, MetadataArgs}, + ASSET_PREFIX, + }, +}; pub fn hash_creators(creators: &[Creator]) -> Result<[u8; 32]> { // Convert creator Vec to bytes Vec. @@ -44,24 +48,51 @@ pub fn replace_leaf<'info>( merkle_tree: &AccountInfo<'info>, log_wrapper: &AccountInfo<'info>, remaining_accounts: &[AccountInfo<'info>], - root_node: Node, - previous_leaf: Node, - new_leaf: Node, + root_node: spl_account_compression::Node, + previous_leaf: spl_account_compression::Node, + new_leaf: spl_account_compression::Node, index: u32, ) -> Result<()> { let seeds = &[seed.as_ref(), &[bump]]; let authority_pda_signer = &[&seeds[..]]; - let cpi_ctx = CpiContext::new_with_signer( - compression_program.clone(), - spl_account_compression::cpi::accounts::Modify { - authority: authority.clone(), - merkle_tree: merkle_tree.clone(), - noop: log_wrapper.clone(), - }, - authority_pda_signer, - ) - .with_remaining_accounts(remaining_accounts.to_vec()); - spl_account_compression::cpi::replace_leaf(cpi_ctx, root_node, previous_leaf, new_leaf, index) + + if compression_program.key == &spl_account_compression::program::SplAccountCompression::id() { + let cpi_ctx = CpiContext::new_with_signer( + compression_program.clone(), + spl_account_compression::cpi::accounts::Modify { + authority: authority.clone(), + merkle_tree: merkle_tree.clone(), + noop: log_wrapper.clone(), + }, + authority_pda_signer, + ) + .with_remaining_accounts(remaining_accounts.to_vec()); + spl_account_compression::cpi::replace_leaf( + cpi_ctx, + root_node, + previous_leaf, + new_leaf, + index, + ) + } else { + let cpi_ctx = CpiContext::new_with_signer( + compression_program.clone(), + mpl_account_compression::cpi::accounts::Modify { + authority: authority.clone(), + merkle_tree: merkle_tree.clone(), + noop: log_wrapper.clone(), + }, + authority_pda_signer, + ) + .with_remaining_accounts(remaining_accounts.to_vec()); + mpl_account_compression::cpi::replace_leaf( + cpi_ctx, + root_node, + previous_leaf, + new_leaf, + index, + ) + } } pub fn append_leaf<'info>( @@ -71,20 +102,34 @@ pub fn append_leaf<'info>( authority: &AccountInfo<'info>, merkle_tree: &AccountInfo<'info>, log_wrapper: &AccountInfo<'info>, - leaf_node: Node, + leaf_node: spl_account_compression::Node, ) -> Result<()> { let seeds = &[seed.as_ref(), &[bump]]; let authority_pda_signer = &[&seeds[..]]; - let cpi_ctx = CpiContext::new_with_signer( - compression_program.clone(), - spl_account_compression::cpi::accounts::Modify { - authority: authority.clone(), - merkle_tree: merkle_tree.clone(), - noop: log_wrapper.clone(), - }, - authority_pda_signer, - ); - spl_account_compression::cpi::append(cpi_ctx, leaf_node) + + if compression_program.key == &spl_account_compression::program::SplAccountCompression::id() { + let cpi_ctx = CpiContext::new_with_signer( + compression_program.clone(), + spl_account_compression::cpi::accounts::Modify { + authority: authority.clone(), + merkle_tree: merkle_tree.clone(), + noop: log_wrapper.clone(), + }, + authority_pda_signer, + ); + spl_account_compression::cpi::append(cpi_ctx, leaf_node) + } else { + let cpi_ctx = CpiContext::new_with_signer( + compression_program.clone(), + mpl_account_compression::cpi::accounts::Modify { + authority: authority.clone(), + merkle_tree: merkle_tree.clone(), + noop: log_wrapper.clone(), + }, + authority_pda_signer, + ); + mpl_account_compression::cpi::append(cpi_ctx, leaf_node) + } } pub fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool { @@ -106,3 +151,95 @@ pub fn get_asset_id(tree_id: &Pubkey, nonce: u64) -> Pubkey { ) .0 } + +/// Wraps a custom event in the most recent version of application event data. +/// Modified from spl-account-compression to allow `noop_program` to be an `UncheckedAccount` +/// and choose the correct one based on program ID. +pub(crate) fn wrap_application_data_v1( + custom_data: Vec, + noop_program: &AccountInfo<'_>, +) -> Result<()> { + // We wrap the event using spl-account-compression no matter whether we are sending the data + // to spl-account-compression or mpl-account-compression. + let versioned_data = spl_account_compression::events::ApplicationDataEventV1 { + application_data: custom_data, + }; + let event = spl_account_compression::events::AccountCompressionEvent::ApplicationData( + spl_account_compression::events::ApplicationDataEvent::V1(versioned_data), + ); + let serialized_event: Vec = event.try_to_vec()?; + + if noop_program.key == &spl_account_compression::Noop::id() { + invoke( + &spl_noop::instruction(serialized_event), + &[noop_program.to_account_info()], + )?; + } else if noop_program.key + == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap() + { + invoke( + &mpl_noop::instruction(serialized_event), + &[noop_program.to_account_info()], + )?; + } else { + return Err(BubblegumError::InvalidLogWrapper.into()); + } + + Ok(()) +} + +/// Validate the Merkle tree is owned by one of the valid program choices, and that the provided +/// log wrapper and compression program are one of the valid choices. +pub(crate) fn validate_ownership_and_programs( + merkle_tree: &AccountInfo<'_>, + log_wrapper: &AccountInfo<'_>, + compression_program: &AccountInfo<'_>, +) -> Result<()> { + if merkle_tree.owner == &spl_account_compression::program::SplAccountCompression::id() { + require!( + log_wrapper.key == &spl_account_compression::Noop::id(), + BubblegumError::InvalidLogWrapper + ); + require!( + compression_program.key + == &spl_account_compression::program::SplAccountCompression::id(), + BubblegumError::InvalidCompressionProgram + ); + } else if merkle_tree.owner + == &Pubkey::from_str("2Kqj8s8JssJoR35E81jyQHFEhKZokfEtf8BLTPEnkQr4").unwrap() + { + require!( + log_wrapper.key + == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap(), + BubblegumError::InvalidLogWrapper + ); + require!( + compression_program.key + == &Pubkey::from_str("2Kqj8s8JssJoR35E81jyQHFEhKZokfEtf8BLTPEnkQr4").unwrap(), + BubblegumError::InvalidCompressionProgram + ); + } else { + return Err(BubblegumError::IncorrectOwner.into()); + } + + require!(log_wrapper.executable, BubblegumError::InvalidLogWrapper); + require!( + compression_program.executable, + BubblegumError::InvalidCompressionProgram + ); + + Ok(()) +} + +/// Validate the provided log wrapper program is one of the valid choices. +pub(crate) fn validate_log_wrapper_program(log_wrapper: &AccountInfo<'_>) -> Result<()> { + require!( + log_wrapper.key == &spl_account_compression::Noop::id() + || log_wrapper.key + == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap(), + BubblegumError::InvalidLogWrapper + ); + require!(log_wrapper.executable, BubblegumError::InvalidLogWrapper); + + Ok(()) +} From f148a93fdf90014cd0e052e884357db7726b3d80 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 14 Oct 2024 03:49:50 -0700 Subject: [PATCH 02/15] Update Cargo.lock --- programs/bubblegum/Cargo.lock | 146 ++++++++++++---------------------- 1 file changed, 49 insertions(+), 97 deletions(-) diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 04241678..87f6e03c 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -261,8 +261,8 @@ checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" dependencies = [ "anchor-lang", "solana-program", - "spl-associated-token-account 2.3.0", - "spl-token 4.0.0", + "spl-associated-token-account", + "spl-token", "spl-token-2022 0.9.0", ] @@ -839,6 +839,8 @@ dependencies = [ "anchor-spl", "async-trait", "bytemuck", + "mpl-account-compression", + "mpl-noop", "mpl-token-auth-rules", "mpl-token-metadata", "num-traits", @@ -846,11 +848,11 @@ dependencies = [ "solana-program-test", "solana-sdk", "spl-account-compression", - "spl-associated-token-account 1.1.3", - "spl-concurrent-merkle-tree", + "spl-associated-token-account", + "spl-concurrent-merkle-tree 0.3.0", "spl-merkle-tree-reference", "spl-noop", - "spl-token 3.5.0", + "spl-token", ] [[package]] @@ -871,9 +873,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] @@ -2411,6 +2413,24 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "mpl-account-compression" +version = "0.4.1" +dependencies = [ + "anchor-lang", + "bytemuck", + "mpl-noop", + "solana-program", + "spl-concurrent-merkle-tree 0.4.0", +] + +[[package]] +name = "mpl-noop" +version = "0.2.0" +dependencies = [ + "solana-program", +] + [[package]] name = "mpl-token-auth-rules" version = "1.5.1" @@ -2602,15 +2622,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - [[package]] name = "num_enum" version = "0.6.1" @@ -2629,18 +2640,6 @@ dependencies = [ "num_enum_derive 0.7.3", ] -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "num_enum_derive" version = "0.6.1" @@ -2659,7 +2658,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.76", @@ -3820,7 +3819,7 @@ dependencies = [ "serde_json", "solana-config-program", "solana-sdk", - "spl-token 4.0.0", + "spl-token", "spl-token-2022 1.0.0", "spl-token-group-interface", "spl-token-metadata-interface", @@ -4799,9 +4798,9 @@ dependencies = [ "serde_json", "solana-account-decoder", "solana-sdk", - "spl-associated-token-account 2.3.0", - "spl-memo 4.0.0", - "spl-token 4.0.0", + "spl-associated-token-account", + "spl-memo", + "spl-token", "spl-token-2022 1.0.0", "thiserror", ] @@ -4971,26 +4970,10 @@ dependencies = [ "anchor-lang", "bytemuck", "solana-program", - "spl-concurrent-merkle-tree", + "spl-concurrent-merkle-tree 0.3.0", "spl-noop", ] -[[package]] -name = "spl-associated-token-account" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" -dependencies = [ - "assert_matches", - "borsh 0.9.3", - "num-derive 0.3.3", - "num-traits", - "solana-program", - "spl-token 3.5.0", - "spl-token-2022 0.6.1", - "thiserror", -] - [[package]] name = "spl-associated-token-account" version = "2.3.0" @@ -5002,7 +4985,7 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "solana-program", - "spl-token 4.0.0", + "spl-token", "spl-token-2022 1.0.0", "thiserror", ] @@ -5018,6 +5001,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-concurrent-merkle-tree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d1bbb97252d8a1b90d3d56425038928382a306b71dbba4c836973c94b33f96" +dependencies = [ + "bytemuck", + "solana-program", + "thiserror", +] + [[package]] name = "spl-discriminator" version = "0.1.0" @@ -5053,15 +5047,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "spl-memo" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" -dependencies = [ - "solana-program", -] - [[package]] name = "spl-memo" version = "4.0.0" @@ -5156,21 +5141,6 @@ dependencies = [ "spl-type-length-value", ] -[[package]] -name = "spl-token" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" -dependencies = [ - "arrayref", - "bytemuck", - "num-derive 0.3.3", - "num-traits", - "num_enum 0.5.11", - "solana-program", - "thiserror", -] - [[package]] name = "spl-token" version = "4.0.0" @@ -5186,24 +5156,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "spl-token-2022" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" -dependencies = [ - "arrayref", - "bytemuck", - "num-derive 0.3.3", - "num-traits", - "num_enum 0.5.11", - "solana-program", - "solana-zk-token-sdk", - "spl-memo 3.0.1", - "spl-token 3.5.0", - "thiserror", -] - [[package]] name = "spl-token-2022" version = "0.9.0" @@ -5217,9 +5169,9 @@ dependencies = [ "num_enum 0.7.3", "solana-program", "solana-zk-token-sdk", - "spl-memo 4.0.0", + "spl-memo", "spl-pod", - "spl-token 4.0.0", + "spl-token", "spl-token-metadata-interface", "spl-transfer-hook-interface 0.3.0", "spl-type-length-value", @@ -5240,9 +5192,9 @@ dependencies = [ "solana-program", "solana-security-txt", "solana-zk-token-sdk", - "spl-memo 4.0.0", + "spl-memo", "spl-pod", - "spl-token 4.0.0", + "spl-token", "spl-token-group-interface", "spl-token-metadata-interface", "spl-transfer-hook-interface 0.4.1", From 7f35d8f6b08512caf07363ea63a429b72dadfc67 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 14 Oct 2024 03:51:17 -0700 Subject: [PATCH 03/15] Regenerate IDL and SDKs --- .../js/src/generated/errors/mplBubblegum.ts | 26 +++++++++++++++++++ .../src/generated/errors/mpl_bubblegum.rs | 6 +++++ idls/bubblegum.json | 10 +++++++ 3 files changed, 42 insertions(+) diff --git a/clients/js/src/generated/errors/mplBubblegum.ts b/clients/js/src/generated/errors/mplBubblegum.ts index 87e50c90..ac191f2d 100644 --- a/clients/js/src/generated/errors/mplBubblegum.ts +++ b/clients/js/src/generated/errors/mplBubblegum.ts @@ -604,6 +604,32 @@ export class InvalidCanopySizeError extends ProgramError { codeToErrorMap.set(0x1799, InvalidCanopySizeError); nameToErrorMap.set('InvalidCanopySize', InvalidCanopySizeError); +/** InvalidLogWrapper: Invalid log wrapper program */ +export class InvalidLogWrapperError extends ProgramError { + readonly name: string = 'InvalidLogWrapper'; + + readonly code: number = 0x179a; // 6042 + + constructor(program: Program, cause?: Error) { + super('Invalid log wrapper program', program, cause); + } +} +codeToErrorMap.set(0x179a, InvalidLogWrapperError); +nameToErrorMap.set('InvalidLogWrapper', InvalidLogWrapperError); + +/** InvalidCompressionProgram: Invalid compression program */ +export class InvalidCompressionProgramError extends ProgramError { + readonly name: string = 'InvalidCompressionProgram'; + + readonly code: number = 0x179b; // 6043 + + constructor(program: Program, cause?: Error) { + super('Invalid compression program', program, cause); + } +} +codeToErrorMap.set(0x179b, InvalidCompressionProgramError); +nameToErrorMap.set('InvalidCompressionProgram', InvalidCompressionProgramError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/rust/src/generated/errors/mpl_bubblegum.rs b/clients/rust/src/generated/errors/mpl_bubblegum.rs index 87dc4019..9eba86ef 100644 --- a/clients/rust/src/generated/errors/mpl_bubblegum.rs +++ b/clients/rust/src/generated/errors/mpl_bubblegum.rs @@ -136,6 +136,12 @@ pub enum MplBubblegumError { /// 6041 (0x1799) - Canopy size should be set bigger for this tree #[error("Canopy size should be set bigger for this tree")] InvalidCanopySize, + /// 6042 (0x179A) - Invalid log wrapper program + #[error("Invalid log wrapper program")] + InvalidLogWrapper, + /// 6043 (0x179B) - Invalid compression program + #[error("Invalid compression program")] + InvalidCompressionProgram, } impl solana_program::program_error::PrintProgramError for MplBubblegumError { diff --git a/idls/bubblegum.json b/idls/bubblegum.json index 070afde2..f670a6f0 100644 --- a/idls/bubblegum.json +++ b/idls/bubblegum.json @@ -2285,6 +2285,16 @@ "code": 6041, "name": "InvalidCanopySize", "msg": "Canopy size should be set bigger for this tree" + }, + { + "code": 6042, + "name": "InvalidLogWrapper", + "msg": "Invalid log wrapper program" + }, + { + "code": 6043, + "name": "InvalidCompressionProgram", + "msg": "Invalid compression program" } ], "metadata": { From 83cb83c5247b93e8b87f64e647cbdc1d81ece1ec Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 14 Oct 2024 04:27:52 -0700 Subject: [PATCH 04/15] Add test for new programs and update program IDs --- clients/js/src/createTree.ts | 12 +++-- clients/js/test/transfer.test.ts | 49 +++++++++++++++++++ configs/validator.cjs | 20 ++++++-- .../program/src/processor/create_tree.rs | 4 +- programs/bubblegum/program/src/utils.rs | 32 +++++------- 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/clients/js/src/createTree.ts b/clients/js/src/createTree.ts index bc6eb227..b598f021 100644 --- a/clients/js/src/createTree.ts +++ b/clients/js/src/createTree.ts @@ -36,10 +36,14 @@ export const createTree = async ( newAccount: input.merkleTree, lamports, space, - programId: context.programs.getPublicKey( - 'splAccountCompression', - SPL_ACCOUNT_COMPRESSION_PROGRAM_ID - ), + programId: input.compressionProgram + ? Array.isArray(input.compressionProgram) + ? input.compressionProgram[0] + : input.compressionProgram + : context.programs.getPublicKey( + 'splAccountCompression', + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID + ), }) ) // Create the tree config. diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index e2ad78f9..a6c77ade 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -55,6 +55,55 @@ test('it can transfer a compressed NFT', async (t) => { t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); +test('it can transfer a compressed NFT using different programs', async (t) => { + // Given a tree with a minted NFT owned by leafOwnerA. + const umi = await createUmi(); + const merkleTree = await createTree(umi, { + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwnerA = generateSigner(umi); + const { metadata, leafIndex } = await mint(umi, { + merkleTree, + leafOwner: leafOwnerA.publicKey, + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }); + + // When leafOwnerA transfers the NFT to leafOwnerB. + const leafOwnerB = generateSigner(umi); + await transfer(umi, { + leafOwner: leafOwnerA, + newLeafOwner: leafOwnerB.publicKey, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + dataHash: hashMetadataData(metadata), + creatorHash: hashMetadataCreators(metadata.creators), + nonce: leafIndex, + index: leafIndex, + proof: [], + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }).sendAndConfirm(umi); + + // Then the leaf was updated in the merkle tree. + const updatedLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwnerB.publicKey, + leafIndex, + metadata, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); +}); + test('it can transfer a compressed NFT as a delegated authority', async (t) => { // Given a tree with a delegated compressed NFT owned by leafOwnerA. const umi = await createUmi(); diff --git a/configs/validator.cjs b/configs/validator.cjs index d4761550..f96bbfb1 100755 --- a/configs/validator.cjs +++ b/configs/validator.cjs @@ -10,16 +10,21 @@ module.exports = { validator: { commitment: "processed", programs: [ + { + label: "MPL Account Compression", + programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", + deployPath: getProgram("mpl_account_compression.so"), + }, + { + label: "MPL Noop", + programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", + deployPath: getProgram("mpl_noop.so"), + }, { label: "Mpl Bubblegum", programId: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", deployPath: getProgram("bubblegum.so"), }, - { - label: "Token Metadata", - programId: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", - deployPath: getProgram("mpl_token_metadata.so"), - }, { label: "SPL Account Compression", programId: "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK", @@ -30,6 +35,11 @@ module.exports = { programId: "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", deployPath: getProgram("spl_noop.so"), }, + { + label: "Token Metadata", + programId: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + deployPath: getProgram("mpl_token_metadata.so"), + }, ], }, }; diff --git a/programs/bubblegum/program/src/processor/create_tree.rs b/programs/bubblegum/program/src/processor/create_tree.rs index 73de376c..adf09d58 100644 --- a/programs/bubblegum/program/src/processor/create_tree.rs +++ b/programs/bubblegum/program/src/processor/create_tree.rs @@ -61,9 +61,7 @@ pub(crate) fn create_tree( }); let authority_pda_signer = &[&seeds[..]]; - if ctx.accounts.compression_program.key - == &spl_account_compression::program::SplAccountCompression::id() - { + if ctx.accounts.compression_program.key == &spl_account_compression::id() { let cpi_ctx = CpiContext::new_with_signer( ctx.accounts.compression_program.to_account_info(), spl_account_compression::cpi::accounts::Initialize { diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index 76eb513e..f9855dee 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -3,7 +3,6 @@ use anchor_lang::{ solana_program::{program::invoke, program_memory::sol_memcmp, pubkey::PUBKEY_BYTES}, }; use solana_program::keccak; -use std::str::FromStr; use crate::{ error::BubblegumError, @@ -56,7 +55,7 @@ pub fn replace_leaf<'info>( let seeds = &[seed.as_ref(), &[bump]]; let authority_pda_signer = &[&seeds[..]]; - if compression_program.key == &spl_account_compression::program::SplAccountCompression::id() { + if compression_program.key == &spl_account_compression::id() { let cpi_ctx = CpiContext::new_with_signer( compression_program.clone(), spl_account_compression::cpi::accounts::Modify { @@ -107,7 +106,7 @@ pub fn append_leaf<'info>( let seeds = &[seed.as_ref(), &[bump]]; let authority_pda_signer = &[&seeds[..]]; - if compression_program.key == &spl_account_compression::program::SplAccountCompression::id() { + if compression_program.key == &spl_account_compression::id() { let cpi_ctx = CpiContext::new_with_signer( compression_program.clone(), spl_account_compression::cpi::accounts::Modify { @@ -169,14 +168,12 @@ pub(crate) fn wrap_application_data_v1( ); let serialized_event: Vec = event.try_to_vec()?; - if noop_program.key == &spl_account_compression::Noop::id() { + if noop_program.key == &spl_noop::id() { invoke( &spl_noop::instruction(serialized_event), &[noop_program.to_account_info()], )?; - } else if noop_program.key - == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap() - { + } else if noop_program.key == &mpl_noop::id() { invoke( &mpl_noop::instruction(serialized_event), &[noop_program.to_account_info()], @@ -195,27 +192,22 @@ pub(crate) fn validate_ownership_and_programs( log_wrapper: &AccountInfo<'_>, compression_program: &AccountInfo<'_>, ) -> Result<()> { - if merkle_tree.owner == &spl_account_compression::program::SplAccountCompression::id() { + if merkle_tree.owner == &spl_account_compression::id() { require!( - log_wrapper.key == &spl_account_compression::Noop::id(), + log_wrapper.key == &spl_noop::id(), BubblegumError::InvalidLogWrapper ); require!( - compression_program.key - == &spl_account_compression::program::SplAccountCompression::id(), + compression_program.key == &spl_account_compression::id(), BubblegumError::InvalidCompressionProgram ); - } else if merkle_tree.owner - == &Pubkey::from_str("2Kqj8s8JssJoR35E81jyQHFEhKZokfEtf8BLTPEnkQr4").unwrap() - { + } else if merkle_tree.owner == &mpl_account_compression::id() { require!( - log_wrapper.key - == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap(), + log_wrapper.key == &mpl_noop::id(), BubblegumError::InvalidLogWrapper ); require!( - compression_program.key - == &Pubkey::from_str("2Kqj8s8JssJoR35E81jyQHFEhKZokfEtf8BLTPEnkQr4").unwrap(), + compression_program.key == &mpl_account_compression::id(), BubblegumError::InvalidCompressionProgram ); } else { @@ -234,9 +226,7 @@ pub(crate) fn validate_ownership_and_programs( /// Validate the provided log wrapper program is one of the valid choices. pub(crate) fn validate_log_wrapper_program(log_wrapper: &AccountInfo<'_>) -> Result<()> { require!( - log_wrapper.key == &spl_account_compression::Noop::id() - || log_wrapper.key - == &Pubkey::from_str("FGrro6PzpWjBRSYScqmRRRXpQNHAQNtJQwfSK8Dm94NQ").unwrap(), + log_wrapper.key == &spl_noop::id() || log_wrapper.key == &mpl_noop::id(), BubblegumError::InvalidLogWrapper ); require!(log_wrapper.executable, BubblegumError::InvalidLogWrapper); From 5365b532ac1a76c3369d7def91f7d51243a2f272 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:14:55 -0700 Subject: [PATCH 05/15] Use MPL-specific types instead of assuming spl-account-compression types are compatible --- .../bubblegum/program/src/processor/burn.rs | 6 ++- .../program/src/processor/create_tree.rs | 6 +-- .../bubblegum/program/src/processor/redeem.rs | 5 +-- .../program/src/state/leaf_schema.rs | 6 ++- programs/bubblegum/program/src/utils.rs | 37 +++++++++++-------- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/programs/bubblegum/program/src/processor/burn.rs b/programs/bubblegum/program/src/processor/burn.rs index 3e20b44a..cd31c558 100644 --- a/programs/bubblegum/program/src/processor/burn.rs +++ b/programs/bubblegum/program/src/processor/burn.rs @@ -1,9 +1,11 @@ use anchor_lang::prelude::*; -use spl_account_compression::Node; use crate::{ error::BubblegumError, - state::{leaf_schema::LeafSchema, TreeConfig}, + state::{ + leaf_schema::{LeafSchema, Node}, + TreeConfig, + }, utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; diff --git a/programs/bubblegum/program/src/processor/create_tree.rs b/programs/bubblegum/program/src/processor/create_tree.rs index adf09d58..6b6a6e0f 100644 --- a/programs/bubblegum/program/src/processor/create_tree.rs +++ b/programs/bubblegum/program/src/processor/create_tree.rs @@ -3,7 +3,7 @@ use bytemuck::cast_slice; use crate::{ error::BubblegumError, - state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, + state::{leaf_schema::Node, DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, utils::validate_ownership_and_programs, }; @@ -111,7 +111,7 @@ fn check_canopy_size( let (_tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size); - let canopy = cast_slice::(canopy_bytes); + let canopy = cast_slice::(canopy_bytes); let cached_path_len = get_cached_path_length(canopy, max_depth)?; @@ -127,7 +127,7 @@ fn check_canopy_size( // Method is taken from account-compression Solana program #[inline(always)] -fn get_cached_path_length(canopy: &[spl_account_compression::Node], max_depth: u32) -> Result { +fn get_cached_path_length(canopy: &[Node], max_depth: u32) -> Result { // The offset of 2 is applied because the canopy is a full binary tree without the root node // Size: (2^n - 2) -> Size + 2 must be a power of 2 let closest_power_of_2 = (canopy.len() + 2) as u32; diff --git a/programs/bubblegum/program/src/processor/redeem.rs b/programs/bubblegum/program/src/processor/redeem.rs index 34a79353..fb9f6a8d 100644 --- a/programs/bubblegum/program/src/processor/redeem.rs +++ b/programs/bubblegum/program/src/processor/redeem.rs @@ -1,11 +1,10 @@ use anchor_lang::prelude::*; -use spl_account_compression::Node; use crate::{ error::BubblegumError, state::{ - leaf_schema::LeafSchema, DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX, - VOUCHER_SIZE, + leaf_schema::{LeafSchema, Node}, + DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX, VOUCHER_SIZE, }, utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; diff --git a/programs/bubblegum/program/src/state/leaf_schema.rs b/programs/bubblegum/program/src/state/leaf_schema.rs index f118c83a..36db432a 100644 --- a/programs/bubblegum/program/src/state/leaf_schema.rs +++ b/programs/bubblegum/program/src/state/leaf_schema.rs @@ -2,6 +2,10 @@ use crate::state::BubblegumEventType; use anchor_lang::{prelude::*, solana_program::keccak}; use borsh::{BorshDeserialize, BorshSerialize}; +/// Abstract type for 32 byte leaf data. Same type as spl-account-compression and +/// mpl-account-compression `Node` types. +pub type Node = [u8; 32]; + #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct LeafSchemaEvent { pub event_type: BubblegumEventType, @@ -125,7 +129,7 @@ impl LeafSchema { LeafSchemaEvent::new(self.version(), self.clone(), self.to_node()) } - pub fn to_node(&self) -> spl_account_compression::Node { + pub fn to_node(&self) -> Node { let hashed_leaf = match self { LeafSchema::V1 { id, diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index f9855dee..814e0597 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -7,6 +7,7 @@ use solana_program::keccak; use crate::{ error::BubblegumError, state::{ + leaf_schema::Node, metaplex_adapter::{Creator, MetadataArgs}, ASSET_PREFIX, }, @@ -47,9 +48,9 @@ pub fn replace_leaf<'info>( merkle_tree: &AccountInfo<'info>, log_wrapper: &AccountInfo<'info>, remaining_accounts: &[AccountInfo<'info>], - root_node: spl_account_compression::Node, - previous_leaf: spl_account_compression::Node, - new_leaf: spl_account_compression::Node, + root_node: Node, + previous_leaf: Node, + new_leaf: Node, index: u32, ) -> Result<()> { let seeds = &[seed.as_ref(), &[bump]]; @@ -101,7 +102,7 @@ pub fn append_leaf<'info>( authority: &AccountInfo<'info>, merkle_tree: &AccountInfo<'info>, log_wrapper: &AccountInfo<'info>, - leaf_node: spl_account_compression::Node, + leaf_node: Node, ) -> Result<()> { let seeds = &[seed.as_ref(), &[bump]]; let authority_pda_signer = &[&seeds[..]]; @@ -158,24 +159,28 @@ pub(crate) fn wrap_application_data_v1( custom_data: Vec, noop_program: &AccountInfo<'_>, ) -> Result<()> { - // We wrap the event using spl-account-compression no matter whether we are sending the data - // to spl-account-compression or mpl-account-compression. - let versioned_data = spl_account_compression::events::ApplicationDataEventV1 { - application_data: custom_data, - }; - let event = spl_account_compression::events::AccountCompressionEvent::ApplicationData( - spl_account_compression::events::ApplicationDataEvent::V1(versioned_data), - ); - let serialized_event: Vec = event.try_to_vec()?; - if noop_program.key == &spl_noop::id() { + let versioned_data = spl_account_compression::events::ApplicationDataEventV1 { + application_data: custom_data, + }; + let event = spl_account_compression::events::AccountCompressionEvent::ApplicationData( + spl_account_compression::events::ApplicationDataEvent::V1(versioned_data), + ); + invoke( - &spl_noop::instruction(serialized_event), + &spl_noop::instruction(event.try_to_vec()?), &[noop_program.to_account_info()], )?; } else if noop_program.key == &mpl_noop::id() { + let versioned_data = mpl_account_compression::events::ApplicationDataEventV1 { + application_data: custom_data, + }; + let event = mpl_account_compression::events::AccountCompressionEvent::ApplicationData( + mpl_account_compression::events::ApplicationDataEvent::V1(versioned_data), + ); + invoke( - &mpl_noop::instruction(serialized_event), + &mpl_noop::instruction(event.try_to_vec()?), &[noop_program.to_account_info()], )?; } else { From b02070960bed39e01d52209dba4fa1e8fae75b7c Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:06:39 -0700 Subject: [PATCH 06/15] Use mpl-account-compression published versions Update spl-account-compression version Use Node type from spl-concurrent-merkle-tree crate --- programs/bubblegum/program/Cargo.toml | 7 ++++--- programs/bubblegum/program/src/processor/burn.rs | 6 ++---- programs/bubblegum/program/src/processor/create_tree.rs | 3 ++- programs/bubblegum/program/src/processor/redeem.rs | 5 +++-- programs/bubblegum/program/src/state/leaf_schema.rs | 5 +---- programs/bubblegum/program/src/utils.rs | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index 300afbb9..82dbdd69 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -23,13 +23,14 @@ default = [] anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } anchor-spl = "0.29.0" bytemuck = "1.13.0" -mpl-account-compression = { path = "/home/danenbm/Metaplex-Workspace/mpl-account-compression/programs/account-compression", features = ["cpi"] } -mpl-noop = { path = "/home/danenbm/Metaplex-Workspace/mpl-account-compression/programs/noop", features = ["no-entrypoint"] } +mpl-account-compression = { version = "0.4.1", features = ["cpi"] } +mpl-noop = { version = "0.2.1", features = ["no-entrypoint"] } mpl-token-metadata = "4.1.2" num-traits = "0.2.15" solana-program = "~1.18.15" -spl-account-compression = { version = "0.3.1", features = ["cpi"] } +spl-account-compression = { version = "0.4.1", features = ["cpi"] } spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } +spl-concurrent-merkle-tree = { version = "0.4.1" } spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } diff --git a/programs/bubblegum/program/src/processor/burn.rs b/programs/bubblegum/program/src/processor/burn.rs index cd31c558..fe897ee6 100644 --- a/programs/bubblegum/program/src/processor/burn.rs +++ b/programs/bubblegum/program/src/processor/burn.rs @@ -1,11 +1,9 @@ use anchor_lang::prelude::*; +use spl_concurrent_merkle_tree::node::Node; use crate::{ error::BubblegumError, - state::{ - leaf_schema::{LeafSchema, Node}, - TreeConfig, - }, + state::{leaf_schema::LeafSchema, TreeConfig}, utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; diff --git a/programs/bubblegum/program/src/processor/create_tree.rs b/programs/bubblegum/program/src/processor/create_tree.rs index 6b6a6e0f..52270bab 100644 --- a/programs/bubblegum/program/src/processor/create_tree.rs +++ b/programs/bubblegum/program/src/processor/create_tree.rs @@ -1,9 +1,10 @@ use anchor_lang::{prelude::*, system_program::System}; use bytemuck::cast_slice; +use spl_concurrent_merkle_tree::node::Node; use crate::{ error::BubblegumError, - state::{leaf_schema::Node, DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, + state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, utils::validate_ownership_and_programs, }; diff --git a/programs/bubblegum/program/src/processor/redeem.rs b/programs/bubblegum/program/src/processor/redeem.rs index fb9f6a8d..dff95f5d 100644 --- a/programs/bubblegum/program/src/processor/redeem.rs +++ b/programs/bubblegum/program/src/processor/redeem.rs @@ -1,10 +1,11 @@ use anchor_lang::prelude::*; +use spl_concurrent_merkle_tree::node::Node; use crate::{ error::BubblegumError, state::{ - leaf_schema::{LeafSchema, Node}, - DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX, VOUCHER_SIZE, + leaf_schema::LeafSchema, DecompressibleState, TreeConfig, Voucher, VOUCHER_PREFIX, + VOUCHER_SIZE, }, utils::{get_asset_id, replace_leaf, validate_ownership_and_programs}, }; diff --git a/programs/bubblegum/program/src/state/leaf_schema.rs b/programs/bubblegum/program/src/state/leaf_schema.rs index 36db432a..df88feec 100644 --- a/programs/bubblegum/program/src/state/leaf_schema.rs +++ b/programs/bubblegum/program/src/state/leaf_schema.rs @@ -1,10 +1,7 @@ use crate::state::BubblegumEventType; use anchor_lang::{prelude::*, solana_program::keccak}; use borsh::{BorshDeserialize, BorshSerialize}; - -/// Abstract type for 32 byte leaf data. Same type as spl-account-compression and -/// mpl-account-compression `Node` types. -pub type Node = [u8; 32]; +use spl_concurrent_merkle_tree::node::Node; #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct LeafSchemaEvent { diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index 814e0597..36aa61b5 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -3,11 +3,11 @@ use anchor_lang::{ solana_program::{program::invoke, program_memory::sol_memcmp, pubkey::PUBKEY_BYTES}, }; use solana_program::keccak; +use spl_concurrent_merkle_tree::node::Node; use crate::{ error::BubblegumError, state::{ - leaf_schema::Node, metaplex_adapter::{Creator, MetadataArgs}, ASSET_PREFIX, }, From bf0ae0251ad496e793832e6a2d5dc81ab8a1090c Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:45:46 -0700 Subject: [PATCH 07/15] Add idl-build feature --- programs/bubblegum/Cargo.lock | 28 +++++++++++-------- programs/bubblegum/program/Cargo.toml | 7 ++++- .../program/src/state/leaf_schema.rs | 3 ++ programs/bubblegum/program/src/state/mod.rs | 3 ++ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 87f6e03c..10f59072 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -243,6 +243,7 @@ dependencies = [ "anchor-derive-accounts", "anchor-derive-serde", "anchor-derive-space", + "anchor-syn", "arrayref", "base64 0.13.1", "bincode", @@ -850,6 +851,7 @@ dependencies = [ "spl-account-compression", "spl-associated-token-account", "spl-concurrent-merkle-tree 0.3.0", + "spl-concurrent-merkle-tree 0.4.1", "spl-merkle-tree-reference", "spl-noop", "spl-token", @@ -2416,17 +2418,21 @@ dependencies = [ [[package]] name = "mpl-account-compression" version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321ebb1c041f6e4b08f85f003ebdecdba64247abb9ed85313ef71c8dee4b3d9" dependencies = [ "anchor-lang", "bytemuck", "mpl-noop", "solana-program", - "spl-concurrent-merkle-tree 0.4.0", + "spl-concurrent-merkle-tree 0.4.1", ] [[package]] name = "mpl-noop" -version = "0.2.0" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179556a9254920ca8b150b18728d2f3106879f710c1ef5a049a8f0d5b03eede5" dependencies = [ "solana-program", ] @@ -4963,14 +4969,14 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602499d5fe3b60280239c4656a361b283c8c5f73f769c6cf41d2e8a151ce72db" +checksum = "8ce8314ec6ae26084ec7c6c0802c3dc173ee86aee5f5d5026a3f82c52cfe1c07" dependencies = [ "anchor-lang", "bytemuck", "solana-program", - "spl-concurrent-merkle-tree 0.3.0", + "spl-concurrent-merkle-tree 0.4.1", "spl-noop", ] @@ -5003,9 +5009,9 @@ dependencies = [ [[package]] name = "spl-concurrent-merkle-tree" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85d1bbb97252d8a1b90d3d56425038928382a306b71dbba4c836973c94b33f96" +checksum = "a14033366e14117679851c7759c3d66c6430a495f0523bd88076d3a275828931" dependencies = [ "bytemuck", "solana-program", @@ -5522,18 +5528,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index 82dbdd69..e35e2a52 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -17,7 +17,12 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] test-sbf = [] -default = [] +default = ["idl-build"] +idl-build = [ + "anchor-lang/idl-build", + "anchor-spl/idl-build", + "mpl-account-compression/idl-build", +] [dependencies] anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } diff --git a/programs/bubblegum/program/src/state/leaf_schema.rs b/programs/bubblegum/program/src/state/leaf_schema.rs index df88feec..709dde97 100644 --- a/programs/bubblegum/program/src/state/leaf_schema.rs +++ b/programs/bubblegum/program/src/state/leaf_schema.rs @@ -36,6 +36,9 @@ impl Version { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for LeafSchema {} + #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub enum LeafSchema { V1 { diff --git a/programs/bubblegum/program/src/state/mod.rs b/programs/bubblegum/program/src/state/mod.rs index 3ca74245..097dcd27 100644 --- a/programs/bubblegum/program/src/state/mod.rs +++ b/programs/bubblegum/program/src/state/mod.rs @@ -81,6 +81,9 @@ pub enum BubblegumEventType { LeafSchemaEvent, } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for DecompressibleState {} + #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)] #[repr(u8)] pub enum DecompressibleState { From fd06d86b4b927ea0eb3b255a51001eca1a20a683 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:04:44 -0700 Subject: [PATCH 08/15] Remove Anchor idl-build feature and remove account compression dependencies that use it --- programs/bubblegum/Cargo.lock | 13 ++++++------- programs/bubblegum/program/Cargo.toml | 11 +++-------- programs/bubblegum/program/src/state/leaf_schema.rs | 3 --- programs/bubblegum/program/src/state/mod.rs | 3 --- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 10f59072..4ccc935f 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -243,7 +243,6 @@ dependencies = [ "anchor-derive-accounts", "anchor-derive-serde", "anchor-derive-space", - "anchor-syn", "arrayref", "base64 0.13.1", "bincode", @@ -699,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -2417,9 +2416,9 @@ dependencies = [ [[package]] name = "mpl-account-compression" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321ebb1c041f6e4b08f85f003ebdecdba64247abb9ed85313ef71c8dee4b3d9" +checksum = "e2ad6a6ba15b2e880d43b94f40510f1b64f2981a843cfc7b8877919467d2ab1a" dependencies = [ "anchor-lang", "bytemuck", @@ -2664,7 +2663,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.76", @@ -4969,9 +4968,9 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8314ec6ae26084ec7c6c0802c3dc173ee86aee5f5d5026a3f82c52cfe1c07" +checksum = "31f461e20e8efb539d7f3f30cd82931ee128651c6366abe326f083b07253a1d6" dependencies = [ "anchor-lang", "bytemuck", diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index e35e2a52..199867ae 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -17,23 +17,18 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] test-sbf = [] -default = ["idl-build"] -idl-build = [ - "anchor-lang/idl-build", - "anchor-spl/idl-build", - "mpl-account-compression/idl-build", -] +default = [] [dependencies] anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } anchor-spl = "0.29.0" bytemuck = "1.13.0" -mpl-account-compression = { version = "0.4.1", features = ["cpi"] } +mpl-account-compression = { version = "0.4.2", features = ["cpi"] } mpl-noop = { version = "0.2.1", features = ["no-entrypoint"] } mpl-token-metadata = "4.1.2" num-traits = "0.2.15" solana-program = "~1.18.15" -spl-account-compression = { version = "0.4.1", features = ["cpi"] } +spl-account-compression = { version = "0.4.0", features = ["cpi"] } spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } spl-concurrent-merkle-tree = { version = "0.4.1" } spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } diff --git a/programs/bubblegum/program/src/state/leaf_schema.rs b/programs/bubblegum/program/src/state/leaf_schema.rs index 709dde97..df88feec 100644 --- a/programs/bubblegum/program/src/state/leaf_schema.rs +++ b/programs/bubblegum/program/src/state/leaf_schema.rs @@ -36,9 +36,6 @@ impl Version { } } -#[cfg(feature = "idl-build")] -impl anchor_lang::IdlBuild for LeafSchema {} - #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub enum LeafSchema { V1 { diff --git a/programs/bubblegum/program/src/state/mod.rs b/programs/bubblegum/program/src/state/mod.rs index 097dcd27..3ca74245 100644 --- a/programs/bubblegum/program/src/state/mod.rs +++ b/programs/bubblegum/program/src/state/mod.rs @@ -81,9 +81,6 @@ pub enum BubblegumEventType { LeafSchemaEvent, } -#[cfg(feature = "idl-build")] -impl anchor_lang::IdlBuild for DecompressibleState {} - #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)] #[repr(u8)] pub enum DecompressibleState { From ef102fa411bd666b724f067321e9308778ac1948 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:18:12 -0700 Subject: [PATCH 09/15] Temporarily skip different compression program test --- clients/js/test/transfer.test.ts | 2 +- configs/validator.cjs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index a6c77ade..c8e2f2e7 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -55,7 +55,7 @@ test('it can transfer a compressed NFT', async (t) => { t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); -test('it can transfer a compressed NFT using different programs', async (t) => { +test.skip('it can transfer a compressed NFT using different programs', async (t) => { // Given a tree with a minted NFT owned by leafOwnerA. const umi = await createUmi(); const merkleTree = await createTree(umi, { diff --git a/configs/validator.cjs b/configs/validator.cjs index f96bbfb1..397ca8fb 100755 --- a/configs/validator.cjs +++ b/configs/validator.cjs @@ -10,16 +10,16 @@ module.exports = { validator: { commitment: "processed", programs: [ - { - label: "MPL Account Compression", - programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", - deployPath: getProgram("mpl_account_compression.so"), - }, - { - label: "MPL Noop", - programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", - deployPath: getProgram("mpl_noop.so"), - }, + // { + // label: "MPL Account Compression", + // programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", + // deployPath: getProgram("mpl_account_compression.so"), + // }, + // { + // label: "MPL Noop", + // programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", + // deployPath: getProgram("mpl_noop.so"), + // }, { label: "Mpl Bubblegum", programId: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", From 52dd1dccf26083dac6712e757f1bc58e9a6ba629 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:47:29 -0700 Subject: [PATCH 10/15] fix ts lint issue --- clients/js/src/createTree.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/clients/js/src/createTree.ts b/clients/js/src/createTree.ts index b598f021..3aa780c8 100644 --- a/clients/js/src/createTree.ts +++ b/clients/js/src/createTree.ts @@ -27,6 +27,18 @@ export const createTree = async ( getMerkleTreeSize(input.maxDepth, input.maxBufferSize, input.canopyDepth); const lamports = await context.rpc.getRent(space); + let programId; + if (input.compressionProgram) { + programId = Array.isArray(input.compressionProgram) + ? input.compressionProgram[0] + : input.compressionProgram; + } else { + programId = context.programs.getPublicKey( + 'splAccountCompression', + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID + ); + } + return ( transactionBuilder() // Create the empty Merkle tree account. @@ -36,14 +48,7 @@ export const createTree = async ( newAccount: input.merkleTree, lamports, space, - programId: input.compressionProgram - ? Array.isArray(input.compressionProgram) - ? input.compressionProgram[0] - : input.compressionProgram - : context.programs.getPublicKey( - 'splAccountCompression', - SPL_ACCOUNT_COMPRESSION_PROGRAM_ID - ), + programId, }) ) // Create the tree config. From e20cc0f1614a4fc0ff8ba28254253f0183c28b4b Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 18 Oct 2024 01:49:24 -0700 Subject: [PATCH 11/15] Download mpl-account-compression programs and reactivate test --- clients/js/test/transfer.test.ts | 2 +- configs/scripts/program/dump.sh | 89 ++++++++++++++++++++++---------- configs/validator.cjs | 20 +++---- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index c8e2f2e7..a6c77ade 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -55,7 +55,7 @@ test('it can transfer a compressed NFT', async (t) => { t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); -test.skip('it can transfer a compressed NFT using different programs', async (t) => { +test('it can transfer a compressed NFT using different programs', async (t) => { // Given a tree with a minted NFT owned by leafOwnerA. const umi = await createUmi(); const merkleTree = await createTree(umi, { diff --git a/configs/scripts/program/dump.sh b/configs/scripts/program/dump.sh index 386df6c6..68d85ee2 100755 --- a/configs/scripts/program/dump.sh +++ b/configs/scripts/program/dump.sh @@ -1,7 +1,9 @@ #!/bin/bash -EXTERNAL_ID=("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV") -EXTERNAL_SO=("mpl_token_metadata.so" "spl_account_compression.so" "spl_noop.so") +EXTERNAL_ID_MAINNET=("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV") +EXTERNAL_SO_MAINNET=("mpl_token_metadata.so" "spl_account_compression.so" "spl_noop.so") +EXTERNAL_ID_DEVNET=("mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW" "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3") +EXTERNAL_SO_DEVNET=("mpl_account_compression.so" "mpl_noop.so") # output colours RED() { echo $'\e[1;31m'$1$'\e[0m'; } @@ -15,9 +17,8 @@ cd $(dirname $(dirname $(dirname $SCRIPT_DIR))) OUTPUT=$1 -if [ -z ${RPC+x} ]; then - RPC="https://api.mainnet-beta.solana.com" -fi +RPC_MAINNET="https://api.mainnet-beta.solana.com" +RPC_DEVNET="https://api.devnet.solana.com" if [ -z "$OUTPUT" ]; then echo "missing output directory" @@ -29,56 +30,90 @@ if [ ! -d ${OUTPUT} ]; then mkdir ${OUTPUT} fi -# only prints this if we have external programs -if [ ${#EXTERNAL_ID[@]} -gt 0 ]; then - echo "Dumping external accounts to '${OUTPUT}':" -fi - # copy external programs or accounts binaries from the chain copy_from_chain() { - ACCOUNT_TYPE=`echo $1 | cut -d. -f2` - PREFIX=$2 + RPC=$1 + ACCOUNT_ID=$2 + ACCOUNT_TYPE=`echo $3 | cut -d. -f2` + PREFIX=$4 case "$ACCOUNT_TYPE" in "bin") - solana account -u $RPC ${EXTERNAL_ID[$i]} -o ${OUTPUT}/$2$1 > /dev/null + solana account -u "$RPC" "$ACCOUNT_ID" -o ${OUTPUT}/$4$3 > /dev/null || { + echo $(RED "[ ERROR ] Failed to dump program '$ACCOUNT_ID'") + exit 1 + } ;; "so") - solana program dump -u $RPC ${EXTERNAL_ID[$i]} ${OUTPUT}/$2$1 > /dev/null + solana program dump -u "$RPC" "$ACCOUNT_ID" ${OUTPUT}/$4$3 > /dev/null|| { + echo $(RED "[ ERROR ] Failed to dump program '$ACCOUNT_ID'") + exit 1 + } ;; *) - echo $(RED "[ ERROR ] unknown account type for '$1'") + echo $(RED "[ ERROR ] unknown account type for '$3'") exit 1 ;; esac if [ -z "$PREFIX" ]; then - echo "Wrote account data to ${OUTPUT}/$2$1" + echo "Wrote account data to ${OUTPUT}/$4$3" fi } -# dump external programs binaries if needed -for i in ${!EXTERNAL_ID[@]}; do - if [ ! -f "${OUTPUT}/${EXTERNAL_SO[$i]}" ]; then - copy_from_chain "${EXTERNAL_SO[$i]}" +# only prints this if we have mainnet external programs +if [ ${#EXTERNAL_ID_MAINNET[@]} -gt 0 ]; then + echo "Dumping external accounts from mainnet to '${OUTPUT}':" +fi + +# dump mainnet external programs binaries if needed +for i in ${!EXTERNAL_ID_MAINNET[@]}; do + if [ ! -f "${OUTPUT}/${EXTERNAL_SO_MAINNET[$i]}" ]; then + copy_from_chain $RPC_MAINNET "${EXTERNAL_ID_MAINNET[$i]}" "${EXTERNAL_SO_MAINNET[$i]}" + else + copy_from_chain $RPC_MAINNET "${EXTERNAL_ID_MAINNET[$i]}" "${EXTERNAL_SO_MAINNET[$i]}" "onchain-" + + ON_CHAIN=`sha256sum -b ${OUTPUT}/onchain-${EXTERNAL_SO_MAINNET[$i]} | cut -d ' ' -f 1` + LOCAL=`sha256sum -b ${OUTPUT}/${EXTERNAL_SO_MAINNET[$i]} | cut -d ' ' -f 1` + + if [ "$ON_CHAIN" != "$LOCAL" ]; then + echo $(YLW "[ WARNING ] on-chain and local binaries are different for '${EXTERNAL_SO_MAINNET[$i]}'") + else + echo "$(GRN "[ SKIPPED ]") on-chain and local binaries are the same for '${EXTERNAL_SO_MAINNET[$i]}'" + fi + + rm ${OUTPUT}/onchain-${EXTERNAL_SO_MAINNET[$i]} + fi +done + +# only prints this if we have devnet external programs +if [ ${#EXTERNAL_ID_DEVNET[@]} -gt 0 ]; then + echo "" + echo "Dumping external accounts from devnet to '${OUTPUT}':" +fi + +# dump devnet external programs binaries if needed +for i in ${!EXTERNAL_ID_DEVNET[@]}; do + if [ ! -f "${OUTPUT}/${EXTERNAL_SO_DEVNET[$i]}" ]; then + copy_from_chain $RPC_DEVNET "${EXTERNAL_ID_DEVNET[$i]}" "${EXTERNAL_SO_DEVNET[$i]}" else - copy_from_chain "${EXTERNAL_SO[$i]}" "onchain-" + copy_from_chain $RPC_DEVNET "${EXTERNAL_ID_DEVNET[$i]}" "${EXTERNAL_SO_DEVNET[$i]}" "onchain-" - ON_CHAIN=`sha256sum -b ${OUTPUT}/onchain-${EXTERNAL_SO[$i]} | cut -d ' ' -f 1` - LOCAL=`sha256sum -b ${OUTPUT}/${EXTERNAL_SO[$i]} | cut -d ' ' -f 1` + ON_CHAIN=`sha256sum -b ${OUTPUT}/onchain-${EXTERNAL_SO_DEVNET[$i]} | cut -d ' ' -f 1` + LOCAL=`sha256sum -b ${OUTPUT}/${EXTERNAL_SO_DEVNET[$i]} | cut -d ' ' -f 1` if [ "$ON_CHAIN" != "$LOCAL" ]; then - echo $(YLW "[ WARNING ] on-chain and local binaries are different for '${EXTERNAL_SO[$i]}'") + echo $(YLW "[ WARNING ] on-chain and local binaries are different for '${EXTERNAL_SO_DEVNET[$i]}'") else - echo "$(GRN "[ SKIPPED ]") on-chain and local binaries are the same for '${EXTERNAL_SO[$i]}'" + echo "$(GRN "[ SKIPPED ]") on-chain and local binaries are the same for '${EXTERNAL_SO_DEVNET[$i]}'" fi - rm ${OUTPUT}/onchain-${EXTERNAL_SO[$i]} + rm ${OUTPUT}/onchain-${EXTERNAL_SO_DEVNET[$i]} fi done # only prints this if we have external programs -if [ ${#EXTERNAL_ID[@]} -gt 0 ]; then +if [ ${#EXTERNAL_ID_MAINNET[@]} -gt 0 ] || [ ${#EXTERNAL_ID_DEVNET[@]} -gt 0 ]; then echo "" fi diff --git a/configs/validator.cjs b/configs/validator.cjs index 397ca8fb..f96bbfb1 100755 --- a/configs/validator.cjs +++ b/configs/validator.cjs @@ -10,16 +10,16 @@ module.exports = { validator: { commitment: "processed", programs: [ - // { - // label: "MPL Account Compression", - // programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", - // deployPath: getProgram("mpl_account_compression.so"), - // }, - // { - // label: "MPL Noop", - // programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", - // deployPath: getProgram("mpl_noop.so"), - // }, + { + label: "MPL Account Compression", + programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", + deployPath: getProgram("mpl_account_compression.so"), + }, + { + label: "MPL Noop", + programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", + deployPath: getProgram("mpl_noop.so"), + }, { label: "Mpl Bubblegum", programId: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", From cb7bd4885353b6715eba3d64f896cbe82f2cdb74 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Tue, 22 Oct 2024 01:30:32 -0700 Subject: [PATCH 12/15] Add tests --- clients/js/test/createTree.test.ts | 253 ++++++++++++++++++++++++++++- clients/js/test/transfer.test.ts | 87 +++++++++- 2 files changed, 337 insertions(+), 3 deletions(-) diff --git a/clients/js/test/createTree.test.ts b/clients/js/test/createTree.test.ts index e40a6fb7..7eaccc56 100644 --- a/clients/js/test/createTree.test.ts +++ b/clients/js/test/createTree.test.ts @@ -1,8 +1,75 @@ -import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import { createAccount } from '@metaplex-foundation/mpl-toolbox'; +import { + generateSigner, + publicKey, + Context, + Signer, + TransactionBuilder, + transactionBuilder, + PublicKey, +} from '@metaplex-foundation/umi'; import test from 'ava'; -import { TreeConfig, createTree, fetchTreeConfigFromSeeds } from '../src'; +import { + TreeConfig, + createTree, + createTreeConfig, + fetchTreeConfigFromSeeds, + safeFetchTreeConfigFromSeeds, + getMerkleTreeSize, + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, +} from '../src'; import { createUmi } from './_setup'; +const createTreeWithSpecificMerkleOwner = async ( + context: Parameters[0] & + Parameters[0] & + Pick, + input: Omit[1], 'merkleTree'> & { + merkleTree: Signer; + merkleTreeSize?: number; + canopyDepth?: number; + merkleTreeOwner?: PublicKey; + } +): Promise => { + const space = + input.merkleTreeSize ?? + getMerkleTreeSize(input.maxDepth, input.maxBufferSize, input.canopyDepth); + const lamports = await context.rpc.getRent(space); + + let programId; + if (input.compressionProgram) { + programId = Array.isArray(input.compressionProgram) + ? input.compressionProgram[0] + : input.compressionProgram; + } else { + programId = context.programs.getPublicKey( + 'splAccountCompression', + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID + ); + } + + return ( + transactionBuilder() + // Create the empty Merkle tree account. + .add( + createAccount(context, { + payer: input.payer ?? context.payer, + newAccount: input.merkleTree, + lamports, + space, + programId: input.merkleTreeOwner ? input.merkleTreeOwner : programId, + }) + ) + // Create the tree config. + .add( + createTreeConfig(context, { + ...input, + merkleTree: input.merkleTree.publicKey, + }) + ) + ); +}; + test('it can create a Bubblegum tree', async (t) => { // Given a brand new merkle tree signer. const umi = await createUmi(); @@ -60,3 +127,185 @@ test('it can create a Bubblegum tree using a newer size', async (t) => { isPublic: false, }); }); + +test('it can create a Bubblegum tree using mpl-account-compression and mpl-noop', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTree(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }); + await builder.sendAndConfirm(umi); + + // Then an account exists at the merkle tree address. + t.true(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was created with the correct data. + const treeConfig = await fetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.like(treeConfig, { + treeCreator: publicKey(umi.identity), + treeDelegate: publicKey(umi.identity), + totalMintCapacity: 2n ** 14n, + numMinted: 0n, + isPublic: false, + }); +}); + +test('it cannot create a Bubblegum tree using invalid logWrapper with spl-account-compression', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTree(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + logWrapper: generateSigner(umi).publicKey, + }); + + const promise = builder.sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidLogWrapper' }); + + // And an account does not exist at the merkle tree address. + t.false(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was not created with the correct data. + const treeConfig = await safeFetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.is(treeConfig, null); +}); + +test('it cannot create a Bubblegum tree using invalid logWrapper with mpl-account-compression', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTree(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + logWrapper: generateSigner(umi).publicKey, + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }); + + const promise = builder.sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidLogWrapper' }); + + // And an account does not exist at the merkle tree address. + t.false(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was not created with the correct data. + const treeConfig = await safeFetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.is(treeConfig, null); +}); + +test('it cannot create a Bubblegum tree when compression program does not match tree owned by spl-account-compression', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTreeWithSpecificMerkleOwner(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + compressionProgram: generateSigner(umi).publicKey, + merkleTreeOwner: umi.programs.getPublicKey( + 'splAccountCompression', + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID + ), + }); + + const promise = builder.sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidCompressionProgram' }); + + // And an account does not exist at the merkle tree address. + t.false(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was not created with the correct data. + const treeConfig = await safeFetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.is(treeConfig, null); +}); + +test('it cannot create a Bubblegum tree when compression program does not match tree owned by mpl-account-compression', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTreeWithSpecificMerkleOwner(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: generateSigner(umi).publicKey, + merkleTreeOwner: publicKey('mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW'), + }); + + const promise = builder.sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidCompressionProgram' }); + + // And an account does not exist at the merkle tree address. + t.false(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was not created with the correct data. + const treeConfig = await safeFetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.is(treeConfig, null); +}); + +test('it cannot create a Bubblegum tree with incorrect Merkle tree owner', async (t) => { + // Given a brand new merkle tree signer. + const umi = await createUmi(); + const merkleTree = generateSigner(umi); + + // When we create a tree at this address. + const builder = await createTreeWithSpecificMerkleOwner(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, + merkleTreeOwner: generateSigner(umi).publicKey, + }); + + const promise = builder.sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'IncorrectOwner' }); + + // And an account does not exist at the merkle tree address. + t.false(await umi.rpc.accountExists(merkleTree.publicKey)); + + // And a tree config was not created with the correct data. + const treeConfig = await safeFetchTreeConfigFromSeeds(umi, { + merkleTree: merkleTree.publicKey, + }); + t.is(treeConfig, null); +}); diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index a6c77ade..4f8366c2 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -55,7 +55,7 @@ test('it can transfer a compressed NFT', async (t) => { t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); -test('it can transfer a compressed NFT using different programs', async (t) => { +test('it can transfer a compressed NFT using mpl-account-compression and mpl-noop', async (t) => { // Given a tree with a minted NFT owned by leafOwnerA. const umi = await createUmi(); const merkleTree = await createTree(umi, { @@ -104,6 +104,91 @@ test('it can transfer a compressed NFT using different programs', async (t) => { t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); +test('it cannot transfer a compressed NFT owned by spl-account-compression using mpl programs', async (t) => { + // Given a tree with a minted NFT owned by leafOwnerA. + const umi = await createUmi(); + const merkleTree = await createTree(umi); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwnerA = generateSigner(umi); + const { metadata, leafIndex } = await mint(umi, { + merkleTree, + leafOwner: leafOwnerA.publicKey, + }); + + // When leafOwnerA transfers the NFT to leafOwnerB. + const leafOwnerB = generateSigner(umi); + const promise = transfer(umi, { + leafOwner: leafOwnerA, + newLeafOwner: leafOwnerB.publicKey, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + dataHash: hashMetadataData(metadata), + creatorHash: hashMetadataCreators(metadata.creators), + nonce: leafIndex, + index: leafIndex, + proof: [], + logWrapper: publicKey('mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'), + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidLogWrapper' }); + + // Then the leaf was not updated in the merkle tree. + const originalLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwnerA.publicKey, + leafIndex, + metadata, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(originalLeaf)); +}); + +test('it cannot transfer a compressed NFT owned by spl-account-compression using mpl-account-compression', async (t) => { + // Given a tree with a minted NFT owned by leafOwnerA. + const umi = await createUmi(); + const merkleTree = await createTree(umi); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwnerA = generateSigner(umi); + const { metadata, leafIndex } = await mint(umi, { + merkleTree, + leafOwner: leafOwnerA.publicKey, + }); + + // When leafOwnerA transfers the NFT to leafOwnerB. + const leafOwnerB = generateSigner(umi); + const promise = transfer(umi, { + leafOwner: leafOwnerA, + newLeafOwner: leafOwnerB.publicKey, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + dataHash: hashMetadataData(metadata), + creatorHash: hashMetadataCreators(metadata.creators), + nonce: leafIndex, + index: leafIndex, + proof: [], + compressionProgram: publicKey( + 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' + ), + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidCompressionProgram' }); + + // Then the leaf was not updated in the merkle tree. + const originalLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwnerA.publicKey, + leafIndex, + metadata, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(originalLeaf)); +}); + test('it can transfer a compressed NFT as a delegated authority', async (t) => { // Given a tree with a delegated compressed NFT owned by leafOwnerA. const umi = await createUmi(); From 38b3e002a38bd30691b0c05c4fe39553605b51b0 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Tue, 22 Oct 2024 01:36:07 -0700 Subject: [PATCH 13/15] Remove log wrapper validation in decompress as it's unused --- .../bubblegum/program/src/processor/decompress.rs | 6 ++---- programs/bubblegum/program/src/utils.rs | 11 ----------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/programs/bubblegum/program/src/processor/decompress.rs b/programs/bubblegum/program/src/processor/decompress.rs index f155ff67..476f0bbe 100644 --- a/programs/bubblegum/program/src/processor/decompress.rs +++ b/programs/bubblegum/program/src/processor/decompress.rs @@ -19,7 +19,7 @@ use crate::{ metaplex_anchor::MplTokenMetadata, Voucher, ASSET_PREFIX, VOUCHER_PREFIX, }, - utils::{cmp_bytes, cmp_pubkeys, hash_metadata, validate_log_wrapper_program}, + utils::{cmp_bytes, cmp_pubkeys, hash_metadata}, }; #[derive(Accounts)] @@ -70,13 +70,11 @@ pub struct DecompressV1<'info> { pub token_metadata_program: Program<'info, MplTokenMetadata>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, - /// CHECK: Program is verified in the instruction + /// CHECK: Program is not used in the instruction pub log_wrapper: UncheckedAccount<'info>, } pub(crate) fn decompress_v1(ctx: Context, metadata: MetadataArgs) -> Result<()> { - validate_log_wrapper_program(&ctx.accounts.log_wrapper)?; - // Validate the incoming metadata match ctx.accounts.voucher.leaf_schema { diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index 36aa61b5..b7e236a1 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -227,14 +227,3 @@ pub(crate) fn validate_ownership_and_programs( Ok(()) } - -/// Validate the provided log wrapper program is one of the valid choices. -pub(crate) fn validate_log_wrapper_program(log_wrapper: &AccountInfo<'_>) -> Result<()> { - require!( - log_wrapper.key == &spl_noop::id() || log_wrapper.key == &mpl_noop::id(), - BubblegumError::InvalidLogWrapper - ); - require!(log_wrapper.executable, BubblegumError::InvalidLogWrapper); - - Ok(()) -} From 8f3107546963e367cf43a4136b512d6eac305048 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:16:39 -0700 Subject: [PATCH 14/15] Update to latest spl-account-compression --- programs/bubblegum/Cargo.lock | 30 ++++++++------------------- programs/bubblegum/program/Cargo.toml | 5 ++--- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 4ccc935f..207be02f 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -698,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -849,8 +849,7 @@ dependencies = [ "solana-sdk", "spl-account-compression", "spl-associated-token-account", - "spl-concurrent-merkle-tree 0.3.0", - "spl-concurrent-merkle-tree 0.4.1", + "spl-concurrent-merkle-tree", "spl-merkle-tree-reference", "spl-noop", "spl-token", @@ -2424,7 +2423,7 @@ dependencies = [ "bytemuck", "mpl-noop", "solana-program", - "spl-concurrent-merkle-tree 0.4.1", + "spl-concurrent-merkle-tree", ] [[package]] @@ -2663,7 +2662,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.76", @@ -4968,14 +4967,14 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f461e20e8efb539d7f3f30cd82931ee128651c6366abe326f083b07253a1d6" +checksum = "2785042005954aec5d5db7fcb99a78754b222be906a89d10a3d66ebdbc8e9548" dependencies = [ "anchor-lang", "bytemuck", "solana-program", - "spl-concurrent-merkle-tree 0.4.1", + "spl-concurrent-merkle-tree", "spl-noop", ] @@ -4995,17 +4994,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "spl-concurrent-merkle-tree" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5f45b971d82cbb0416fdffad3c9098f259545d54072e83a0a482f60f8f689" -dependencies = [ - "bytemuck", - "solana-program", - "thiserror", -] - [[package]] name = "spl-concurrent-merkle-tree" version = "0.4.1" @@ -5063,9 +5051,9 @@ dependencies = [ [[package]] name = "spl-merkle-tree-reference" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28437c617c7f0db6b7229a489239f3ea6160499542d9367fbca2fc5ec7744abb" +checksum = "70d540c0983d5214dbba3cc7f708e2f5ac7d99294e1f633fd36178a22434285d" dependencies = [ "solana-program", "thiserror", diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index 199867ae..4680315d 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -28,7 +28,7 @@ mpl-noop = { version = "0.2.1", features = ["no-entrypoint"] } mpl-token-metadata = "4.1.2" num-traits = "0.2.15" solana-program = "~1.18.15" -spl-account-compression = { version = "0.4.0", features = ["cpi"] } +spl-account-compression = { version = "0.4.2", features = ["cpi"] } spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } spl-concurrent-merkle-tree = { version = "0.4.1" } spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } @@ -39,5 +39,4 @@ async-trait = "0.1.71" mpl-token-auth-rules = { version = "1.5.1", features = ["no-entrypoint"] } solana-program-test = "~1.18.15" solana-sdk = "~1.18.15" -spl-concurrent-merkle-tree = "0.3.0" -spl-merkle-tree-reference = "0.1.0" +spl-merkle-tree-reference = "0.1.1" From bd82d284066c820c3a7eef57970c38da4329bf1b Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 24 Oct 2024 01:13:01 -0700 Subject: [PATCH 15/15] Update program download script --- configs/scripts/program/dump.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/configs/scripts/program/dump.sh b/configs/scripts/program/dump.sh index 68d85ee2..eabff05a 100755 --- a/configs/scripts/program/dump.sh +++ b/configs/scripts/program/dump.sh @@ -22,6 +22,7 @@ RPC_DEVNET="https://api.devnet.solana.com" if [ -z "$OUTPUT" ]; then echo "missing output directory" + cd ${CURRENT_DIR} exit 1 fi @@ -41,17 +42,20 @@ copy_from_chain() { "bin") solana account -u "$RPC" "$ACCOUNT_ID" -o ${OUTPUT}/$4$3 > /dev/null || { echo $(RED "[ ERROR ] Failed to dump program '$ACCOUNT_ID'") + cd ${CURRENT_DIR} exit 1 } ;; "so") - solana program dump -u "$RPC" "$ACCOUNT_ID" ${OUTPUT}/$4$3 > /dev/null|| { + solana program dump -u "$RPC" "$ACCOUNT_ID" ${OUTPUT}/$4$3 > /dev/null || { echo $(RED "[ ERROR ] Failed to dump program '$ACCOUNT_ID'") + cd ${CURRENT_DIR} exit 1 } ;; *) echo $(RED "[ ERROR ] unknown account type for '$3'") + cd ${CURRENT_DIR} exit 1 ;; esac