diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 83a03c064..2aab63508 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -92,7 +92,7 @@ impl ScheduledCommitsProcessorImpl { fn preprocess_intent( &self, mut base_intent: ScheduledBaseIntent, - ) -> (ScheduledBaseIntentWrapper, Vec, Vec) { + ) -> (ScheduledBaseIntentWrapper, Vec) { let is_undelegate = base_intent.is_undelegate(); let Some(committed_accounts) = base_intent.get_committed_accounts_mut() else { @@ -100,43 +100,37 @@ impl ScheduledCommitsProcessorImpl { inner: base_intent, trigger_type: TriggerType::OnChain, }; - return (intent, vec![], vec![]); + return (intent, vec![]); }; - let mut excluded_pubkeys = vec![]; - let mut pubkeys_being_undelegated = vec![]; - // Retains only account that are valid to be committed (all delegated ones) - committed_accounts.retain_mut(|account| { - let pubkey = account.pubkey; - let acc = self.accounts_bank.get_account(&pubkey); - match acc { - Some(acc) => { - if acc.delegated() { - if is_undelegate { - pubkeys_being_undelegated.push(pubkey); - } - true - } else { - excluded_pubkeys.push(pubkey); - false - } + // dump undelegated pubkeys + let pubkeys_being_undelegated: Vec<_> = committed_accounts + .iter() + .inspect(|account| { + let pubkey = account.pubkey; + if self.accounts_bank.get_account(&pubkey).is_none() { + // This doesn't affect intent validity + // We assume that intent is correct at the moment of scheduling + // All the checks are performed by runtime & magic-program at the moment of scheduling + // This log could be a sign of eviction or a bug in implementation + info!("Account got evicted from AccountsDB after intent was scheduled!"); } - None => { - warn!( - "Account {} not found in AccountsDb, skipping from commit", - pubkey - ); - false + }) + .filter_map(|account| { + if is_undelegate { + Some(account.pubkey) + } else { + None } - } - }); + }) + .collect(); let intent = ScheduledBaseIntentWrapper { inner: base_intent, trigger_type: TriggerType::OnChain, }; - (intent, excluded_pubkeys, pubkeys_being_undelegated) + (intent, pubkeys_being_undelegated) } async fn process_undelegation_requests(&self, pubkeys: Vec) { @@ -341,7 +335,7 @@ impl ScheduledCommitsProcessorImpl { payer: intent_meta.payer, chain_signatures, included_pubkeys: intent_meta.included_pubkeys, - excluded_pubkeys: intent_meta.excluded_pubkeys, + excluded_pubkeys: vec![], requested_undelegation: intent_meta.requested_undelegation, } } @@ -350,14 +344,14 @@ impl ScheduledCommitsProcessorImpl { #[async_trait] impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { async fn process(&self) -> ScheduledCommitsProcessorResult<()> { - let scheduled_base_intent = + let scheduled_base_intents = self.transaction_scheduler.take_scheduled_actions(); - if scheduled_base_intent.is_empty() { + if scheduled_base_intents.is_empty() { return Ok(()); } - let intents = scheduled_base_intent + let intents = scheduled_base_intents .into_iter() .map(|intent| self.preprocess_intent(intent)); @@ -368,10 +362,10 @@ impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { let mut pubkeys_being_undelegated = HashSet::new(); let intents = intents - .map(|(intent, excluded_pubkeys, undelegated)| { + .map(|(intent, undelegated)| { intent_metas.insert( intent.id, - ScheduledBaseIntentMeta::new(&intent, excluded_pubkeys), + ScheduledBaseIntentMeta::new(&intent), ); pubkeys_being_undelegated.extend(undelegated); @@ -409,16 +403,12 @@ struct ScheduledBaseIntentMeta { blockhash: Hash, payer: Pubkey, included_pubkeys: Vec, - excluded_pubkeys: Vec, intent_sent_transaction: Transaction, requested_undelegation: bool, } impl ScheduledBaseIntentMeta { - fn new( - intent: &ScheduledBaseIntent, - excluded_pubkeys: Vec, - ) -> Self { + fn new(intent: &ScheduledBaseIntent) -> Self { Self { slot: intent.slot, blockhash: intent.blockhash, @@ -426,7 +416,6 @@ impl ScheduledBaseIntentMeta { included_pubkeys: intent .get_committed_pubkeys() .unwrap_or_default(), - excluded_pubkeys, intent_sent_transaction: intent.action_sent_transaction.clone(), requested_undelegation: intent.is_undelegate(), } diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 1fb050188..bdfeac26b 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -217,13 +217,14 @@ impl CommitAndUndelegate { ) -> Result<(), InstructionError> { account_indices.iter().copied().try_for_each(|idx| { let is_writable = get_writable_with_idx(context.transaction_context, idx as u16)?; - if is_writable { + let delegated = get_instruction_account_with_idx(context.transaction_context, idx as u16)?; + if is_writable && delegated.borrow().delegated() { Ok(()) } else { let pubkey = get_instruction_pubkey_with_idx(context.transaction_context, idx as u16)?; ic_msg!( context.invoke_context, - "ScheduleCommit ERR: account {} is required to be writable in order to be undelegated", + "ScheduleCommit ERR: account {} is required to be writable and delegated in order to be undelegated", pubkey ); Err(InstructionError::ReadonlyDataModified) diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs b/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs index 787f799de..9fdffd82d 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs @@ -14,7 +14,7 @@ use crate::{ }, schedule_transactions::check_magic_context_id, utils::{ - account_actions::mark_account_as_undelegating, + account_actions::mark_account_as_undelegated, accounts::{ get_instruction_account_with_idx, get_instruction_pubkey_with_idx, }, @@ -143,7 +143,7 @@ pub(crate) fn process_schedule_base_intent( // Once account is undelegated we need to make it immutable in our validator. for (pubkey, account_ref) in undelegated_accounts_ref.iter() { undelegated_pubkeys.push(pubkey.to_string()); - mark_account_as_undelegating(account_ref); + mark_account_as_undelegated(account_ref); } } if !undelegated_pubkeys.is_empty() { diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index a97f90a05..0ab615b92 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -16,7 +16,7 @@ use crate::{ }, schedule_transactions, utils::{ - account_actions::mark_account_as_undelegating, + account_actions::mark_account_as_undelegated, accounts::{ get_instruction_account_with_idx, get_instruction_pubkey_with_idx, get_writable_with_idx, @@ -133,14 +133,15 @@ pub(crate) fn process_schedule_commit( get_instruction_account_with_idx(transaction_context, idx as u16)?; { if opts.request_undelegation { - // Since we need to modify the account during undelegation, we expect it to be writable - // We rely on invariant "writable means delegated" + // Check if account is writable and also undelegated + // SVM doesn't check delegated, so we need to do extra checks here + // Otherwise account could be undelegated twice let acc_writable = get_writable_with_idx(transaction_context, idx as u16)?; - if !acc_writable { + if !acc_writable || !acc.borrow().delegated() { ic_msg!( invoke_context, - "ScheduleCommit ERR: account {} is required to be writable in order to be undelegated", + "ScheduleCommit ERR: account {} is required to be writable and delegated in order to be undelegated", acc_pubkey ); return Err(InstructionError::ReadonlyDataModified); @@ -201,7 +202,7 @@ pub(crate) fn process_schedule_commit( // // We also set the undelegating flag on the account in order to detect // undelegations for which we miss updates - mark_account_as_undelegating(acc); + mark_account_as_undelegated(acc); ic_msg!( invoke_context, "ScheduleCommit: Marking account {} as undelegating", diff --git a/programs/magicblock/src/utils/account_actions.rs b/programs/magicblock/src/utils/account_actions.rs index fff842bc1..2db075d13 100644 --- a/programs/magicblock/src/utils/account_actions.rs +++ b/programs/magicblock/src/utils/account_actions.rs @@ -14,8 +14,10 @@ pub(crate) fn set_account_owner( acc.borrow_mut().set_owner(pubkey); } -/// Sets proper values on account during undelegation -pub(crate) fn mark_account_as_undelegating(acc: &RefCell) { +/// Sets proper account values during undelegation +pub(crate) fn mark_account_as_undelegated(acc: &RefCell) { set_account_owner(acc, DELEGATION_PROGRAM_ID); - acc.borrow_mut().set_undelegating(true); + let mut acc = acc.borrow_mut(); + acc.set_undelegating(true); + acc.set_delegated(false); } diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 2ed59aef7..710c49968 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -719,7 +719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" dependencies = [ "borsh-derive 0.10.4", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -2268,10 +2268,10 @@ dependencies = [ [[package]] name = "guinea" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "serde", "solana-program", ] @@ -3454,7 +3454,7 @@ dependencies = [ [[package]] name = "magicblock-account-cloner" -version = "0.3.0" +version = "0.3.1" dependencies = [ "async-trait", "bincode", @@ -3465,7 +3465,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "magicblock-program", "magicblock-rpc-client", "solana-sdk", @@ -3475,7 +3475,7 @@ dependencies = [ [[package]] name = "magicblock-accounts" -version = "0.3.0" +version = "0.3.1" dependencies = [ "async-trait", "futures-util", @@ -3488,7 +3488,7 @@ dependencies = [ "magicblock-core", "magicblock-delegation-program", "magicblock-ledger", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3503,7 +3503,7 @@ dependencies = [ [[package]] name = "magicblock-accounts-db" -version = "0.3.0" +version = "0.3.1" dependencies = [ "lmdb-rkv", "log", @@ -3520,7 +3520,7 @@ dependencies = [ [[package]] name = "magicblock-aperture" -version = "0.3.0" +version = "0.3.1" dependencies = [ "arc-swap", "base64 0.21.7", @@ -3569,7 +3569,7 @@ dependencies = [ [[package]] name = "magicblock-api" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "bincode", @@ -3590,7 +3590,7 @@ dependencies = [ "magicblock-core", "magicblock-delegation-program", "magicblock-ledger", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3613,7 +3613,7 @@ dependencies = [ [[package]] name = "magicblock-chainlink" -version = "0.3.0" +version = "0.3.1" dependencies = [ "arc-swap", "async-trait", @@ -3624,7 +3624,7 @@ dependencies = [ "lru 0.16.0", "magicblock-core", "magicblock-delegation-program", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "magicblock-metrics", "serde_json", "solana-account", @@ -3648,7 +3648,7 @@ dependencies = [ [[package]] name = "magicblock-committor-program" -version = "0.3.0" +version = "0.3.1" dependencies = [ "borsh 1.5.7", "borsh-derive 1.5.7", @@ -3662,7 +3662,7 @@ dependencies = [ [[package]] name = "magicblock-committor-service" -version = "0.3.0" +version = "0.3.1" dependencies = [ "async-trait", "base64 0.21.7", @@ -3694,7 +3694,7 @@ dependencies = [ [[package]] name = "magicblock-config" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bs58", "clap 4.5.41", @@ -3713,11 +3713,11 @@ dependencies = [ [[package]] name = "magicblock-config-helpers" -version = "0.3.0" +version = "0.3.1" [[package]] name = "magicblock-config-macro" -version = "0.3.0" +version = "0.3.1" dependencies = [ "clap 4.5.41", "convert_case 0.8.0", @@ -3729,11 +3729,11 @@ dependencies = [ [[package]] name = "magicblock-core" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "flume", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "serde", "solana-account", "solana-account-decoder", @@ -3770,7 +3770,7 @@ dependencies = [ [[package]] name = "magicblock-ledger" -version = "0.3.0" +version = "0.3.1" dependencies = [ "arc-swap", "bincode", @@ -3790,7 +3790,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-sdk", - "solana-storage-proto 0.3.0", + "solana-storage-proto 0.3.1", "solana-svm 2.2.1 (git+https://github.com/magicblock-labs/magicblock-svm.git?rev=e480fa2)", "solana-timings", "solana-transaction-status", @@ -3812,7 +3812,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "serde", @@ -3821,7 +3821,7 @@ dependencies = [ [[package]] name = "magicblock-metrics" -version = "0.3.0" +version = "0.3.1" dependencies = [ "http-body-util", "hyper 1.6.0", @@ -3835,7 +3835,7 @@ dependencies = [ [[package]] name = "magicblock-processor" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "log", @@ -3869,12 +3869,12 @@ dependencies = [ [[package]] name = "magicblock-program" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "lazy_static", "magicblock-core", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "magicblock-metrics", "num-derive", "num-traits", @@ -3887,7 +3887,7 @@ dependencies = [ [[package]] name = "magicblock-rpc-client" -version = "0.3.0" +version = "0.3.1" dependencies = [ "log", "solana-rpc-client", @@ -3901,7 +3901,7 @@ dependencies = [ [[package]] name = "magicblock-table-mania" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ed25519-dalek", "log", @@ -3919,7 +3919,7 @@ dependencies = [ [[package]] name = "magicblock-task-scheduler" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "chrono", @@ -3944,7 +3944,7 @@ dependencies = [ [[package]] name = "magicblock-validator-admin" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "log", @@ -3962,7 +3962,7 @@ dependencies = [ [[package]] name = "magicblock-version" -version = "0.3.0" +version = "0.3.1" dependencies = [ "git-version", "rustc_version", @@ -4851,7 +4851,7 @@ dependencies = [ "bincode", "borsh 1.5.7", "ephemeral-rollups-sdk", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "serde", "solana-program", ] @@ -4875,6 +4875,7 @@ dependencies = [ "borsh 1.5.7", "ephemeral-rollups-sdk", "magicblock-delegation-program", + "magicblock-magic-program-api 0.3.1", "solana-program", ] @@ -5831,7 +5832,7 @@ dependencies = [ "integration-test-tools", "log", "magicblock-core", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "program-schedulecommit", "schedulecommit-client", "solana-program", @@ -5847,7 +5848,7 @@ version = "0.0.0" dependencies = [ "integration-test-tools", "magicblock-core", - "magicblock-magic-program-api 0.3.0", + "magicblock-magic-program-api 0.3.1", "program-schedulecommit", "program-schedulecommit-security", "schedulecommit-client", @@ -8988,7 +8989,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bincode", "bs58", @@ -10489,7 +10490,7 @@ dependencies = [ [[package]] name = "test-kit" -version = "0.3.0" +version = "0.3.1" dependencies = [ "env_logger 0.11.8", "guinea", diff --git a/test-integration/programs/schedulecommit/Cargo.toml b/test-integration/programs/schedulecommit/Cargo.toml index 59c45c378..9067d6327 100644 --- a/test-integration/programs/schedulecommit/Cargo.toml +++ b/test-integration/programs/schedulecommit/Cargo.toml @@ -8,6 +8,7 @@ borsh = { workspace = true } ephemeral-rollups-sdk = { workspace = true } solana-program = { workspace = true } magicblock-delegation-program = { workspace = true } +magicblock_magic_program_api = { workspace = true } [lib] crate-type = ["cdylib", "lib"] diff --git a/test-integration/programs/schedulecommit/src/api.rs b/test-integration/programs/schedulecommit/src/api.rs index 57475abed..4af491322 100644 --- a/test-integration/programs/schedulecommit/src/api.rs +++ b/test-integration/programs/schedulecommit/src/api.rs @@ -220,6 +220,32 @@ pub fn schedule_commit_and_undelegate_cpi_with_mod_after_instruction( ) } +pub fn schedule_commit_and_undelegate_cpi_twice( + payer: Pubkey, + magic_program_id: Pubkey, + magic_context_id: Pubkey, + players: &[Pubkey], + committees: &[Pubkey], +) -> Instruction { + let program_id = crate::id(); + let mut account_metas = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(magic_context_id, false), + AccountMeta::new_readonly(magic_program_id, false), + ]; + for committee in committees { + account_metas.push(AccountMeta::new(*committee, false)); + } + + Instruction::new_with_borsh( + program_id, + &ScheduleCommitInstruction::ScheduleCommitAndUndelegateCpiTwice( + players.to_vec(), + ), + account_metas, + ) +} + pub fn increase_count_instruction(committee: Pubkey) -> Instruction { let program_id = crate::id(); let account_metas = vec![AccountMeta::new(committee, false)]; diff --git a/test-integration/programs/schedulecommit/src/lib.rs b/test-integration/programs/schedulecommit/src/lib.rs index a90da8c26..411ca3509 100644 --- a/test-integration/programs/schedulecommit/src/lib.rs +++ b/test-integration/programs/schedulecommit/src/lib.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use borsh::{BorshDeserialize, BorshSerialize}; use ephemeral_rollups_sdk::{ consts::EXTERNAL_UNDELEGATE_DISCRIMINATOR, @@ -6,11 +8,14 @@ use ephemeral_rollups_sdk::{ }, ephem::{commit_accounts, commit_and_undelegate_accounts}, }; +use magicblock_magic_program_api::instruction::MagicBlockInstruction; use solana_program::{ account_info::{next_account_info, AccountInfo}, declare_id, entrypoint::{self, ProgramResult}, + instruction::{AccountMeta, Instruction}, msg, + program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, }; @@ -22,6 +27,7 @@ use crate::{ AllocateAndAssignAccountArgs, }, }; + pub mod api; pub mod magicblock_program; mod utils; @@ -89,6 +95,18 @@ pub enum ScheduleCommitInstruction { /// - **4.** `[]` System program ScheduleCommitAndUndelegateCpiModAfter(Vec), + /// Same instruction input like [ScheduleCommitInstruction::ScheduleCommitCpi]. + /// Behavior differs that it will commit and undelegate account twice + /// requested commit + undelegation. + /// + /// # Account references: + /// - **0.** `[WRITE]` Delegated account + /// - **1.** `[]` Delegation program + /// - **2.** `[WRITE]` Buffer account + /// - **3.** `[WRITE]` Payer + /// - **4.** `[]` System program + ScheduleCommitAndUndelegateCpiTwice(Vec), + /// Increases the count of a PDA of this program by one. /// This instruction can only run on the ephemeral after the account was /// delegated or on chain while it is undelegated. @@ -146,6 +164,11 @@ pub fn process_instruction<'a>( accounts, &players, ) } + ScheduleCommitAndUndelegateCpiTwice(players) => { + process_schedulecommit_and_undelegation_cpi_twice( + accounts, &players, + ) + } IncreaseCount => process_increase_count(accounts), } } @@ -432,6 +455,111 @@ fn process_schedulecommit_and_undelegation_cpi_with_mod_after( Ok(()) } +pub fn create_schedule_commit_ix<'a, 'info>( + payer: &'a AccountInfo<'info>, + account_infos: &[&'a AccountInfo<'info>], + magic_context: &'a AccountInfo<'info>, + magic_program: &'a AccountInfo<'info>, +) -> Instruction { + let mut account_metas = vec![ + AccountMeta { + pubkey: *payer.key, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: *magic_context.key, + is_signer: false, + is_writable: true, + }, + ]; + account_metas.extend(account_infos.iter().map(|x| AccountMeta { + pubkey: *x.key, + is_signer: true, + is_writable: x.is_writable, + })); + Instruction::new_with_bincode( + *magic_program.key, + &MagicBlockInstruction::ScheduleCommitAndUndelegate, + account_metas, + ) +} + +// ----------------- +// process_schedulecommit_and_undelegation_cpi_with_mod_after +// ----------------- +fn process_schedulecommit_and_undelegation_cpi_twice( + accounts: &[AccountInfo], + player_pubkeys: &[Pubkey], +) -> Result<(), ProgramError> { + msg!("Processing process_schedulecommit_and_undelegation_cpi_twice instruction"); + + let accounts_iter = &mut accounts.iter(); + let payer = next_account_info(accounts_iter)?; + let magic_context = next_account_info(accounts_iter)?; + let magic_program = next_account_info(accounts_iter)?; + + let mut remaining = Vec::new(); + for info in accounts_iter.by_ref() { + remaining.push(info.clone()); + } + + if remaining.len() != player_pubkeys.len() { + msg!( + "ERROR: player_pubkeys.len() != committes.len() | {} != {}", + player_pubkeys.len(), + remaining.len() + ); + return Err(ProgramError::InvalidArgument); + } + + // Request the PDA accounts to be committed and undelegated + commit_and_undelegate_accounts( + payer, + remaining.iter().collect::>(), + magic_context, + magic_program, + )?; + + // All accounts that will be passed to the CPI + let mut account_infos = Vec::with_capacity(2 + remaining.len()); + account_infos.push(payer.clone()); + account_infos.push(magic_context.clone()); + account_infos.extend(remaining.iter().cloned()); + + // Undelegate accounts 1 time + let ix = create_schedule_commit_ix( + payer, + remaining.iter().collect::>().deref(), + magic_context, + magic_program, + ); + + let mut all_seeds: Vec>> = + Vec::with_capacity(player_pubkeys.len()); + for pk in player_pubkeys { + let (_pda, bump) = pda_and_bump(pk); + let bump_arr = [bump]; + let tmp_seeds = pda_seeds_with_bump(pk, &bump_arr); + let owned_seeds: Vec> = + tmp_seeds.iter().map(|s| s.to_vec()).collect(); + + all_seeds.push(owned_seeds); + } + + let signer_seeds_vec: Vec> = all_seeds + .iter() + .map(|seed_vec| seed_vec.iter().map(|s| s.as_slice()).collect()) + .collect(); + let signer_seed_slices: Vec<&[&[u8]]> = + signer_seeds_vec.iter().map(|v| v.as_slice()).collect(); + + // Attempt undelegation with same accounts 2 time + invoke_signed(&ix, &account_infos, &signer_seed_slices)?; + + Ok(()) +} + // ----------------- // Undelegate Request // ----------------- diff --git a/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs b/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs index 120650a70..9b4e30b3f 100644 --- a/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs +++ b/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs @@ -6,6 +6,7 @@ use integration_test_tools::{ use log::*; use program_schedulecommit::api::{ increase_count_instruction, schedule_commit_and_undelegate_cpi_instruction, + schedule_commit_and_undelegate_cpi_twice, schedule_commit_and_undelegate_cpi_with_mod_after_instruction, }; use schedulecommit_client::{ @@ -161,6 +162,54 @@ fn commit_and_undelegate_two_accounts( (ctx, *sig, tx_res) } +fn commit_and_undelegate_two_accounts_twice() -> ( + ScheduleCommitTestContext, + Signature, + Result, +) { + let ctx = get_context_with_delegated_committees(2); + let ScheduleCommitTestContextFields { + payer_ephem: payer, + committees, + commitment, + ephem_client, + .. + } = ctx.fields(); + + let ix = schedule_commit_and_undelegate_cpi_twice( + payer.pubkey(), + magicblock_magic_program_api::id(), + magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY, + &committees + .iter() + .map(|(player, _)| player.pubkey()) + .collect::>(), + &committees.iter().map(|(_, pda)| *pda).collect::>(), + ); + + let ephem_blockhash = ephem_client.get_latest_blockhash().unwrap(); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + ephem_blockhash, + ); + + let sig = tx.get_signature(); + let tx_res = ephem_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + *commitment, + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ); + + debug!("Commit and Undelegate Transaction result: '{:?}'", tx_res); + (ctx, *sig, tx_res) +} + #[test] fn test_committing_and_undelegating_one_account() { run_test!({ @@ -518,3 +567,34 @@ fn test_committing_and_undelegating_two_accounts_modifying_them_after() { debug!("✅ Verified that not commit was scheduled since tx failed"); }); } + +#[test] +fn test_committing_and_undelegating_two_accounts_twice() { + run_test!({ + let (ctx, sig, tx_res) = commit_and_undelegate_two_accounts_twice(); + debug!( + "✅ Committed and undelegated accounts and tried to mod after {} '{:?}'", + sig, tx_res + ); + + // 1. Show we cannot use them in the ephemeral anymore + ctx.assert_ephemeral_transaction_error( + sig, + &tx_res, + "is required to be writable and delegated in order to be undelegated", + ); + debug!("✅ Verified we could not increase counts in same tx that triggered undelegation in ephem"); + + // 2. Retrieve the signature of the scheduled commit sent + let logs = ctx.fetch_ephemeral_logs(sig).unwrap(); + let scheduled_commmit_sent_sig = + extract_scheduled_commit_sent_signature_from_logs(&logs).unwrap(); + + // 3. Assert that the commit was not scheduled -> the transaction is not confirmed + debug!("Verifying that commit was not scheduled: {scheduled_commmit_sent_sig}"); + assert!(!ctx + .confirm_transaction_ephem(&scheduled_commmit_sent_sig, None) + .unwrap()); + debug!("✅ Verified that not commit was scheduled since tx failed"); + }); +}