diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bcdc06393116b..35bf80872a6e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Release channels have their own copy of this changelog: * SDK: * `cargo-build-sbf` and `cargo-build-bpf` have been deprecated for two years and have now been definitely removed. Use `cargo-build-sbf` and `cargo-test-sbf` instead. + * Stake: + * removed the unreleased `redelegate` instruction processor and CLI commands (#2213) * Changes * SDK: removed the `respan` macro. This was marked as "internal use only" and was no longer used internally. diff --git a/cli/src/cli.rs b/cli/src/cli.rs index f051891fa29901..643782e418b161 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -229,7 +229,6 @@ pub enum CliCommand { nonce_authority: SignerIndex, memo: Option, fee_payer: SignerIndex, - redelegation_stake_account: Option, compute_unit_price: Option, }, SplitStake { @@ -718,8 +717,10 @@ pub fn parse_command( ("delegate-stake", Some(matches)) => { parse_stake_delegate_stake(matches, default_signer, wallet_manager) } - ("redelegate-stake", Some(matches)) => { - parse_stake_delegate_stake(matches, default_signer, wallet_manager) + ("redelegate-stake", _) => { + Err(CliError::CommandNotRecognized( + "`redelegate-stake` no longer exists and will be completely removed in a future release".to_string(), + )) } ("withdraw-stake", Some(matches)) => { parse_stake_withdraw_stake(matches, default_signer, wallet_manager) @@ -1242,7 +1243,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { nonce_authority, memo, fee_payer, - redelegation_stake_account, compute_unit_price, } => process_delegate_stake( &rpc_client, @@ -1258,7 +1258,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *nonce_authority, memo.as_ref(), *fee_payer, - *redelegation_stake_account, *compute_unit_price, ), CliCommand::SplitStake { diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 3c94ffb9507903..0e32a6216935a0 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -11,7 +11,7 @@ use { nonce::check_nonce_account, spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, }, - clap::{value_t, App, Arg, ArgGroup, ArgMatches, SubCommand}, + clap::{value_t, App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand}, solana_clap_utils::{ compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG}, fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, @@ -328,49 +328,13 @@ impl StakeSubCommands for App<'_, '_> { ) .subcommand( SubCommand::with_name("redelegate-stake") - .about("Redelegate active stake to another vote account") + .setting(AppSettings::Hidden) .arg( - Arg::with_name("force") - .long("force") - .takes_value(false) - .hidden(hidden_unless_forced()) // Don't document this argument to discourage its use - .help("Override vote account sanity checks (use carefully!)"), - ) - .arg(pubkey!( - Arg::with_name("stake_account_pubkey") - .index(1) - .value_name("STAKE_ACCOUNT_ADDRESS") - .required(true), - "Existing delegated stake account that has been fully activated. On success \ - this stake account will be scheduled for deactivation and the rent-exempt \ - balance may be withdrawn once fully deactivated." - )) - .arg(pubkey!( - Arg::with_name("vote_account_pubkey") - .index(2) - .value_name("REDELEGATED_VOTE_ACCOUNT_ADDRESS") - .required(true), - "Vote account to which the stake will be redelegated." - )) - .arg( - Arg::with_name("redelegation_stake_account") - .index(3) - .value_name("REDELEGATION_STAKE_ACCOUNT") - .takes_value(true) - .required(true) - .validator(is_valid_signer) - .help( - "Stake account to create for the redelegation. On success this stake \ - account will be created and scheduled for activation with all the \ - stake in the existing stake account, exclusive of the rent-exempt \ - balance retained in the existing account", - ), - ) - .arg(stake_authority_arg()) - .offline_args() - .nonce_args(false) - .arg(fee_payer_arg()) - .arg(memo_arg()), + // Consume all positional arguments + Arg::with_name("arg") + .multiple(true) + .hidden(hidden_unless_forced()), + ), ) .subcommand( SubCommand::with_name("stake-authorize") @@ -911,8 +875,6 @@ pub fn parse_stake_delegate_stake( pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap(); let vote_account_pubkey = pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); - let (redelegation_stake_account, redelegation_stake_account_pubkey) = - signer_of(matches, "redelegation_stake_account", wallet_manager)?; let force = matches.is_present("force"); let sign_only = matches.is_present(SIGN_ONLY_ARG.name); let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); @@ -929,9 +891,6 @@ pub fn parse_stake_delegate_stake( if nonce_account.is_some() { bulk_signers.push(nonce_authority); } - if redelegation_stake_account.is_some() { - bulk_signers.push(redelegation_stake_account); - } let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name); @@ -949,8 +908,6 @@ pub fn parse_stake_delegate_stake( nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), memo, fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), - redelegation_stake_account: redelegation_stake_account_pubkey - .and_then(|_| signer_info.index_of(redelegation_stake_account_pubkey)), compute_unit_price, }, signers: signer_info.signers, @@ -2681,30 +2638,12 @@ pub fn process_delegate_stake( nonce_authority: SignerIndex, memo: Option<&String>, fee_payer: SignerIndex, - redelegation_stake_account: Option, compute_unit_price: Option, ) -> ProcessResult { check_unique_pubkeys( (&config.signers[0].pubkey(), "cli keypair".to_string()), (stake_account_pubkey, "stake_account_pubkey".to_string()), )?; - let redelegation_stake_account = redelegation_stake_account.map(|index| config.signers[index]); - if let Some(redelegation_stake_account) = &redelegation_stake_account { - check_unique_pubkeys( - (stake_account_pubkey, "stake_account_pubkey".to_string()), - ( - &redelegation_stake_account.pubkey(), - "redelegation_stake_account".to_string(), - ), - )?; - check_unique_pubkeys( - (&config.signers[0].pubkey(), "cli keypair".to_string()), - ( - &redelegation_stake_account.pubkey(), - "redelegation_stake_account".to_string(), - ), - )?; - } let stake_authority = config.signers[stake_authority]; if !sign_only { @@ -2758,20 +2697,11 @@ pub fn process_delegate_stake( let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; - let ixs = if let Some(redelegation_stake_account) = &redelegation_stake_account { - stake_instruction::redelegate( - stake_account_pubkey, - &stake_authority.pubkey(), - vote_account_pubkey, - &redelegation_stake_account.pubkey(), - ) - } else { - vec![stake_instruction::delegate_stake( - stake_account_pubkey, - &stake_authority.pubkey(), - vote_account_pubkey, - )] - } + let ixs = vec![stake_instruction::delegate_stake( + stake_account_pubkey, + &stake_authority.pubkey(), + vote_account_pubkey, + )] .with_memo(memo) .with_compute_unit_config(&ComputeUnitConfig { compute_unit_price, @@ -4173,7 +4103,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())], @@ -4206,7 +4135,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![ @@ -4239,7 +4167,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())], @@ -4275,7 +4202,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())], @@ -4306,7 +4232,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())], @@ -4347,7 +4272,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 1, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![ @@ -4397,7 +4321,6 @@ mod tests { nonce_authority: 2, memo: None, fee_payer: 1, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![ @@ -4435,7 +4358,6 @@ mod tests { nonce_authority: 0, memo: None, fee_payer: 1, - redelegation_stake_account: None, compute_unit_price: None, }, signers: vec![ @@ -4445,49 +4367,6 @@ mod tests { } ); - // Test RedelegateStake Subcommand (minimal test due to the significant implementation - // overlap with DelegateStake) - let (redelegation_stake_account_keypair_file, mut redelegation_stake_account_tmp_file) = - make_tmp_file(); - let redelegation_stake_account_keypair = Keypair::new(); - write_keypair( - &redelegation_stake_account_keypair, - redelegation_stake_account_tmp_file.as_file_mut(), - ) - .unwrap(); - - let test_redelegate_stake = test_commands.clone().get_matches_from(vec![ - "test", - "redelegate-stake", - &stake_account_string, - &vote_account_string, - &redelegation_stake_account_keypair_file, - ]); - assert_eq!( - parse_command(&test_redelegate_stake, &default_signer, &mut None).unwrap(), - CliCommandInfo { - command: CliCommand::DelegateStake { - stake_account_pubkey, - vote_account_pubkey, - stake_authority: 0, - force: false, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::default(), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - redelegation_stake_account: Some(1), - compute_unit_price: None, - }, - signers: vec![ - Box::new(read_keypair_file(&default_keypair_file).unwrap()), - Box::new(read_keypair_file(&redelegation_stake_account_keypair_file).unwrap()) - ], - } - ); - // Test WithdrawStake Subcommand let test_withdraw_stake = test_commands.clone().get_matches_from(vec![ "test", diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 3aeeb47d9c0b8a..bcc5f6bb7b39ef 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -11,9 +11,7 @@ use { solana_cli_output::{parse_sign_only_reply_string, OutputFormat}, solana_faucet::faucet::run_local_faucet, solana_rpc_client::rpc_client::RpcClient, - solana_rpc_client_api::{ - request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, response::StakeActivationState, - }, + solana_rpc_client_api::request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery}, solana_sdk::{ account_utils::StateMut, @@ -28,276 +26,13 @@ use { stake::{ self, instruction::LockupArgs, - state::{Delegation, Lockup, StakeActivationStatus, StakeAuthorize, StakeStateV2}, + state::{Lockup, StakeAuthorize, StakeStateV2}, }, - sysvar::stake_history::{self, StakeHistory}, }, solana_streamer::socket::SocketAddrSpace, solana_test_validator::{TestValidator, TestValidatorGenesis}, }; -#[test] -fn test_stake_redelegation() { - let mint_keypair = Keypair::new(); - let mint_pubkey = mint_keypair.pubkey(); - let authorized_withdrawer = Keypair::new().pubkey(); - let faucet_addr = run_local_faucet(mint_keypair, None); - - let slots_per_epoch = 32; - let test_validator = TestValidatorGenesis::default() - .fee_rate_governor(FeeRateGovernor::new(0, 0)) - .rent(Rent { - lamports_per_byte_year: 1, - exemption_threshold: 1.0, - ..Rent::default() - }) - .epoch_schedule(EpochSchedule::custom( - slots_per_epoch, - slots_per_epoch, - /* enable_warmup_epochs = */ false, - )) - .faucet_addr(Some(faucet_addr)) - .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) - .expect("validator start failed"); - - let rpc_client = - RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); - let default_signer = Keypair::new(); - - let mut config = CliConfig::recent_for_tests(); - config.json_rpc_url = test_validator.rpc_url(); - config.signers = vec![&default_signer]; - - request_and_confirm_airdrop( - &rpc_client, - &config, - &config.signers[0].pubkey(), - 100_000_000_000, - ) - .unwrap(); - - // Create vote account - let vote_keypair = Keypair::new(); - config.signers = vec![&default_signer, &vote_keypair]; - config.command = CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 0, - authorized_voter: None, - authorized_withdrawer, - commission: 0, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }; - process_command(&config).unwrap(); - - // Create second vote account - let vote2_keypair = Keypair::new(); - config.signers = vec![&default_signer, &vote2_keypair]; - config.command = CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 0, - authorized_voter: None, - authorized_withdrawer, - commission: 0, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }; - process_command(&config).unwrap(); - - // Create stake account - let stake_keypair = Keypair::new(); - config.signers = vec![&default_signer, &stake_keypair]; - config.command = CliCommand::CreateStakeAccount { - stake_account: 1, - seed: None, - staker: None, - withdrawer: None, - withdrawer_signer: None, - lockup: Lockup::default(), - amount: SpendAmount::Some(50_000_000_000), - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - from: 0, - compute_unit_price: None, - }; - process_command(&config).unwrap(); - - // Delegate stake to `vote_keypair` - config.signers = vec![&default_signer]; - config.command = CliCommand::DelegateStake { - stake_account_pubkey: stake_keypair.pubkey(), - vote_account_pubkey: vote_keypair.pubkey(), - stake_authority: 0, - force: true, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::default(), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - redelegation_stake_account: None, - compute_unit_price: None, - }; - process_command(&config).unwrap(); - - // wait for new epoch plus one additional slot for rewards payout - wait_for_next_epoch_plus_n_slots(&rpc_client, 1); - - let check_activation_status = |delegation: &Delegation, - expected_state: StakeActivationState, - expected_active_stake: u64| { - let stake_history_account = rpc_client.get_account(&stake_history::id()).unwrap(); - let stake_history: StakeHistory = - solana_sdk::account::from_account(&stake_history_account).unwrap(); - let current_epoch = rpc_client.get_epoch_info().unwrap().epoch; - let StakeActivationStatus { - effective, - activating, - deactivating, - } = delegation.stake_activating_and_deactivating(current_epoch, &stake_history, None); - let stake_activation_state = if deactivating > 0 { - StakeActivationState::Deactivating - } else if activating > 0 { - StakeActivationState::Activating - } else if effective > 0 { - StakeActivationState::Active - } else { - StakeActivationState::Inactive - }; - assert_eq!(stake_activation_state, expected_state); - assert_eq!(effective, expected_active_stake); - }; - - // `stake_keypair` should now be delegated to `vote_keypair` and fully activated - let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); - let stake_state: StakeStateV2 = stake_account.state().unwrap(); - - let rent_exempt_reserve = match stake_state { - StakeStateV2::Stake(meta, stake, _) => { - assert_eq!(stake.delegation.voter_pubkey, vote_keypair.pubkey()); - check_activation_status( - &stake.delegation, - StakeActivationState::Active, - 50_000_000_000 - meta.rent_exempt_reserve, - ); - meta.rent_exempt_reserve - } - _ => panic!("Unexpected stake state!"), - }; - - check_balance!(50_000_000_000, &rpc_client, &stake_keypair.pubkey()); - - let stake2_keypair = Keypair::new(); - - // Add an extra `rent_exempt_reserve` amount into `stake2_keypair` before redelegation to - // account for the `rent_exempt_reserve` balance that'll be pealed off the stake during the - // redelegation process - request_and_confirm_airdrop( - &rpc_client, - &config, - &stake2_keypair.pubkey(), - rent_exempt_reserve, - ) - .unwrap(); - - // wait for a new epoch to ensure the `Redelegate` happens as soon as possible (i.e. till the - // last reward distribution block in the new epoch) to reduce the risk of a race condition - // when checking the stake account correctly enters the deactivating state for the - // remainder of the current epoch. - wait_for_next_epoch_plus_n_slots(&rpc_client, 1); - - // Redelegate to `vote2_keypair` via `stake2_keypair - config.signers = vec![&default_signer, &stake2_keypair]; - config.command = CliCommand::DelegateStake { - stake_account_pubkey: stake_keypair.pubkey(), - vote_account_pubkey: vote2_keypair.pubkey(), - stake_authority: 0, - force: true, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::default(), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - redelegation_stake_account: Some(1), - compute_unit_price: None, - }; - process_command(&config).unwrap(); - - // `stake_keypair` should now be deactivating - let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); - let stake_state: StakeStateV2 = stake_account.state().unwrap(); - let StakeStateV2::Stake(_, stake, _) = stake_state else { - panic!() - }; - check_activation_status( - &stake.delegation, - StakeActivationState::Deactivating, - 50_000_000_000 - rent_exempt_reserve, - ); - - // `stake_keypair2` should now be activating - let stake_account = rpc_client.get_account(&stake2_keypair.pubkey()).unwrap(); - let stake_state: StakeStateV2 = stake_account.state().unwrap(); - let StakeStateV2::Stake(_, stake, _) = stake_state else { - panic!() - }; - check_activation_status(&stake.delegation, StakeActivationState::Activating, 0); - - // check that all the stake, save `rent_exempt_reserve`, have been moved from `stake_keypair` - // to `stake2_keypair` - check_balance!(rent_exempt_reserve, &rpc_client, &stake_keypair.pubkey()); - check_balance!(50_000_000_000, &rpc_client, &stake2_keypair.pubkey()); - - // wait for new epoch plus reward blocks - wait_for_next_epoch_plus_n_slots(&rpc_client, 1); - - // `stake_keypair` should now be deactivated - let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); - let stake_state: StakeStateV2 = stake_account.state().unwrap(); - let StakeStateV2::Stake(_, stake, _) = stake_state else { - panic!() - }; - check_activation_status(&stake.delegation, StakeActivationState::Inactive, 0); - - // `stake2_keypair` should now be delegated to `vote2_keypair` and fully activated - let stake2_account = rpc_client.get_account(&stake2_keypair.pubkey()).unwrap(); - let stake2_state: StakeStateV2 = stake2_account.state().unwrap(); - - match stake2_state { - StakeStateV2::Stake(meta, stake, _) => { - assert_eq!(stake.delegation.voter_pubkey, vote2_keypair.pubkey()); - check_activation_status( - &stake.delegation, - StakeActivationState::Active, - 50_000_000_000 - meta.rent_exempt_reserve, - ); - } - _ => panic!("Unexpected stake2 state!"), - }; -} - #[test] fn test_stake_delegation_force() { let mint_keypair = Keypair::new(); @@ -396,7 +131,6 @@ fn test_stake_delegation_force() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config).unwrap(); @@ -440,7 +174,6 @@ fn test_stake_delegation_force() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config).unwrap_err(); @@ -458,7 +191,6 @@ fn test_stake_delegation_force() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config).unwrap(); @@ -537,7 +269,6 @@ fn test_seed_stake_delegation_and_deactivation() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config_validator).unwrap(); @@ -629,7 +360,6 @@ fn test_stake_delegation_and_deactivation() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config_validator).unwrap(); @@ -745,7 +475,6 @@ fn test_offline_stake_delegation_and_deactivation() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; config_offline.output_format = OutputFormat::JsonCompact; @@ -768,7 +497,6 @@ fn test_offline_stake_delegation_and_deactivation() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config_payer).unwrap(); @@ -906,7 +634,6 @@ fn test_nonced_stake_delegation_and_deactivation() { nonce_authority: 0, memo: None, fee_payer: 0, - redelegation_stake_account: None, compute_unit_price: None, }; process_command(&config).unwrap(); diff --git a/program-test/tests/stake.rs b/program-test/tests/stake.rs deleted file mode 100644 index 1ad7a756b32631..00000000000000 --- a/program-test/tests/stake.rs +++ /dev/null @@ -1,193 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] - -mod setup; - -use { - setup::{setup_stake, setup_vote}, - solana_program_test::ProgramTest, - solana_sdk::{ - instruction::InstructionError, - signature::{Keypair, Signer}, - stake::{instruction as stake_instruction, instruction::StakeError}, - transaction::{Transaction, TransactionError}, - }, - test_case::test_case, -}; - -#[derive(PartialEq)] -enum PendingStakeActivationTestFlag { - MergeActive, - MergeInactive, - NoMerge, -} - -#[test_case(PendingStakeActivationTestFlag::NoMerge; "test that redelegate stake then deactivate it then withdraw from it is not permitted")] -#[test_case(PendingStakeActivationTestFlag::MergeActive; "test that redelegate stake then merge it with another active stake then deactivate it then withdraw from it is not permitted")] -#[test_case(PendingStakeActivationTestFlag::MergeInactive; "test that redelegate stake then merge it with another inactive stake then deactivate it then withdraw from it is not permitted")] -#[tokio::test] -async fn test_stake_redelegation_pending_activation(merge_flag: PendingStakeActivationTestFlag) { - let program_test = ProgramTest::default(); - let mut context = program_test.start_with_context().await; - - // 1. create first vote accounts - context.warp_to_slot(100).unwrap(); - let vote_address = setup_vote(&mut context).await; - - // 1.1 advance to normal epoch - let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot; - let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch; - let mut current_slot = first_normal_slot + slots_per_epoch; - context.warp_to_slot(current_slot).unwrap(); - context.warp_forward_force_reward_interval_end().unwrap(); - - // 2. create first stake account and delegate to first vote_address - let stake_lamports = 50_000_000_000; - let user_keypair = Keypair::new(); - let stake_address = - setup_stake(&mut context, &user_keypair, &vote_address, stake_lamports).await; - - // 2.1 advance to new epoch so that the stake is activated. - current_slot += slots_per_epoch; - context.warp_to_slot(current_slot).unwrap(); - context.warp_forward_force_reward_interval_end().unwrap(); - - // 2.2 stake is now activated and can't withdrawal directly - let transaction = Transaction::new_signed_with_payer( - &[stake_instruction::withdraw( - &stake_address, - &user_keypair.pubkey(), - &solana_sdk::pubkey::new_rand(), - 1, - None, - )], - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - let r = context.banks_client.process_transaction(transaction).await; - assert_eq!( - r.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::InsufficientFunds) - ); - - // 3. create 2nd vote account - let vote_address2 = setup_vote(&mut context).await; - - // 3.1 relegate stake account to 2nd vote account, which creates 2nd stake account - let stake_keypair2 = Keypair::new(); - let stake_address2 = stake_keypair2.pubkey(); - let transaction = Transaction::new_signed_with_payer( - &stake_instruction::redelegate( - &stake_address, - &user_keypair.pubkey(), - &vote_address2, - &stake_address2, - ), - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair, &stake_keypair2], - context.last_blockhash, - ); - context - .banks_client - .process_transaction(transaction) - .await - .unwrap(); - - if merge_flag != PendingStakeActivationTestFlag::NoMerge { - // 3.2 create 3rd to-merge stake account - let stake_address3 = - setup_stake(&mut context, &user_keypair, &vote_address2, stake_lamports).await; - - // 3.2.1 deactivate merge stake account - if merge_flag == PendingStakeActivationTestFlag::MergeInactive { - let transaction = Transaction::new_signed_with_payer( - &[stake_instruction::deactivate_stake( - &stake_address3, - &user_keypair.pubkey(), - )], - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - context - .banks_client - .process_transaction(transaction) - .await - .unwrap(); - } - - // 3.2.2 merge 3rd stake account to 2nd stake account. However, it should not clear the pending stake activation flags on stake_account2. - let transaction = Transaction::new_signed_with_payer( - &stake_instruction::merge(&stake_address2, &stake_address3, &user_keypair.pubkey()), - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - context - .banks_client - .process_transaction(transaction) - .await - .unwrap(); - } - - // 3.3 deactivate 2nd stake account should fail because of pending stake activation. - let transaction = Transaction::new_signed_with_payer( - &[stake_instruction::deactivate_stake( - &stake_address2, - &user_keypair.pubkey(), - )], - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - let r = context.banks_client.process_transaction(transaction).await; - assert_eq!( - r.unwrap_err().unwrap(), - TransactionError::InstructionError( - 0, - InstructionError::Custom( - StakeError::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted as u32 - ) - ) - ); - - // 3.4 withdraw from 2nd stake account should also fail because of pending stake activation. - let transaction = Transaction::new_signed_with_payer( - &[stake_instruction::withdraw( - &stake_address2, - &user_keypair.pubkey(), - &solana_sdk::pubkey::new_rand(), - 1, - None, - )], - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - let r = context.banks_client.process_transaction(transaction).await; - assert_eq!( - r.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::InsufficientFunds) - ); - - // 4. advance to new epoch so that the 2nd stake account is fully activated - current_slot += slots_per_epoch; - context.warp_to_slot(current_slot).unwrap(); - context.warp_forward_force_reward_interval_end().unwrap(); - - // 4.1 Now deactivate 2nd stake account should succeed because there is no pending stake activation. - let transaction = Transaction::new_signed_with_payer( - &[stake_instruction::deactivate_stake( - &stake_address2, - &user_keypair.pubkey(), - )], - Some(&context.payer.pubkey()), - &vec![&context.payer, &user_keypair], - context.last_blockhash, - ); - context - .banks_client - .process_transaction(transaction) - .await - .unwrap(); -} diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 1b874cd9750596..30671825108904 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -1,8 +1,8 @@ use { crate::stake_state::{ authorize, authorize_with_seed, deactivate, deactivate_delinquent, delegate, initialize, - merge, move_lamports, move_stake, new_warmup_cooldown_rate_epoch, redelegate, set_lockup, - split, withdraw, + merge, move_lamports, move_stake, new_warmup_cooldown_rate_epoch, set_lockup, split, + withdraw, }, log::*, solana_program_runtime::{ @@ -324,7 +324,6 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| let clock = invoke_context.get_sysvar_cache().get_clock()?; deactivate_delinquent( - invoke_context, transaction_context, instruction_context, &mut me, @@ -333,25 +332,10 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| clock.epoch, ) } + #[allow(deprecated)] StakeInstruction::Redelegate => { - let mut me = get_stake_account()?; - if invoke_context - .get_feature_set() - .is_active(&feature_set::stake_redelegate_instruction::id()) - { - instruction_context.check_number_of_instruction_accounts(3)?; - redelegate( - invoke_context, - transaction_context, - instruction_context, - &mut me, - 1, - 2, - &signers, - ) - } else { - Err(InstructionError::InvalidInstructionData) - } + let _ = get_stake_account()?; + Err(InstructionError::InvalidInstructionData) } StakeInstruction::MoveStake(lamports) => { if invoke_context @@ -428,9 +412,7 @@ mod tests { LockupArgs, StakeError, }, stake_flags::StakeFlags, - state::{ - warmup_cooldown_rate, Authorized, Lockup, StakeActivationStatus, StakeAuthorize, - }, + state::{warmup_cooldown_rate, Authorized, Lockup, StakeAuthorize}, MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION, }, stake_history::{StakeHistory, StakeHistoryEntry}, @@ -849,16 +831,6 @@ mod tests { ), Err(InstructionError::InvalidAccountOwner), ); - process_instruction_as_one_arg( - Arc::clone(&feature_set), - &instruction::redelegate( - &spoofed_stake_state_pubkey(), - &Pubkey::new_unique(), - &Pubkey::new_unique(), - &Pubkey::new_unique(), - )[2], - Err(InstructionError::InvalidAccountOwner), - ); } #[test_case(feature_set_no_minimum_delegation(); "no_min_delegation")] @@ -7305,618 +7277,6 @@ mod tests { ); } - #[test_case(feature_set_no_minimum_delegation(); "no_min_delegation")] - #[test_case(feature_set_all_enabled(); "all_enabled")] - fn test_redelegate(feature_set: Arc) { - let feature_set = Arc::new(feature_set); - - let minimum_delegation = crate::get_minimum_delegation(&feature_set); - let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of()); - let stake_history = StakeHistory::default(); - let current_epoch = 100; - - let authorized_staker = Pubkey::new_unique(); - let vote_address = Pubkey::new_unique(); - let new_vote_address = Pubkey::new_unique(); - let stake_address = Pubkey::new_unique(); - let uninitialized_stake_address = Pubkey::new_unique(); - - let prepare_stake_account = |activation_epoch, expected_stake_activation_status| { - let initial_stake_delegation = minimum_delegation + rent_exempt_reserve; - let initial_stake_state = StakeStateV2::Stake( - Meta { - authorized: Authorized { - staker: authorized_staker, - withdrawer: Pubkey::new_unique(), - }, - rent_exempt_reserve, - ..Meta::default() - }, - new_stake( - initial_stake_delegation, - &vote_address, - &VoteState::default(), - activation_epoch, - ), - StakeFlags::empty(), - ); - - if let Some(expected_stake_activation_status) = expected_stake_activation_status { - assert_eq!( - expected_stake_activation_status, - initial_stake_state - .delegation() - .unwrap() - .stake_activating_and_deactivating(current_epoch, &stake_history, None) - ); - } - - AccountSharedData::new_data_with_space( - rent_exempt_reserve + initial_stake_delegation, /* lamports */ - &initial_stake_state, - StakeStateV2::size_of(), - &id(), - ) - .unwrap() - }; - - let new_vote_account = AccountSharedData::new_data_with_space( - 1, /* lamports */ - &VoteStateVersions::new_current(VoteState::default()), - VoteState::size_of(), - &solana_vote_program::id(), - ) - .unwrap(); - - let process_instruction_redelegate = - |stake_address: &Pubkey, - stake_account: &AccountSharedData, - authorized_staker: &Pubkey, - vote_address: &Pubkey, - vote_account: &AccountSharedData, - uninitialized_stake_address: &Pubkey, - uninitialized_stake_account: &AccountSharedData, - expected_result| { - #[allow(deprecated)] - process_instruction( - Arc::clone(&feature_set), - &serialize(&StakeInstruction::Redelegate).unwrap(), - vec![ - (*stake_address, stake_account.clone()), - ( - *uninitialized_stake_address, - uninitialized_stake_account.clone(), - ), - (*vote_address, vote_account.clone()), - (*authorized_staker, AccountSharedData::default()), - ( - stake_config::id(), - config::create_account(0, &stake_config::Config::default()), - ), - ( - stake_history::id(), - create_account_shared_data_for_test(&stake_history), - ), - (rent::id(), create_account_shared_data_for_test(&rent)), - ( - clock::id(), - create_account_shared_data_for_test(&Clock { - epoch: current_epoch, - ..Clock::default() - }), - ), - ( - epoch_schedule::id(), - create_account_shared_data_for_test(&EpochSchedule::default()), - ), - ], - vec![ - AccountMeta { - pubkey: *stake_address, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: *uninitialized_stake_address, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: *vote_address, - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: stake_config::id(), - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: *authorized_staker, - is_signer: true, - is_writable: false, - }, - ], - expected_result, - ) - }; - - // - // Failure: incorrect authorized staker - // - let stake_account = prepare_stake_account(0 /*activation_epoch*/, None); - let uninitialized_stake_account = - AccountSharedData::new(0 /* lamports */, StakeStateV2::size_of(), &id()); - - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &Pubkey::new_unique(), // <-- Incorrect authorized staker - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(InstructionError::MissingRequiredSignature), - ); - - // - // Success: normal case - // - let output_accounts = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Ok(()), - ); - - assert_eq!(output_accounts[0].lamports(), rent_exempt_reserve); - if let StakeStateV2::Stake(meta, stake, _) = output_accounts[0].deserialize_data().unwrap() - { - assert_eq!(meta.rent_exempt_reserve, rent_exempt_reserve); - assert_eq!( - stake.delegation.stake, - minimum_delegation + rent_exempt_reserve - ); - assert_eq!(stake.delegation.activation_epoch, 0); - assert_eq!(stake.delegation.deactivation_epoch, current_epoch); - } else { - panic!("Invalid output_accounts[0] data"); - } - assert_eq!( - output_accounts[1].lamports(), - minimum_delegation + rent_exempt_reserve - ); - if let StakeStateV2::Stake(meta, stake, _) = output_accounts[1].deserialize_data().unwrap() - { - assert_eq!(meta.rent_exempt_reserve, rent_exempt_reserve); - assert_eq!(stake.delegation.stake, minimum_delegation); - assert_eq!(stake.delegation.activation_epoch, current_epoch); - assert_eq!(stake.delegation.deactivation_epoch, u64::MAX); - } else { - panic!("Invalid output_accounts[1] data"); - } - - // - // Variations of rescinding the deactivation of `stake_account` - // - let deactivated_stake_accounts = [ - ( - // Failure: insufficient stake in `stake_account` to even delegate normally - { - let mut deactivated_stake_account = output_accounts[0].clone(); - deactivated_stake_account - .checked_add_lamports(minimum_delegation - 1) - .unwrap(); - deactivated_stake_account - }, - Err(StakeError::InsufficientDelegation.into()), - ), - ( - // Failure: `stake_account` holds the "virtual stake" that's cooling now, with the - // real stake now warming up in `uninitialized_stake_account` - { - let mut deactivated_stake_account = output_accounts[0].clone(); - deactivated_stake_account - .checked_add_lamports(minimum_delegation) - .unwrap(); - deactivated_stake_account - }, - Err(StakeError::TooSoonToRedelegate.into()), - ), - ( - // Success: `stake_account` has been replenished with additional lamports to - // fully realize its "virtual stake" - { - let mut deactivated_stake_account = output_accounts[0].clone(); - deactivated_stake_account - .checked_add_lamports(minimum_delegation + rent_exempt_reserve) - .unwrap(); - deactivated_stake_account - }, - Ok(()), - ), - ( - // Failure: `stake_account` has been replenished with 1 lamport less than what's - // necessary to fully realize its "virtual stake" - { - let mut deactivated_stake_account = output_accounts[0].clone(); - deactivated_stake_account - .checked_add_lamports(minimum_delegation + rent_exempt_reserve - 1) - .unwrap(); - deactivated_stake_account - }, - Err(StakeError::TooSoonToRedelegate.into()), - ), - ]; - for (deactivated_stake_account, expected_result) in deactivated_stake_accounts { - #[allow(deprecated)] - process_instruction( - Arc::clone(&feature_set), - &serialize(&StakeInstruction::DelegateStake).unwrap(), - vec![ - (stake_address, deactivated_stake_account), - (vote_address, new_vote_account.clone()), - (authorized_staker, AccountSharedData::default()), - ( - stake_config::id(), - config::create_account(0, &stake_config::Config::default()), - ), - ( - stake_history::id(), - create_account_shared_data_for_test(&stake_history), - ), - (rent::id(), create_account_shared_data_for_test(&rent)), - ( - clock::id(), - create_account_shared_data_for_test(&Clock { - epoch: current_epoch, - ..Clock::default() - }), - ), - ( - epoch_schedule::id(), - create_account_shared_data_for_test(&EpochSchedule::default()), - ), - ], - vec![ - AccountMeta { - pubkey: stake_address, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: vote_address, - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: clock::id(), - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: stake_history::id(), - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: stake_config::id(), - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: authorized_staker, - is_signer: true, - is_writable: false, - }, - ], - expected_result, - ); - } - - // - // Success: `uninitialized_stake_account` starts with 42 extra lamports - // - let uninitialized_stake_account_with_extra_lamports = - AccountSharedData::new(42 /* lamports */, StakeStateV2::size_of(), &id()); - let output_accounts = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account_with_extra_lamports, - Ok(()), - ); - - assert_eq!(output_accounts[0].lamports(), rent_exempt_reserve); - assert_eq!( - output_accounts[1].lamports(), - minimum_delegation + rent_exempt_reserve + 42 - ); - if let StakeStateV2::Stake(meta, stake, _) = output_accounts[1].deserialize_data().unwrap() - { - assert_eq!(meta.rent_exempt_reserve, rent_exempt_reserve); - assert_eq!(stake.delegation.stake, minimum_delegation + 42); - assert_eq!(stake.delegation.activation_epoch, current_epoch); - assert_eq!(stake.delegation.deactivation_epoch, u64::MAX); - } else { - panic!("Invalid output_accounts[1] data"); - } - - // - // Success: `stake_account` is over-allocated and holds a greater than required `rent_exempt_reserve` - // - let mut stake_account_over_allocated = - prepare_stake_account(0 /*activation_epoch:*/, None); - if let StakeStateV2::Stake(mut meta, stake, stake_flags) = - stake_account_over_allocated.deserialize_data().unwrap() - { - meta.rent_exempt_reserve += 42; - stake_account_over_allocated - .set_state(&StakeStateV2::Stake(meta, stake, stake_flags)) - .unwrap(); - } - stake_account_over_allocated - .checked_add_lamports(42) - .unwrap(); - assert_eq!( - stake_account_over_allocated.lamports(), - (minimum_delegation + rent_exempt_reserve) + (rent_exempt_reserve + 42), - ); - assert_eq!(uninitialized_stake_account.lamports(), 0); - let output_accounts = process_instruction_redelegate( - &stake_address, - &stake_account_over_allocated, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Ok(()), - ); - - assert_eq!(output_accounts[0].lamports(), rent_exempt_reserve + 42); - if let StakeStateV2::Stake(meta, _stake, _) = output_accounts[0].deserialize_data().unwrap() - { - assert_eq!(meta.rent_exempt_reserve, rent_exempt_reserve + 42); - } else { - panic!("Invalid output_accounts[0] data"); - } - assert_eq!( - output_accounts[1].lamports(), - minimum_delegation + rent_exempt_reserve, - ); - if let StakeStateV2::Stake(meta, stake, _) = output_accounts[1].deserialize_data().unwrap() - { - assert_eq!(meta.rent_exempt_reserve, rent_exempt_reserve); - assert_eq!(stake.delegation.stake, minimum_delegation); - } else { - panic!("Invalid output_accounts[1] data"); - } - - // - // Failure: `uninitialized_stake_account` with invalid program id - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &AccountSharedData::new( - 0, /* lamports */ - StakeStateV2::size_of(), - &Pubkey::new_unique(), // <-- Invalid program id - ), - Err(InstructionError::IncorrectProgramId), - ); - - // - // Failure: `uninitialized_stake_account` with size too small - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &AccountSharedData::new(0 /* lamports */, StakeStateV2::size_of() - 1, &id()), // <-- size too small - Err(InstructionError::InvalidAccountData), - ); - - // - // Failure: `uninitialized_stake_account` with size too large - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &AccountSharedData::new(0 /* lamports */, StakeStateV2::size_of() + 1, &id()), // <-- size too large - Err(InstructionError::InvalidAccountData), - ); - - // - // Failure: `uninitialized_stake_account` with initialized stake account - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &stake_account.clone(), // <-- Initialized stake account - Err(InstructionError::AccountAlreadyInitialized), - ); - - // - // Failure: invalid `new_vote_account` - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &new_vote_address, - &uninitialized_stake_account, // <-- Invalid vote account - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(InstructionError::IncorrectProgramId), - ); - - // - // Failure: invalid `stake_account` - // - let _ = process_instruction_redelegate( - &stake_address, - &uninitialized_stake_account, // <-- Uninitialized stake account - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(InstructionError::InvalidAccountData), - ); - - // - // Failure: stake is inactive, activating or deactivating - // - let inactive_stake_account = prepare_stake_account( - current_epoch + 1, /*activation_epoch*/ - Some(StakeActivationStatus { - effective: 0, - activating: 0, - deactivating: 0, - }), - ); - let _ = process_instruction_redelegate( - &stake_address, - &inactive_stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(StakeError::RedelegateTransientOrInactiveStake.into()), - ); - - let activating_stake_account = prepare_stake_account( - current_epoch, /*activation_epoch*/ - Some(StakeActivationStatus { - effective: 0, - activating: minimum_delegation + rent_exempt_reserve, - deactivating: 0, - }), - ); - let _ = process_instruction_redelegate( - &stake_address, - &activating_stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(StakeError::RedelegateTransientOrInactiveStake.into()), - ); - - let mut deactivating_stake_account = - prepare_stake_account(0 /*activation_epoch:*/, None); - if let StakeStateV2::Stake(meta, mut stake, _stake_flags) = - deactivating_stake_account.deserialize_data().unwrap() - { - stake.deactivate(current_epoch).unwrap(); - assert_eq!( - StakeActivationStatus { - effective: minimum_delegation + rent_exempt_reserve, - activating: 0, - deactivating: minimum_delegation + rent_exempt_reserve, - }, - stake.delegation.stake_activating_and_deactivating( - current_epoch, - &stake_history, - None - ) - ); - - deactivating_stake_account - .set_state(&StakeStateV2::Stake(meta, stake, StakeFlags::empty())) - .unwrap(); - } - let _ = process_instruction_redelegate( - &stake_address, - &deactivating_stake_account, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(StakeError::RedelegateTransientOrInactiveStake.into()), - ); - - // - // Failure: `stake_account` has insufficient stake - // (less than `minimum_delegation + rent_exempt_reserve`) - // - let mut stake_account_too_few_lamports = stake_account.clone(); - if let StakeStateV2::Stake(meta, mut stake, stake_flags) = - stake_account_too_few_lamports.deserialize_data().unwrap() - { - stake.delegation.stake -= 1; - assert_eq!( - stake.delegation.stake, - minimum_delegation + rent_exempt_reserve - 1 - ); - stake_account_too_few_lamports - .set_state(&StakeStateV2::Stake(meta, stake, stake_flags)) - .unwrap(); - } else { - panic!("Invalid stake_account"); - } - stake_account_too_few_lamports - .checked_sub_lamports(1) - .unwrap(); - assert_eq!( - stake_account_too_few_lamports.lamports(), - minimum_delegation + 2 * rent_exempt_reserve - 1 - ); - - let _ = process_instruction_redelegate( - &stake_address, - &stake_account_too_few_lamports, - &authorized_staker, - &new_vote_address, - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(StakeError::InsufficientDelegation.into()), - ); - - // - // Failure: redelegate to same vote address - // - let _ = process_instruction_redelegate( - &stake_address, - &stake_account, - &authorized_staker, - &vote_address, // <-- Same vote address - &new_vote_account, - &uninitialized_stake_address, - &uninitialized_stake_account, - Err(StakeError::RedelegateToSameVoteAccount.into()), - ); - } - #[test] fn test_stake_process_instruction_with_epoch_rewards_active() { let feature_set = feature_set_all_enabled(); @@ -8080,16 +7440,6 @@ mod tests { ), Err(StakeError::EpochRewardsActive.into()), ); - process_instruction_as_one_arg( - Arc::clone(&feature_set), - &instruction::redelegate( - &Pubkey::new_unique(), - &Pubkey::new_unique(), - &Pubkey::new_unique(), - &Pubkey::new_unique(), - )[2], - Err(StakeError::EpochRewardsActive.into()), - ); process_instruction_as_one_arg( Arc::clone(&feature_set), &instruction::move_stake( diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index d9bc075d68cc82..6bb26e288db8f2 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -15,7 +15,7 @@ use { account::{AccountSharedData, ReadableAccount}, account_utils::StateMut, clock::{Clock, Epoch}, - feature_set::{self, FeatureSet}, + feature_set::FeatureSet, instruction::{checked_add, InstructionError}, pubkey::Pubkey, rent::Rent, @@ -94,25 +94,11 @@ fn redelegate_stake( let new_rate_activation_epoch = new_warmup_cooldown_rate_epoch(invoke_context); // If stake is currently active: if stake.stake(clock.epoch, stake_history, new_rate_activation_epoch) != 0 { - let stake_lamports_ok = if invoke_context - .get_feature_set() - .is_active(&feature_set::stake_redelegate_instruction::id()) - { - // When a stake account is redelegated, the delegated lamports from the source stake - // account are transferred to a new stake account. Do not permit the deactivation of - // the source stake account to be rescinded, by more generally requiring the delegation - // be configured with the expected amount of stake lamports before rescinding. - stake_lamports >= stake.delegation.stake - } else { - true - }; - // If pubkey of new voter is the same as current, // and we are scheduled to start deactivating this epoch, // we rescind deactivation if stake.delegation.voter_pubkey == *voter_pubkey && clock.epoch == stake.delegation.deactivation_epoch - && stake_lamports_ok { stake.delegation.deactivation_epoch = u64::MAX; return Ok(()); @@ -375,56 +361,15 @@ pub fn delegate( } } -fn deactivate_stake( - invoke_context: &InvokeContext, - stake: &mut Stake, - stake_flags: &mut StakeFlags, - epoch: Epoch, -) -> Result<(), InstructionError> { - if invoke_context - .get_feature_set() - .is_active(&feature_set::stake_redelegate_instruction::id()) - { - if stake_flags.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED) { - let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?; - // when MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED flag is set on stake_flags, - // deactivation is only permitted when the stake delegation activating amount is zero. - let status = stake.delegation.stake_activating_and_deactivating( - epoch, - stake_history.as_ref(), - new_warmup_cooldown_rate_epoch(invoke_context), - ); - if status.activating != 0 { - Err(InstructionError::from( - StakeError::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted, - )) - } else { - stake.deactivate(epoch)?; - // After deactivation, need to clear `MustFullyActivateBeforeDeactivationIsPermitted` flag if any. - // So that future activation and deactivation are not subject to that restriction. - stake_flags - .remove(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED); - Ok(()) - } - } else { - stake.deactivate(epoch)?; - Ok(()) - } - } else { - stake.deactivate(epoch)?; - Ok(()) - } -} - pub fn deactivate( - invoke_context: &InvokeContext, + _invoke_context: &InvokeContext, stake_account: &mut BorrowedAccount, clock: &Clock, signers: &HashSet, ) -> Result<(), InstructionError> { - if let StakeStateV2::Stake(meta, mut stake, mut stake_flags) = stake_account.get_state()? { + if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_account.get_state()? { meta.authorized.check(signers, StakeAuthorize::Staker)?; - deactivate_stake(invoke_context, &mut stake, &mut stake_flags, clock.epoch)?; + stake.deactivate(clock.epoch)?; stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags)) } else { Err(InstructionError::InvalidAccountData) @@ -669,123 +614,6 @@ pub fn merge( Ok(()) } -pub fn redelegate( - invoke_context: &InvokeContext, - transaction_context: &TransactionContext, - instruction_context: &InstructionContext, - stake_account: &mut BorrowedAccount, - uninitialized_stake_account_index: IndexOfAccount, - vote_account_index: IndexOfAccount, - signers: &HashSet, -) -> Result<(), InstructionError> { - let clock = invoke_context.get_sysvar_cache().get_clock()?; - - // ensure `uninitialized_stake_account_index` is in the uninitialized state - let mut uninitialized_stake_account = instruction_context - .try_borrow_instruction_account(transaction_context, uninitialized_stake_account_index)?; - if *uninitialized_stake_account.get_owner() != id() { - ic_msg!( - invoke_context, - "expected uninitialized stake account owner to be {}, not {}", - id(), - *uninitialized_stake_account.get_owner() - ); - return Err(InstructionError::IncorrectProgramId); - } - if uninitialized_stake_account.get_data().len() != StakeStateV2::size_of() { - ic_msg!( - invoke_context, - "expected uninitialized stake account data len to be {}, not {}", - StakeStateV2::size_of(), - uninitialized_stake_account.get_data().len() - ); - return Err(InstructionError::InvalidAccountData); - } - if !matches!( - uninitialized_stake_account.get_state()?, - StakeStateV2::Uninitialized - ) { - ic_msg!( - invoke_context, - "expected uninitialized stake account to be uninitialized", - ); - return Err(InstructionError::AccountAlreadyInitialized); - } - - // validate the provided vote account - let vote_account = instruction_context - .try_borrow_instruction_account(transaction_context, vote_account_index)?; - if *vote_account.get_owner() != solana_vote_program::id() { - ic_msg!( - invoke_context, - "expected vote account owner to be {}, not {}", - solana_vote_program::id(), - *vote_account.get_owner() - ); - return Err(InstructionError::IncorrectProgramId); - } - let vote_pubkey = *vote_account.get_key(); - let vote_state = vote_account.get_state::()?; - - let (stake_meta, effective_stake) = - if let StakeStateV2::Stake(meta, stake, _stake_flags) = stake_account.get_state()? { - let status = get_stake_status(invoke_context, &stake, &clock)?; - if status.effective == 0 || status.activating != 0 || status.deactivating != 0 { - ic_msg!(invoke_context, "stake is not active"); - return Err(StakeError::RedelegateTransientOrInactiveStake.into()); - } - - // Deny redelegating to the same vote account. This is nonsensical and could be used to - // grief the global stake warm-up/cool-down rate - if stake.delegation.voter_pubkey == vote_pubkey { - ic_msg!( - invoke_context, - "redelegating to the same vote account not permitted" - ); - return Err(StakeError::RedelegateToSameVoteAccount.into()); - } - - (meta, status.effective) - } else { - ic_msg!(invoke_context, "invalid stake account data",); - return Err(InstructionError::InvalidAccountData); - }; - - // deactivate `stake_account` - // - // Note: This function also ensures `signers` contains the `StakeAuthorize::Staker` - deactivate(invoke_context, stake_account, &clock, signers)?; - - // transfer the effective stake to the uninitialized stake account - stake_account.checked_sub_lamports(effective_stake)?; - uninitialized_stake_account.checked_add_lamports(effective_stake)?; - - // initialize and schedule `uninitialized_stake_account` for activation - let sysvar_cache = invoke_context.get_sysvar_cache(); - let rent = sysvar_cache.get_rent()?; - let mut uninitialized_stake_meta = stake_meta; - uninitialized_stake_meta.rent_exempt_reserve = - rent.minimum_balance(uninitialized_stake_account.get_data().len()); - - let ValidatedDelegatedInfo { stake_amount } = validate_delegated_amount( - &uninitialized_stake_account, - &uninitialized_stake_meta, - invoke_context.get_feature_set(), - )?; - uninitialized_stake_account.set_state(&StakeStateV2::Stake( - uninitialized_stake_meta, - new_stake( - stake_amount, - &vote_pubkey, - &vote_state.convert_to_current(), - clock.epoch, - ), - StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, - ))?; - - Ok(()) -} - pub fn move_stake( invoke_context: &InvokeContext, transaction_context: &TransactionContext, @@ -862,8 +690,6 @@ pub fn move_stake( source_stake.credits_observed, )?; - // StakeFlags::empty() is valid here because the only existing stake flag, - // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to active stakes destination_account.set_state(&StakeStateV2::Stake( destination_meta, destination_stake, @@ -881,8 +707,6 @@ pub fn move_stake( let mut destination_stake = source_stake; destination_stake.delegation.stake = lamports; - // StakeFlags::empty() is valid here because the only existing stake flag, - // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, is cleared when a stake is activated destination_account.set_state(&StakeStateV2::Stake( destination_meta, destination_stake, @@ -899,8 +723,6 @@ pub fn move_stake( } else { source_stake.delegation.stake = source_final_stake; - // StakeFlags::empty() is valid here because the only existing stake flag, - // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to active stakes source_account.set_state(&StakeStateV2::Stake( source_meta, source_stake, @@ -1079,7 +901,6 @@ pub fn withdraw( } pub(crate) fn deactivate_delinquent( - invoke_context: &InvokeContext, transaction_context: &TransactionContext, instruction_context: &InstructionContext, stake_account: &mut BorrowedAccount, @@ -1113,7 +934,7 @@ pub(crate) fn deactivate_delinquent( return Err(StakeError::InsufficientReferenceVotes.into()); } - if let StakeStateV2::Stake(meta, mut stake, mut stake_flags) = stake_account.get_state()? { + if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_account.get_state()? { if stake.delegation.voter_pubkey != *delinquent_vote_account_pubkey { return Err(StakeError::VoteAddressMismatch.into()); } @@ -1121,7 +942,7 @@ pub(crate) fn deactivate_delinquent( // Deactivate the stake account if its delegated vote account has never voted or has not // voted in the last `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, current_epoch) { - deactivate_stake(invoke_context, &mut stake, &mut stake_flags, current_epoch)?; + stake.deactivate(current_epoch)?; stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags)) } else { Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into()) diff --git a/sdk/program/src/stake/instruction.rs b/sdk/program/src/stake/instruction.rs index 57559d817ba328..89357050e93ca8 100644 --- a/sdk/program/src/stake/instruction.rs +++ b/sdk/program/src/stake/instruction.rs @@ -1,5 +1,8 @@ -#[allow(deprecated)] -use crate::stake::config; +// Remove the following `allow` when the `Redelegate` variant is renamed to +// `Unused` starting from v3. +// Required to avoid warnings from uses of deprecated types during trait derivations. +#![allow(deprecated)] + use { crate::{ clock::{Epoch, UnixTimestamp}, @@ -7,6 +10,7 @@ use { program_error::ProgramError, pubkey::Pubkey, stake::{ + config, program::id, state::{Authorized, Lockup, StakeAuthorize, StakeStateV2}, }, @@ -306,6 +310,7 @@ pub enum StakeInstruction { /// 3. `[]` Unused account, formerly the stake config /// 4. `[SIGNER]` Stake authority /// + #[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] Redelegate, /// Move stake between accounts with the same authorities and lockups, using Staker authority. @@ -726,7 +731,6 @@ pub fn delegate_stake( AccountMeta::new_readonly(*vote_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false), - #[allow(deprecated)] // For backwards compatibility we pass the stake config, although this account is unused AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(*authorized_pubkey, true), @@ -832,7 +836,6 @@ fn _redelegate( AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*uninitialized_stake_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false), - #[allow(deprecated)] // For backwards compatibility we pass the stake config, although this account is unused AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(*authorized_pubkey, true), @@ -840,6 +843,7 @@ fn _redelegate( Instruction::new_with_bincode(id(), &StakeInstruction::Redelegate, account_metas) } +#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] pub fn redelegate( stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey, @@ -858,6 +862,7 @@ pub fn redelegate( ] } +#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] pub fn redelegate_with_seed( stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey, diff --git a/sdk/program/src/stake/stake_flags.rs b/sdk/program/src/stake/stake_flags.rs index 0da426f12f6387..072305d48d619d 100644 --- a/sdk/program/src/stake/stake_flags.rs +++ b/sdk/program/src/stake/stake_flags.rs @@ -65,6 +65,10 @@ impl borsh0_10::ser::BorshSerialize for StakeFlags { /// Currently, only bit 1 is used. The other 7 bits are reserved for future usage. impl StakeFlags { /// Stake must be fully activated before deactivation is allowed (bit 1). + #[deprecated( + since = "2.1.0", + note = "This flag will be removed because it was only used for `redelegate`, which will not be enabled." + )] pub const MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED: Self = Self { bits: 0b0000_0001 }; @@ -102,6 +106,7 @@ mod test { use super::*; #[test] + #[allow(deprecated)] fn test_stake_flags() { let mut f = StakeFlags::empty(); assert!(!f.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); diff --git a/sdk/program/src/stake/state.rs b/sdk/program/src/stake/state.rs index b2a03d68c3d672..22fc5ea44645c6 100644 --- a/sdk/program/src/stake/state.rs +++ b/sdk/program/src/stake/state.rs @@ -1071,6 +1071,7 @@ mod test { }, lockup: Lockup::default(), })); + #[allow(deprecated)] check_borsh_serialization(StakeStateV2::Stake( Meta { rent_exempt_reserve: 1, @@ -1152,6 +1153,7 @@ mod test { assert_eq!(bincode_serialized[FLAG_OFFSET], expected); assert_eq!(borsh_serialized[FLAG_OFFSET], expected); }; + #[allow(deprecated)] check_flag( StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, 1, diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 3ff450ac20b3e1..887d2e547f19b2 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -282,10 +282,6 @@ pub mod stake_deactivate_delinquent_instruction { solana_sdk::declare_id!("437r62HoAdUb63amq3D7ENnBLDhHT2xY8eFkLJYVKK4x"); } -pub mod stake_redelegate_instruction { - solana_sdk::declare_id!("2KKG3C6RBnxQo9jVVrbzsoSh41TDXLK7gBc9gduyxSzW"); -} - pub mod vote_withdraw_authority_may_change_authorized_voter { solana_sdk::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU"); } @@ -950,7 +946,6 @@ lazy_static! { (nonce_must_be_authorized::id(), "nonce must be authorized"), (nonce_must_be_advanceable::id(), "durable nonces must be advanceable"), (vote_authorize_with_seed::id(), "An instruction you can use to change a vote accounts authority when the current authority is a derived key #25860"), - (stake_redelegate_instruction::id(), "enable the redelegate stake instruction #26294"), (preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"), (enable_bpf_loader_extend_program_ix::id(), "enable bpf upgradeable loader ExtendProgram instruction #25234"), (skip_rent_rewrites::id(), "skip rewriting rent exempt accounts during rent collection #26491"), diff --git a/transaction-status/src/parse_stake.rs b/transaction-status/src/parse_stake.rs index f1586e58694950..aff72d5bbe8320 100644 --- a/transaction-status/src/parse_stake.rs +++ b/transaction-status/src/parse_stake.rs @@ -284,6 +284,7 @@ pub fn parse_stake( }), }) } + #[allow(deprecated)] StakeInstruction::Redelegate => { check_num_stake_accounts(&instruction.accounts, 5)?; Ok(ParsedInstructionEnum {