diff --git a/programs/bubblegum/Cargo.lock b/programs/bubblegum/Cargo.lock index 6575aea..34d2059 100644 --- a/programs/bubblegum/Cargo.lock +++ b/programs/bubblegum/Cargo.lock @@ -1014,17 +1014,18 @@ dependencies = [ "bytemuck", "mpl-token-auth-rules", "mpl-token-metadata", + "mplx-rewards", "mplx-staking-states", "num-traits", "solana-program", "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]] @@ -2333,6 +2334,18 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lib-sokoban" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0640eb476052d9f48e920969f8117b054d1be1a0b2e4055e61293cd2b1afcce1" +dependencies = [ + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "thiserror", +] + [[package]] name = "libc" version = "0.2.154" @@ -2577,8 +2590,9 @@ dependencies = [ [[package]] name = "mpl-token-auth-rules" -version = "1.5.0" -source = "git+https://github.com/metaplex-foundation/mpl-token-auth-rules.git?branch=main#fd9026d68ebc7e1bc2aeb7af73d04e8161eeeaf8" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8637dd3d13d045a43a2410dbb57f0257d506838aa71461faf0eaf2806e6d5071" dependencies = [ "borsh 0.10.3", "bytemuck", @@ -2587,7 +2601,7 @@ dependencies = [ "num-traits", "rmp-serde", "serde", - "shank", + "shank 0.3.0", "solana-program", "solana-zk-token-sdk", "thiserror", @@ -2616,17 +2630,30 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "mplx-rewards" +version = "0.1.0" +source = "git+https://github.com/adm-metaex/mplx-rewards.git#406078fcc3656dfeb57ae776ee81097480a27a62" +dependencies = [ + "borsh 1.5.0", + "bytemuck", + "lib-sokoban", + "num-derive 0.4.2", + "num-traits", + "shank 0.4.2", + "solana-program", + "spl-token 4.0.0", + "thiserror", +] + [[package]] name = "mplx-staking-states" version = "0.0.1" -source = "git+https://github.com/adm-metaex/mplx-staking.git#094f7277c4c7d1a0549415df1b152cc0c525a1c7" +source = "git+https://github.com/adm-metaex/mplx-staking.git#03459b9290d3fe58b5788728b434a6441adc1779" dependencies = [ "anchor-lang 0.26.0", "anchor-spl 0.26.0", - "borsh 0.9.3", - "bytemuck", "static_assertions", - "thiserror", ] [[package]] @@ -2833,7 +2860,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", @@ -3862,7 +3889,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c9395612d493b69a522725eef78a095f199d43eeb847f4a4b77ec0cacab535" dependencies = [ - "shank_macro", + "shank_macro 0.3.0", +] + +[[package]] +name = "shank" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d894855493d4ce613b25550fe1ed1c62d0af5486b984579ba55e3f8c9631d5" +dependencies = [ + "shank_macro 0.4.2", ] [[package]] @@ -3873,8 +3909,21 @@ checksum = "8abef069c02e15f62233679b1e71f3152fac10f90b3ff89ebbad6a25b7497754" dependencies = [ "proc-macro2", "quote", - "shank_macro_impl", - "shank_render", + "shank_macro_impl 0.3.0", + "shank_render 0.3.0", + "syn 1.0.109", +] + +[[package]] +name = "shank_macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9bf2645f8eebde043da69200195058e7b59806705104f908a31d05ca82844ce" +dependencies = [ + "proc-macro2", + "quote", + "shank_macro_impl 0.4.2", + "shank_render 0.4.2", "syn 1.0.109", ] @@ -3891,6 +3940,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "shank_macro_impl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d0593f48acb0a722906416b1f6b8926f6571eb9af16d566a7c65427f269f50" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + [[package]] name = "shank_render" version = "0.3.0" @@ -3899,7 +3961,18 @@ checksum = "5a2ea9c6dd95ea311b3b81e63cf4e9c808ed04b098819e6d2c4b1a467d587203" dependencies = [ "proc-macro2", "quote", - "shank_macro_impl", + "shank_macro_impl 0.3.0", +] + +[[package]] +name = "shank_render" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121175ba61809189f888dc5822ebfd30fa0d91e1e1f61d25a4d40b0847b3075e" +dependencies = [ + "proc-macro2", + "quote", + "shank_macro_impl 0.4.2", ] [[package]] diff --git a/programs/bubblegum/program/Cargo.toml b/programs/bubblegum/program/Cargo.toml index 11756fc..32f88df 100644 --- a/programs/bubblegum/program/Cargo.toml +++ b/programs/bubblegum/program/Cargo.toml @@ -29,11 +29,12 @@ solana-program = "~1.18.11" spl-account-compression = {git = "https://github.com/StanChe/solana-program-library.git", branch = "feature/init_with_root", features = ["cpi"] } spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } -mplx-staking-states = {git = "https://github.com/adm-metaex/mplx-staking.git" } +mplx-staking-states = { git = "https://github.com/adm-metaex/mplx-staking.git" } +mplx-rewards = { git = "https://github.com/adm-metaex/mplx-rewards.git", features = ["no-entrypoint"] } [dev-dependencies] async-trait = "0.1.71" -mpl-token-auth-rules = { git = "https://github.com/metaplex-foundation/mpl-token-auth-rules.git", branch = "main", features = ["no-entrypoint"] } +mpl-token-auth-rules = { version = "1.5.1", features = ["no-entrypoint"] } solana-program-test = "~1.18.11" solana-sdk = "~1.18.11" spl-concurrent-merkle-tree = { git = "https://github.com/StanChe/solana-program-library.git", branch = "feature/init_with_root" } diff --git a/programs/bubblegum/program/src/error.rs b/programs/bubblegum/program/src/error.rs index 6946181..636d734 100644 --- a/programs/bubblegum/program/src/error.rs +++ b/programs/bubblegum/program/src/error.rs @@ -106,6 +106,8 @@ pub enum BubblegumError { StakingVoterRegistrarMismatch, #[msg("Staking voter authority mismatch")] StakingVoterAuthorityMismatch, + #[msg("Invalid mining owner")] + MiningOwnerMismatch, } // Converts certain Token Metadata errors into Bubblegum equivalents diff --git a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs index ab38576..9db3188 100644 --- a/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root.rs @@ -10,6 +10,7 @@ use crate::{ }, }; +const DISCRIMINATOR_LEN: usize = REGISTRAR_DISCRIMINATOR.len(); #[derive(Accounts)] pub struct FinalizeTreeWithRoot<'info> { #[account( @@ -30,6 +31,8 @@ pub struct FinalizeTreeWithRoot<'info> { /// CHECK: pub voter: UncheckedAccount<'info>, /// CHECK: + pub mining: UncheckedAccount<'info>, + /// CHECK: #[account(mut)] pub fee_receiver: UncheckedAccount<'info>, pub log_wrapper: Program<'info, Noop>, @@ -61,6 +64,7 @@ pub(crate) fn finalize_tree_with_root<'info>( &ctx.accounts.staker.to_account_info(), &ctx.accounts.registrar.to_account_info(), &ctx.accounts.voter.to_account_info(), + &ctx.accounts.mining.to_account_info(), )?; let num_minted = (rightmost_index + 1) as u64; @@ -107,6 +111,7 @@ pub(crate) fn check_stake<'info>( staker_acc: &AccountInfo<'info>, registrar_acc: &AccountInfo<'info>, voter_acc: &AccountInfo<'info>, + mining_acc: &AccountInfo<'info>, ) -> Result<()> { require!( registrar_acc.owner == &mplx_staking_states::ID, @@ -116,6 +121,10 @@ pub(crate) fn check_stake<'info>( voter_acc.owner == &mplx_staking_states::ID, BubblegumError::StakingVoterMismatch ); + require!( + mining_acc.owner == &mplx_rewards::ID, + BubblegumError::MiningOwnerMismatch + ); let generated_registrar = Pubkey::find_program_address( &[ @@ -146,13 +155,13 @@ pub(crate) fn check_stake<'info>( ); let registrar_bytes = registrar_acc.to_account_info().data; - + let registrar_bytes = registrar_bytes.borrow(); require!( - (*registrar_bytes.borrow())[..8] == REGISTRAR_DISCRIMINATOR, + registrar_bytes[..DISCRIMINATOR_LEN] == REGISTRAR_DISCRIMINATOR, BubblegumError::StakingRegistrarDiscriminatorMismatch ); - let registrar: Registrar = *bytemuck::from_bytes(&(*registrar_bytes.borrow())[8..]); + let registrar: &Registrar = bytemuck::from_bytes(®istrar_bytes[DISCRIMINATOR_LEN..]); require!( registrar.realm == REALM, @@ -164,12 +173,12 @@ pub(crate) fn check_stake<'info>( ); let voter_bytes = voter_acc.to_account_info().data; + let voter_bytes = voter_bytes.borrow(); require!( - (*voter_bytes.borrow())[..8] == VOTER_DISCRIMINATOR, + voter_bytes[..DISCRIMINATOR_LEN] == VOTER_DISCRIMINATOR, BubblegumError::StakingVoterDiscriminatorMismatch ); - - let voter: Voter = *bytemuck::from_bytes(&(*voter_bytes.borrow())[8..]); + let voter: &Voter = bytemuck::from_bytes(&voter_bytes[DISCRIMINATOR_LEN..]); require!( &voter.registrar == registrar_acc.key, @@ -179,7 +188,12 @@ pub(crate) fn check_stake<'info>( &voter.voter_authority == staker_acc.key, BubblegumError::StakingVoterAuthorityMismatch ); - + let mining_data = mining_acc.data.borrow(); + let mining = mplx_rewards::state::WrappedImmutableMining::from_bytes(&mining_data)?; + require!( + &mining.mining.owner == staker_acc.key, + BubblegumError::MiningOwnerMismatch + ); let clock = Clock::get()?; let curr_ts = clock.unix_timestamp as u64; let weighted_sum: u64 = voter @@ -188,7 +202,11 @@ pub(crate) fn check_stake<'info>( .map(|d| d.weighted_stake(curr_ts)) .sum(); - if weighted_sum < MINIMUM_WEIGHTED_STAKE { + if weighted_sum + .checked_add(mining.mining.stake_from_others) + .ok_or(BubblegumError::NumericalOverflowError)? + < MINIMUM_WEIGHTED_STAKE + { return Err(BubblegumError::NotEnoughStakeForOperation.into()); } diff --git a/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs b/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs index 9b47560..2b69d6a 100644 --- a/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs +++ b/programs/bubblegum/program/src/processor/finalize_tree_with_root_and_collection.rs @@ -27,6 +27,8 @@ pub struct FinalizeTreeWithRootAndCollection<'info> { /// CHECK: pub voter: UncheckedAccount<'info>, /// CHECK: + pub mining: UncheckedAccount<'info>, + /// CHECK: #[account(mut)] pub fee_receiver: UncheckedAccount<'info>, /// CHECK: Optional collection authority record PDA. @@ -95,6 +97,7 @@ impl<'info> From<&mut FinalizeTreeWithRootAndCollection<'info>> for FinalizeTree staker: value.staker.to_owned(), registrar: value.registrar.to_owned(), voter: value.voter.to_owned(), + mining: value.mining.to_owned(), fee_receiver: value.fee_receiver.to_owned(), log_wrapper: value.log_wrapper.to_owned(), compression_program: value.compression_program.to_owned(), diff --git a/programs/bubblegum/program/tests/batch-mint.rs b/programs/bubblegum/program/tests/batch-mint.rs index 2c54281..b722fe7 100644 --- a/programs/bubblegum/program/tests/batch-mint.rs +++ b/programs/bubblegum/program/tests/batch-mint.rs @@ -8,11 +8,12 @@ use bubblegum::{ state::{ metaplex_adapter::{MetadataArgs, TokenProgramVersion, TokenStandard}, FEE_RECEIVER, PROTOCOL_FEE_PER_1024_ASSETS, REALM, REALM_GOVERNING_MINT, + VOTER_DISCRIMINATOR, }, }; use mplx_staking_states::state::{ DepositEntry, Lockup, LockupKind, LockupPeriod, Registrar, Voter, VotingMintConfig, - REGISTRAR_DISCRIMINATOR, VOTER_DISCRIMINATOR, + REGISTRAR_DISCRIMINATOR, }; use solana_program_test::{tokio, BanksClientError}; use solana_sdk::{ @@ -137,13 +138,13 @@ fn reverse_each_couple(vec: &mut Vec) { } } -// This function initializes registrar and voter keys. -// That keys are related to SPL Governance program. +// This function initializes registrar, voter and mining keys. +// Those registrar and voter are related to SPL Governance program. And the mining key is related to the reward program. // Initialization of these account is required because batch creation requires MPLX stake, // and all the user's information about stake is saving on these accounts. async fn initialize_staking_accounts( program_context: &mut BubblegumTestContext, -) -> (Pubkey, Pubkey) { +) -> (Pubkey, Pubkey, Pubkey) { let governance_program_id = Pubkey::from_str("CuyWCRdHT8pZLG793UR5R9z31AC49d47ZW9ggN6P7qZ4").unwrap(); let realm_authority = Pubkey::from_str("Euec5oQGN3Y9kqVrz6PQRfTpYSn6jK3k1JonDiMTzAtA").unwrap(); @@ -151,6 +152,8 @@ async fn initialize_staking_accounts( let mplx_mint_key = Pubkey::new_unique(); let grant_authority = Pubkey::new_unique(); + let mining_key = Pubkey::new_unique(); + let reward_pool_key = Pubkey::new_unique(); let registrar_key = Pubkey::find_program_address( &[ @@ -175,11 +178,6 @@ async fn initialize_staking_accounts( 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 { @@ -187,14 +185,10 @@ async fn initialize_staking_accounts( realm: REALM, realm_governing_token_mint: REALM_GOVERNING_MINT, realm_authority: realm_authority.clone(), - voting_mints: [ - voting_mint_config, - voting_mint_config, - voting_mint_config, - voting_mint_config, - ], - time_offset: 0, + voting_mints: [voting_mint_config, voting_mint_config], + reward_pool: reward_pool_key, bump: 0, + padding: [0; 7], }; let current_time = SystemTime::now() @@ -209,6 +203,7 @@ async fn initialize_staking_accounts( cooldown_requested: false, kind: LockupKind::Constant, period: LockupPeriod::ThreeMonths, + _reserved0: [0; 16], _reserved1: [0; 5], }; @@ -217,6 +212,9 @@ async fn initialize_staking_accounts( amount_deposited_native: 100_000_000_000_000, voting_mint_config_idx: 0, is_used: true, + delegate: Pubkey::new_unique(), + delegate_last_update_ts: 0, + _reserved0: [0; 32], _reserved1: [0; 6], }; @@ -252,15 +250,30 @@ async fn initialize_staking_accounts( &mplx_staking_states::ID, ); voter_account.set_data_from_slice(voter_acc_data.as_ref()); - + let mut mining_acc_data = [0; mplx_rewards::state::WrappedMining::LEN]; + // TODO: good luck trying to make it work with those allignment requirements of the WrappedMining struct, + // let account_type:u8 = mplx_rewards::state::AccountType::Mining.into(); + // mining_acc_data[0] = account_type; + // let mining_acc = mplx_rewards::state::WrappedMining::from_bytes_mut(&mut mining_acc_data) + // .expect("Failed to create mining account"); + // mining_acc.mining.owner = voter_authority; + // mining_acc.mining.stake_from_others = 0; + // so here is a hacky way to set the owner of the mining account directly + mining_acc_data[32..64].copy_from_slice(&voter_authority.to_bytes()); + let mut mining_account = + AccountSharedData::new(10000000000000000, mining_acc_data.len(), &mplx_rewards::ID); + mining_account.set_data_from_slice(mining_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); + program_context + .mut_test_context() + .set_account(&mining_key, &mining_account); - (registrar_key, voter_key) + (registrar_key, voter_key, mining_key) } #[tokio::test] @@ -285,7 +298,8 @@ async fn test_prepare_tree_without_canopy() { let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) @@ -330,6 +344,7 @@ async fn test_prepare_tree_without_canopy() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, FEE_RECEIVER, ); @@ -397,7 +412,8 @@ async fn test_prepare_tree_with_canopy() { let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; let mut tree_tx_builder = tree.prepare_tree_tx( &program_context.test_context().payer, @@ -435,6 +451,7 @@ async fn test_prepare_tree_with_canopy() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, FEE_RECEIVER, ); @@ -474,7 +491,8 @@ async fn test_put_wrong_canopy() { let canopy_hashes = vec![[1; 32]; 32]; - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; let mut tree_tx_builder = tree.prepare_tree_tx( &program_context.test_context().payer, @@ -512,6 +530,7 @@ async fn test_put_wrong_canopy() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, FEE_RECEIVER, ); @@ -606,7 +625,8 @@ async fn test_put_wrong_fee_receiver() { let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) @@ -638,6 +658,7 @@ async fn test_put_wrong_fee_receiver() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, wrong_fee_receiver, ); @@ -689,7 +710,8 @@ async fn test_prepare_tree_with_collection() { let rightmost_proof = tree.proof_of_leaf((num_of_assets_to_mint - 1) as u32); let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) @@ -722,6 +744,7 @@ async fn test_prepare_tree_with_collection() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, FEE_RECEIVER, ); @@ -757,7 +780,8 @@ async fn test_prepare_tree_with_collection_wrong_authority() { let rightmost_leaf = tree.get_node(num_of_assets_to_mint - 1); - let (registrar_key, voter_key) = initialize_staking_accounts(&mut program_context).await; + let (registrar_key, voter_key, mining_key) = + initialize_staking_accounts(&mut program_context).await; program_context .fund_account(tree.creator_pubkey(), 10_000_000_000) @@ -790,6 +814,7 @@ async fn test_prepare_tree_with_collection_wrong_authority() { "fileHash".to_string(), registrar_key, voter_key, + mining_key, FEE_RECEIVER, ); diff --git a/programs/bubblegum/program/tests/utils/tree.rs b/programs/bubblegum/program/tests/utils/tree.rs index e6329c6..3b66c50 100644 --- a/programs/bubblegum/program/tests/utils/tree.rs +++ b/programs/bubblegum/program/tests/utils/tree.rs @@ -287,6 +287,7 @@ impl Tree FinalizeWithRootBuilder { let tree_authority = @@ -300,6 +301,7 @@ impl Tree Tree FinalizeWithRootAndCollectionBuilder { let tree_authority = @@ -350,6 +353,7 @@ impl Tree