diff --git a/programs/mpl-staking/src/cpi_instructions.rs b/programs/mpl-staking/src/cpi_instructions.rs index 77807a05..021eadbd 100644 --- a/programs/mpl-staking/src/cpi_instructions.rs +++ b/programs/mpl-staking/src/cpi_instructions.rs @@ -179,6 +179,50 @@ pub enum RewardsInstruction { /// [S] Reward pool account /// [W] Mining AllowTokenFlow { mining_owner: Pubkey }, + + /// Restricts batch minting until the specified time + /// + /// Accounts: + /// [RS] Deposit authority + /// [S] Reward pool account + /// [W] Mining + RestrictBatchMinting { + /// Time until batch minting is restricted + until_ts: u64, + /// Owner of the mining account + mining_owner: Pubkey, + }, +} + +pub fn restrict_batch_minting<'a>( + program_id: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + mining_owner: &Pubkey, + until_ts: u64, + signers_seeds: &[&[u8]], +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new_readonly(deposit_authority.key(), true), + AccountMeta::new_readonly(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + ]; + + let ix = Instruction::new_with_borsh( + program_id.key(), + &RewardsInstruction::RestrictBatchMinting { + until_ts, + mining_owner: *mining_owner, + }, + accounts, + ); + + invoke_signed( + &ix, + &[deposit_authority, reward_pool, mining, program_id], + &[signers_seeds], + ) } pub fn restrict_tokenflow<'a>( diff --git a/programs/mpl-staking/src/instructions/penalties/mod.rs b/programs/mpl-staking/src/instructions/penalties/mod.rs index 81531180..28788eee 100644 --- a/programs/mpl-staking/src/instructions/penalties/mod.rs +++ b/programs/mpl-staking/src/instructions/penalties/mod.rs @@ -3,9 +3,11 @@ use anchor_lang::{ prelude::{AccountLoader, Signer, ToAccountInfo, UncheckedAccount}, Accounts, }; +pub use restrict_batch_minting::*; pub use restrict_tokenflow::*; mod allow_tokenflow; +mod restrict_batch_minting; mod restrict_tokenflow; use mplx_staking_states::state::Registrar; diff --git a/programs/mpl-staking/src/instructions/penalties/restrict_batch_minting.rs b/programs/mpl-staking/src/instructions/penalties/restrict_batch_minting.rs new file mode 100644 index 00000000..a0c08422 --- /dev/null +++ b/programs/mpl-staking/src/instructions/penalties/restrict_batch_minting.rs @@ -0,0 +1,38 @@ +use super::Penalty; +use crate::cpi_instructions; +use anchor_lang::prelude::*; +use mplx_staking_states::error::MplStakingError; + +/// Restricts batch minting operation for the account until the specified timestamp. +pub fn restrict_batch_minting( + ctx: Context, + until_ts: u64, + mining_owner: Pubkey, +) -> Result<()> { + let registrar = ctx.accounts.registrar.load()?; + + require_keys_eq!( + registrar.realm_authority, + ctx.accounts.realm_authority.key(), + MplStakingError::InvalidRealmAuthority + ); + + let signers_seeds = &[ + ®istrar.realm.key().to_bytes(), + b"registrar".as_ref(), + ®istrar.realm_governing_token_mint.key().to_bytes(), + &[registrar.bump][..], + ]; + + cpi_instructions::restrict_batch_minting( + ctx.accounts.rewards_program.to_account_info(), + ctx.accounts.registrar.to_account_info(), + ctx.accounts.reward_pool.to_account_info(), + ctx.accounts.deposit_mining.to_account_info(), + &mining_owner, + until_ts, + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/mpl-staking/src/lib.rs b/programs/mpl-staking/src/lib.rs index 720fb5f5..af915bde 100644 --- a/programs/mpl-staking/src/lib.rs +++ b/programs/mpl-staking/src/lib.rs @@ -188,6 +188,14 @@ pub mod mpl_staking { pub fn allow_tokenflow(ctx: Context, mining_owner: Pubkey) -> Result<()> { instructions::allow_tokenflow(ctx, mining_owner) } + + pub fn restrict_batch_minting( + ctx: Context, + until_ts: u64, + mining_owner: Pubkey, + ) -> Result<()> { + instructions::restrict_batch_minting(ctx, until_ts, mining_owner) + } } #[derive(Accounts)] diff --git a/programs/mpl-staking/tests/fixtures/mplx_rewards.so b/programs/mpl-staking/tests/fixtures/mplx_rewards.so index b3fae7fd..221c93f2 100755 Binary files a/programs/mpl-staking/tests/fixtures/mplx_rewards.so and b/programs/mpl-staking/tests/fixtures/mplx_rewards.so differ diff --git a/programs/mpl-staking/tests/fixtures/spl-governance-3.1.0 b/programs/mpl-staking/tests/fixtures/spl-governance-3.1.0 deleted file mode 100644 index bb8a89ec..00000000 Binary files a/programs/mpl-staking/tests/fixtures/spl-governance-3.1.0 and /dev/null differ diff --git a/programs/mpl-staking/tests/program_test/addin.rs b/programs/mpl-staking/tests/program_test/addin.rs index 60f0f528..d7c4b4ce 100644 --- a/programs/mpl-staking/tests/program_test/addin.rs +++ b/programs/mpl-staking/tests/program_test/addin.rs @@ -761,6 +761,44 @@ impl AddinCookie { self.solana.context.borrow_mut().set_sysvar(&new_clock); } + #[allow(clippy::too_many_arguments)] + pub async fn restrict_batch_minting( + &self, + reward_pool: &Pubkey, + deposit_mining: &Pubkey, + registrar: &RegistrarCookie, + realm_authority: &Keypair, + mining_owner: &Pubkey, + until_ts: u64, + rewards_program: &Pubkey, + ) -> std::result::Result<(), BanksClientError> { + let data = InstructionData::data(&mpl_staking::instruction::RestrictBatchMinting { + until_ts, + mining_owner: *mining_owner, + }); + + let accounts = anchor_lang::ToAccountMetas::to_account_metas( + &mpl_staking::accounts::Penalty { + registrar: registrar.address, + realm_authority: realm_authority.pubkey(), + reward_pool: *reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, + }, + None, + ); + + let instructions = vec![Instruction { + program_id: self.program_id, + accounts, + data, + }]; + + self.solana + .process_transaction(&instructions, Some(&[realm_authority])) + .await + } + pub async fn restrict_tokenflow( &self, reward_pool: &Pubkey, diff --git a/programs/mpl-staking/tests/restrict_batch_minting.rs b/programs/mpl-staking/tests/restrict_batch_minting.rs new file mode 100644 index 00000000..31cd7945 --- /dev/null +++ b/programs/mpl-staking/tests/restrict_batch_minting.rs @@ -0,0 +1,168 @@ +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +mod program_test; + +#[tokio::test] +async fn restrict_batch_minting_poc() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + REALM_NAME, + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let deposit_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(deposit_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + let depositer_token_account = context.users[1].token_accounts[0]; + + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::ThreeMonths, + ) + .await?; + + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + deposit_authority, + depositer_token_account, + 0, + 10000, + ) + .await?; + + context + .addin + .stake( + ®istrar, + &voter, + voter.authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 10000, + ) + .await?; + + let distribution_ends_at = context + .solana + .context + .borrow_mut() + .banks_client + .get_sysvar::() + .await + .unwrap() + .unix_timestamp as u64 + + 86400; + + context + .addin + .restrict_batch_minting( + &rewards_pool, + &deposit_mining, + ®istrar, + &realm_authority, + &voter_authority.pubkey(), + distribution_ends_at, + &context.rewards.program_id, + ) + .await?; + + Ok(()) +}