diff --git a/clients/rust/Cargo.lock b/clients/rust/Cargo.lock index 348d21d..aa80f32 100644 --- a/clients/rust/Cargo.lock +++ b/clients/rust/Cargo.lock @@ -4853,7 +4853,7 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.4.0" +version = "0.3.0" dependencies = [ "anchor-lang", "bytemuck", @@ -5537,18 +5537,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/mplx-staking b/mplx-staking index d23d818..77f4e07 160000 --- a/mplx-staking +++ b/mplx-staking @@ -1 +1 @@ -Subproject commit d23d818cddbd771ff082a9ff048d00ebaf36e009 +Subproject commit 77f4e074ede2300f05f34cc9fc665266b61d9ad9 diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 84c3433..2fcd75f 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -1020,11 +1020,11 @@ dependencies = [ "solana-program-test", "solana-sdk", "spl-account-compression", - "spl-associated-token-account 1.1.3", + "spl-associated-token-account 2.3.0", "spl-concurrent-merkle-tree", "spl-merkle-tree-reference", "spl-noop", - "spl-token 3.5.0", + "spl-token 4.0.0", ] [[package]] @@ -2831,7 +2831,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.63", @@ -5142,7 +5142,7 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.4.0" +version = "0.3.0" dependencies = [ "anchor-lang 0.29.0", "bytemuck", @@ -5740,18 +5740,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index f36c71b..0337f54 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -122,8 +122,8 @@ pub mod bubblegum { } /// Creates a new tree. - pub fn create_tree( - ctx: Context, + pub fn create_tree<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTree<'info>>, max_depth: u32, max_buffer_size: u32, public: Option, diff --git a/programs/bubblegum/program/src/processor/create_tree.rs b/programs/bubblegum/program/src/processor/create_tree.rs index 0420b61..eab8613 100644 --- a/programs/bubblegum/program/src/processor/create_tree.rs +++ b/programs/bubblegum/program/src/processor/create_tree.rs @@ -1,21 +1,13 @@ -use std::mem::size_of; - 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, + Noop, }; use crate::{ - error::BubblegumError, - state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, + state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}, utils::check_canopy_size, }; -pub const MAX_ACC_PROOFS_SIZE: u32 = 17; - #[derive(Accounts)] pub struct CreateTree<'info> { #[account( @@ -37,15 +29,15 @@ pub struct CreateTree<'info> { pub system_program: Program<'info, System>, } -pub(crate) fn create_tree( - ctx: Context, +pub(crate) fn create_tree<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTree<'info>>, max_depth: u32, max_buffer_size: u32, public: Option, ) -> Result<()> { let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); - check_canopy_size(&ctx, max_depth, max_buffer_size)?; + check_canopy_size(ctx.accounts.merkle_tree.to_account_info(), ctx.accounts.tree_authority.to_account_info(), max_depth, max_buffer_size)?; let seed = merkle_tree.key(); let seeds = &[seed.as_ref(), &[ctx.bumps.tree_authority]]; @@ -70,36 +62,3 @@ pub(crate) fn create_tree( ); spl_account_compression::cpi::init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size) } - -fn check_canopy_size( - ctx: &Context, - max_depth: u32, - max_buffer_size: u32, -) -> Result<()> { - 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); - - let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; - header.initialize( - max_depth, - max_buffer_size, - &ctx.accounts.tree_authority.key(), - Clock::get()?.slot, - ); - - let merkle_tree_size = merkle_tree_get_size(&header)?; - - let (_tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size); - - let required_canopy = max_depth.saturating_sub(MAX_ACC_PROOFS_SIZE); - - let actual_canopy_size = canopy_bytes.len() / size_of::(); - - require!( - (actual_canopy_size as u32) >= required_canopy, - BubblegumError::InvalidCanopySize - ); - - Ok(()) -} diff --git a/programs/bubblegum/program/src/processor/prepare_tree.rs b/programs/bubblegum/program/src/processor/prepare_tree.rs index ab9c37c..5ae4200 100644 --- a/programs/bubblegum/program/src/processor/prepare_tree.rs +++ b/programs/bubblegum/program/src/processor/prepare_tree.rs @@ -1,7 +1,11 @@ use anchor_lang::{prelude::*, system_program::System}; use spl_account_compression::{program::SplAccountCompression, Noop}; -use crate::state::{DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE}; +use crate::{ + state::{ + DecompressibleState, TreeConfig, TREE_AUTHORITY_SIZE, + }, utils::check_canopy_size, +}; #[derive(Accounts)] pub struct PrepareTree<'info> { @@ -31,7 +35,7 @@ pub(crate) fn prepare_tree<'info>( max_buffer_size: u32, public: Option, ) -> Result<()> { - // TODO: check canopy size + check_canopy_size(ctx.accounts.merkle_tree.to_account_info(), ctx.accounts.tree_authority.to_account_info(), max_depth, max_buffer_size)?; let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); let seed = merkle_tree.key(); diff --git a/programs/bubblegum/program/src/state/mod.rs b/programs/bubblegum/program/src/state/mod.rs index a44407a..90118a1 100644 --- a/programs/bubblegum/program/src/state/mod.rs +++ b/programs/bubblegum/program/src/state/mod.rs @@ -12,6 +12,8 @@ pub const VOUCHER_PREFIX: &str = "voucher"; pub const ASSET_PREFIX: &str = "asset"; pub const COLLECTION_CPI_PREFIX: &str = "collection_cpi"; +pub const MAX_ACC_PROOFS_SIZE: u32 = 17; + // TODO: set real keys before mainnet deploy pub const REALM: Pubkey = solana_program::pubkey!("EzsKaQq68FLZwRaiUx7t17LWVVzsE8wRkhBghFrZGGwG"); pub const REALM_GOVERNING_MINT: Pubkey = diff --git a/programs/bubblegum/program/src/utils.rs b/programs/bubblegum/program/src/utils.rs index 3dfc915..53c28b2 100644 --- a/programs/bubblegum/program/src/utils.rs +++ b/programs/bubblegum/program/src/utils.rs @@ -1,13 +1,15 @@ -use crate::state::{ +use std::mem::size_of; + +use crate::{error::BubblegumError, state::{ metaplex_adapter::{Creator, MetadataArgs}, - ASSET_PREFIX, -}; + ASSET_PREFIX, MAX_ACC_PROOFS_SIZE, +}}; use anchor_lang::{ prelude::*, solana_program::{program_memory::sol_memcmp, pubkey::PUBKEY_BYTES}, }; use solana_program::keccak; -use spl_account_compression::Node; +use spl_account_compression::{state::{merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1}, Node}; pub fn hash_creators(creators: &[Creator]) -> Result<[u8; 32]> { // Convert creator Vec to bytes Vec. @@ -106,3 +108,37 @@ pub fn get_asset_id(tree_id: &Pubkey, nonce: u64) -> Pubkey { ) .0 } + +pub(crate) fn check_canopy_size<'info>( + merkle_tree: AccountInfo<'info>, + tree_authority: AccountInfo<'info>, + max_depth: u32, + max_buffer_size: u32, +) -> Result<()> { + let merkle_tree_bytes = merkle_tree.data.borrow(); + + let (header_bytes, rest) = merkle_tree_bytes.split_at(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); + + let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; + header.initialize( + max_depth, + max_buffer_size, + &tree_authority.key(), + Clock::get()?.slot, + ); + + let merkle_tree_size = merkle_tree_get_size(&header)?; + + let (_tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size); + + let required_canopy = max_depth.saturating_sub(MAX_ACC_PROOFS_SIZE); + + let actual_canopy_size = canopy_bytes.len() / size_of::(); + + require!( + (actual_canopy_size as u32) >= required_canopy, + BubblegumError::InvalidCanopySize + ); + + Ok(()) +} diff --git a/programs/bubblegum/program/tests/rollup.rs b/programs/bubblegum/program/tests/rollup.rs index b0ab7e0..aaba1dc 100644 --- a/programs/bubblegum/program/tests/rollup.rs +++ b/programs/bubblegum/program/tests/rollup.rs @@ -604,3 +604,167 @@ async fn test_put_wrong_canopy() { panic!("Should have failed"); } } + +#[tokio::test] +async fn test_prepare_with_small_canopy() { + let mut program_context = BubblegumTestContext::new().await.unwrap(); + + let merkle_tree = MerkleTree::new(vec![Node::default(); 1 << 20].as_slice()); + + let governance_program_id = + Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); + let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); + let voter_authority = program_context.test_context().payer.pubkey(); + + let mplx_mint_key = Pubkey::new_unique(); + let grant_authority = Pubkey::new_unique(); + + let registrar_key = Pubkey::find_program_address( + &[ + REALM.to_bytes().as_ref(), + b"registrar".as_ref(), + REALM_GOVERNING_MINT.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ) + .0; + + let (voter_key, voter_bump) = Pubkey::find_program_address( + &[ + registrar_key.to_bytes().as_ref(), + b"voter".as_ref(), + voter_authority.to_bytes().as_ref(), + ], + &mplx_staking_states::ID, + ); + + // // init structs for Registrar and Voter and fill it in with data + let voting_mint_config = VotingMintConfig { + mint: mplx_mint_key, + grant_authority, + baseline_vote_weight_scaled_factor: 0, + max_extra_lockup_vote_weight_scaled_factor: 0, + lockup_saturation_secs: 0, + digit_shift: 0, + padding: [0, 0, 0, 0, 0, 0, 0], + }; + + let registrar = Registrar { + governance_program_id, + realm: REALM, + realm_governing_token_mint: REALM_GOVERNING_MINT, + realm_authority, + voting_mints: [ + voting_mint_config, + voting_mint_config, + voting_mint_config, + voting_mint_config, + ], + time_offset: 0, + bump: 0, + }; + + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let lockup = Lockup { + start_ts: 0, + end_ts: current_time + 100, + cooldown_ends_at: 0, + cooldown_requested: false, + kind: LockupKind::Constant, + period: LockupPeriod::ThreeMonths, + _reserved1: [0; 5], + }; + + let deposit_entry = DepositEntry { + lockup: lockup.clone(), + amount_deposited_native: 100000000, + voting_mint_config_idx: 0, + is_used: true, + _reserved1: [0; 6], + }; + + let deposit_entries = [deposit_entry; 32]; + + let voter = Voter { + deposits: deposit_entries, + voter_authority, + registrar: registrar_key, + voter_bump, + voter_weight_record_bump: 0, + _reserved1: [0; 14], + }; + + let registrar_acc_data = [ + REGISTRAR_DISCRIMINATOR.as_ref(), + bytemuck::bytes_of(®istrar), + ] + .concat(); + let voter_acc_data = [VOTER_DISCRIMINATOR.as_ref(), bytemuck::bytes_of(&voter)].concat(); + + // for next two accounts set arbitrary balance because it doesn't meter for test + let mut registrar_account = AccountSharedData::new( + 10000000000000000, + registrar_acc_data.len(), + &mplx_staking_states::ID, + ); + registrar_account.set_data_from_slice(registrar_acc_data.as_ref()); + + let mut voter_account = AccountSharedData::new( + 10000000000000000, + voter_acc_data.len(), + &mplx_staking_states::ID, + ); + voter_account.set_data_from_slice(voter_acc_data.as_ref()); + + program_context + .mut_test_context() + .set_account(®istrar_key, ®istrar_account); + program_context + .mut_test_context() + .set_account(&voter_key, &voter_account); + + let tree_creator = Keypair::from_bytes(TREE_CREATOR.as_ref()).unwrap(); + + let tree_key = Keypair::from_bytes(TREE_KEY.as_ref()).unwrap(); + + let mut tree = Tree::<20, 64>::with_preinitialised_tree( + &tree_creator, + &tree_key, + program_context.client(), + merkle_tree, + 1000, + 0, + ); + + tree.alloc(&program_context.test_context().payer) + .await + .unwrap(); + + let mut tree_tx_builder = tree.prepare_tree_tx( + &program_context.test_context().payer, + false, + 20, + 64, + registrar_key, + voter_key, + ); + + let res = tree_tx_builder.execute_without_root_check().await; + + if let Err(err) = res { + if let BanksClient(BanksClientError::TransactionError(e)) = *err { + assert_eq!( + e, + TransactionError::InstructionError(0, InstructionError::Custom(6041),) + ); + } else { + panic!("Wrong variant"); + } + } else { + panic!("Should have failed"); + } +} diff --git a/solana-program-library b/solana-program-library index c4f9169..7218128 160000 --- a/solana-program-library +++ b/solana-program-library @@ -1 +1 @@ -Subproject commit c4f916994d679a8055a51b84ff09819a4567c54f +Subproject commit 721812863c383c69e5743573c6bc3b79678c4a14