diff --git a/account-decoder/src/lib.rs b/account-decoder/src/lib.rs index b0f52c8d01..67317a4c41 100644 --- a/account-decoder/src/lib.rs +++ b/account-decoder/src/lib.rs @@ -7,6 +7,7 @@ extern crate serde_derive; pub mod parse_account_data; pub mod parse_address_lookup_table; pub mod parse_bpf_loader; +#[allow(deprecated)] pub mod parse_config; pub mod parse_nonce; pub mod parse_stake; diff --git a/account-decoder/src/parse_config.rs b/account-decoder/src/parse_config.rs index f1fa51a286..eadc267555 100644 --- a/account-decoder/src/parse_config.rs +++ b/account-decoder/src/parse_config.rs @@ -8,7 +8,9 @@ use { solana_config_program::{get_config_data, ConfigKeys}, solana_sdk::{ pubkey::Pubkey, - stake::config::{self as stake_config, Config as StakeConfig}, + stake::config::{ + Config as StakeConfig, {self as stake_config}, + }, }, }; @@ -66,6 +68,10 @@ pub struct UiConfigKey { pub signer: bool, } +#[deprecated( + since = "1.16.7", + note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead" +)] #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UiStakeConfig { diff --git a/account-decoder/src/parse_stake.rs b/account-decoder/src/parse_stake.rs index 50197cb0d3..f870789777 100644 --- a/account-decoder/src/parse_stake.rs +++ b/account-decoder/src/parse_stake.rs @@ -119,11 +119,16 @@ pub struct UiDelegation { pub stake: StringAmount, pub activation_epoch: StringAmount, pub deactivation_epoch: StringAmount, + #[deprecated( + since = "1.16.7", + note = "Please use `solana_sdk::stake::stake::warmup_cooldown_rate()` instead" + )] pub warmup_cooldown_rate: f64, } impl From for UiDelegation { fn from(delegation: Delegation) -> Self { + #[allow(deprecated)] Self { voter: delegation.voter_pubkey.to_string(), stake: delegation.stake.to_string(), @@ -139,6 +144,7 @@ mod test { use {super::*, bincode::serialize, solana_sdk::stake::stake_flags::StakeFlags}; #[test] + #[allow(deprecated)] fn test_parse_stake() { let stake_state = StakeState::Uninitialized; let stake_data = serialize(&stake_state).unwrap(); diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 208517edd4..ad8758b3e7 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -2,6 +2,7 @@ use { crate::{ cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, compute_unit_price::WithComputeUnitPrice, + feature::get_feature_activation_epoch, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, }, clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}, @@ -43,6 +44,7 @@ use { clock::{self, Clock, Slot}, commitment_config::CommitmentConfig, epoch_schedule::Epoch, + feature_set, hash::Hash, message::Message, native_token::lamports_to_sol, @@ -1800,6 +1802,8 @@ pub fn process_show_stakes( let stake_history = from_account(&stake_history_account).ok_or_else(|| { CliError::RpcRequestError("Failed to deserialize stake history".to_string()) })?; + let new_rate_activation_epoch = + get_feature_activation_epoch(rpc_client, &feature_set::reduce_stake_warmup_cooldown::id())?; let mut stake_accounts: Vec = vec![]; for (stake_pubkey, stake_account) in all_stake_accounts { @@ -1815,6 +1819,7 @@ pub fn process_show_stakes( use_lamports_unit, &stake_history, &clock, + new_rate_activation_epoch, ), }); } @@ -1833,6 +1838,7 @@ pub fn process_show_stakes( use_lamports_unit, &stake_history, &clock, + new_rate_activation_epoch, ), }); } diff --git a/cli/src/feature.rs b/cli/src/feature.rs index a5972bca7a..348a7e8fc6 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -22,6 +22,7 @@ use { genesis_config::ClusterType, message::Message, pubkey::Pubkey, + stake_history::Epoch, transaction::Transaction, }, std::{cmp::Ordering, collections::HashMap, fmt, str::FromStr, sync::Arc}, @@ -817,6 +818,22 @@ pub fn get_feature_is_active( .map(|status| matches!(status, Some(CliFeatureStatus::Active(_)))) } +pub fn get_feature_activation_epoch( + rpc_client: &RpcClient, + feature_id: &Pubkey, +) -> Result, ClientError> { + rpc_client + .get_feature_activation_slot(feature_id) + .and_then(|activation_slot: Option| { + rpc_client + .get_epoch_schedule() + .map(|epoch_schedule| (activation_slot, epoch_schedule)) + }) + .map(|(activation_slot, epoch_schedule)| { + activation_slot.map(|slot| epoch_schedule.get_epoch(slot)) + }) +} + fn process_status( rpc_client: &RpcClient, config: &CliConfig, diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 9e1726744b..58d65aa1c2 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -6,6 +6,7 @@ use { ProcessResult, }, compute_unit_price::WithComputeUnitPrice, + feature::get_feature_activation_epoch, memo::WithMemo, nonce::check_nonce_account, spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, @@ -40,6 +41,7 @@ use { clock::{Clock, UnixTimestamp, SECONDS_PER_DAY}, commitment_config::CommitmentConfig, epoch_schedule::EpochSchedule, + feature_set, message::Message, pubkey::Pubkey, stake::{ @@ -48,7 +50,7 @@ use { state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeState}, tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent}, }, - stake_history::StakeHistory, + stake_history::{Epoch, StakeHistory}, system_instruction::SystemError, sysvar::{clock, stake_history}, transaction::Transaction, @@ -2186,6 +2188,7 @@ pub fn build_stake_state( use_lamports_unit: bool, stake_history: &StakeHistory, clock: &Clock, + new_rate_activation_epoch: Option, ) -> CliStakeState { match stake_state { StakeState::Stake( @@ -2202,9 +2205,11 @@ pub fn build_stake_state( effective, activating, deactivating, - } = stake - .delegation - .stake_activating_and_deactivating(current_epoch, Some(stake_history)); + } = stake.delegation.stake_activating_and_deactivating( + current_epoch, + Some(stake_history), + new_rate_activation_epoch, + ); let lockup = if lockup.is_in_force(clock, None) { Some(lockup.into()) } else { @@ -2424,6 +2429,10 @@ pub fn process_show_stake_account( let clock: Clock = from_account(&clock_account).ok_or_else(|| { CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string()) })?; + let new_rate_activation_epoch = get_feature_activation_epoch( + rpc_client, + &feature_set::reduce_stake_warmup_cooldown::id(), + )?; let mut state = build_stake_state( stake_account.lamports, @@ -2431,6 +2440,7 @@ pub fn process_show_stake_account( use_lamports_unit, &stake_history, &clock, + new_rate_activation_epoch, ); if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() { diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 879f3ae561..c066a52165 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -38,13 +38,13 @@ use { pubkey::Pubkey, signature::{Keypair, Signer}, stake::{ - config as stake_config, instruction as stake_instruction, + instruction as stake_instruction, state::{Authorized, Lockup}, }, system_transaction, transaction::Transaction, }, - solana_stake_program::{config::create_account as create_stake_config_account, stake_state}, + solana_stake_program::stake_state, solana_streamer::socket::SocketAddrSpace, solana_tpu_client::tpu_client::{ DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC, @@ -267,18 +267,6 @@ impl LocalCluster { .native_instruction_processors .extend_from_slice(&config.native_instruction_processors); - // Replace staking config - genesis_config.add_account( - stake_config::id(), - create_stake_config_account( - 1, - &stake_config::Config { - warmup_cooldown_rate: std::f64::MAX, - slash_penalty: std::u8::MAX, - }, - ), - ); - let (leader_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); let leader_contact_info = leader_node.info.clone(); let mut leader_config = safe_clone_config(&config.validator_configs[0]); diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index d1e54ede74..2dbf6dbc39 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -1535,10 +1535,13 @@ fn test_fake_shreds_broadcast_leader() { fn test_wait_for_max_stake() { solana_logger::setup_with_default(RUST_LOG_FILTER); let validator_config = ValidatorConfig::default_for_test(); + let slots_per_epoch = MINIMUM_SLOTS_PER_EPOCH; let mut config = ClusterConfig { cluster_lamports: DEFAULT_CLUSTER_LAMPORTS, node_stakes: vec![DEFAULT_NODE_STAKE; 4], validator_configs: make_identical_validator_configs(&validator_config, 4), + slots_per_epoch, + stakers_slot_offset: slots_per_epoch, ..ClusterConfig::default() }; let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified); diff --git a/program-test/tests/warp.rs b/program-test/tests/warp.rs index 5da577acdc..7ab151dd63 100644 --- a/program-test/tests/warp.rs +++ b/program-test/tests/warp.rs @@ -278,7 +278,7 @@ async fn stake_rewards_from_warp() { assert_eq!( stake .delegation - .stake_activating_and_deactivating(clock.epoch, Some(&stake_history)), + .stake_activating_and_deactivating(clock.epoch, Some(&stake_history), None), StakeActivationStatus::with_effective(stake.delegation.stake), ); } @@ -394,7 +394,7 @@ async fn stake_rewards_filter_bench_core(num_stake_accounts: u64) { assert_eq!( stake .delegation - .stake_activating_and_deactivating(clock.epoch, Some(&stake_history)), + .stake_activating_and_deactivating(clock.epoch, Some(&stake_history), None), StakeActivationStatus::with_effective(stake.delegation.stake), ); } diff --git a/programs/config/src/lib.rs b/programs/config/src/lib.rs index 912c69ab23..d7ec42a0e6 100644 --- a/programs/config/src/lib.rs +++ b/programs/config/src/lib.rs @@ -4,6 +4,8 @@ pub mod config_processor; pub mod date_instruction; pub use solana_sdk::config::program::id; +#[allow(deprecated)] +use solana_sdk::stake::config::Config as StakeConfig; use { bincode::{deserialize, serialize, serialized_size}, serde_derive::{Deserialize, Serialize}, @@ -11,7 +13,6 @@ use { account::{Account, AccountSharedData}, pubkey::Pubkey, short_vec, - stake::config::Config as StakeConfig, }, }; @@ -21,6 +22,7 @@ pub trait ConfigState: serde::Serialize + Default { } // TODO move ConfigState into `solana_program` to implement trait locally +#[allow(deprecated)] impl ConfigState for StakeConfig { fn max_space() -> u64 { serialized_size(&StakeConfig::default()).unwrap() diff --git a/programs/stake/src/config.rs b/programs/stake/src/config.rs index 1b6b138051..d08bd82021 100644 --- a/programs/stake/src/config.rs +++ b/programs/stake/src/config.rs @@ -5,27 +5,31 @@ note = "Please use `solana_sdk::stake::config` or `solana_program::stake::config` instead" )] pub use solana_sdk::stake::config::*; +#[allow(deprecated)] +use solana_sdk::stake::config::{self, Config}; use { bincode::deserialize, solana_config_program::{create_config_account, get_config_data}, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, genesis_config::GenesisConfig, - stake::config::{self, Config}, transaction_context::BorrowedAccount, }, }; +#[allow(deprecated)] pub fn from(account: &BorrowedAccount) -> Option { get_config_data(account.get_data()) .ok() .and_then(|data| deserialize(data).ok()) } +#[allow(deprecated)] pub fn create_account(lamports: u64, config: &Config) -> AccountSharedData { create_config_account(vec![], config, lamports) } +#[allow(deprecated)] pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 { let mut account = create_config_account(vec![], &Config::default(), 0); let lamports = genesis_config.rent.minimum_balance(account.data().len()); diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 743b7d8d0f..21e982fb4b 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -3,7 +3,8 @@ use { config, stake_state::{ authorize, authorize_with_seed, deactivate, deactivate_delinquent, delegate, - initialize, merge, redelegate, set_lockup, split, withdraw, + initialize, merge, new_warmup_cooldown_rate_epoch, redelegate, set_lockup, split, + withdraw, }, }, log::*, @@ -172,14 +173,19 @@ declare_process_instruction!( )?; instruction_context.check_number_of_instruction_accounts(5)?; drop(me); - let config_account = - instruction_context.try_borrow_instruction_account(transaction_context, 4)?; - if !config::check_id(config_account.get_key()) { - return Err(InstructionError::InvalidArgument); - } - let config = + if !invoke_context + .feature_set + .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) + { + // Post feature activation, remove both the feature gate code and the config completely in the interface + let config_account = instruction_context + .try_borrow_instruction_account(transaction_context, 4)?; + #[allow(deprecated)] + if !config::check_id(config_account.get_key()) { + return Err(InstructionError::InvalidArgument); + } config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; - drop(config_account); + } delegate( invoke_context, transaction_context, @@ -188,7 +194,6 @@ declare_process_instruction!( 1, &clock, &stake_history, - &config, &signers, &invoke_context.feature_set, ) @@ -255,6 +260,7 @@ declare_process_instruction!( } else { None }, + new_warmup_cooldown_rate_epoch(invoke_context), ) } Ok(StakeInstruction::Deactivate) => { @@ -422,15 +428,19 @@ declare_process_instruction!( .is_active(&feature_set::stake_redelegate_instruction::id()) { instruction_context.check_number_of_instruction_accounts(3)?; - let config_account = instruction_context - .try_borrow_instruction_account(transaction_context, 3)?; - if !config::check_id(config_account.get_key()) { - return Err(InstructionError::InvalidArgument); - } - let config = + if !invoke_context + .feature_set + .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) + { + // Post feature activation, remove both the feature gate code and the config completely in the interface + let config_account = instruction_context + .try_borrow_instruction_account(transaction_context, 3)?; + #[allow(deprecated)] + if !config::check_id(config_account.get_key()) { + return Err(InstructionError::InvalidArgument); + } config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; - drop(config_account); - + } redelegate( invoke_context, transaction_context, @@ -438,7 +448,6 @@ declare_process_instruction!( &mut me, 1, 2, - &config, &signers, ) } else { @@ -454,9 +463,12 @@ declare_process_instruction!( mod tests { use { super::*, - crate::stake_state::{ - authorized_from, create_stake_history_from_delegations, from, new_stake, stake_from, - Delegation, Meta, Stake, StakeState, + crate::{ + config, + stake_state::{ + authorized_from, create_stake_history_from_delegations, from, new_stake, + stake_from, Delegation, Meta, Stake, StakeState, + }, }, assert_matches::assert_matches, bincode::serialize, @@ -470,7 +482,8 @@ mod tests { }, account_utils::StateMut, clock::{Epoch, UnixTimestamp}, - feature_set::FeatureSet, + epoch_schedule::EpochSchedule, + feature_set::{reduce_stake_warmup_cooldown::NewWarmupCooldownRateEpoch, FeatureSet}, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, rent::Rent, @@ -482,26 +495,34 @@ mod tests { LockupArgs, StakeError, }, stake_flags::StakeFlags, - state::{Authorized, Lockup, StakeActivationStatus, StakeAuthorize}, + state::{ + warmup_cooldown_rate, Authorized, Lockup, StakeActivationStatus, StakeAuthorize, + }, MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION, }, stake_history::{StakeHistory, StakeHistoryEntry}, system_program, - sysvar::{clock, rent, rewards, stake_history}, + sysvar::{clock, epoch_schedule, rent, rewards, stake_history}, }, solana_vote_program::vote_state::{self, VoteState, VoteStateVersions}, std::{collections::HashSet, str::FromStr, sync::Arc}, test_case::test_case, }; - /// The "new" behavior enables all features - fn feature_set_new_behavior() -> Arc { + fn feature_set_all_enabled() -> Arc { Arc::new(FeatureSet::all_enabled()) } - /// The "old" behavior is before the stake minimum delegation was raised - fn feature_set_old_behavior() -> Arc { - let mut feature_set = feature_set_new_behavior(); + /// With stake minimum delegation but 25% warmup/cooldown + fn feature_set_old_warmup_cooldown() -> Arc { + let mut feature_set = FeatureSet::all_enabled(); + feature_set.deactivate(&feature_set::reduce_stake_warmup_cooldown::id()); + Arc::new(feature_set) + } + + /// No stake minimum delegation and 25% warmup/cooldown + fn feature_set_old_warmup_cooldown_no_minimum_delegation() -> Arc { + let mut feature_set = feature_set_old_warmup_cooldown(); Arc::get_mut(&mut feature_set) .unwrap() .deactivate(&feature_set::stake_raise_minimum_delegation_to_1_sol::id()); @@ -565,6 +586,8 @@ mod tests { .map(|meta| meta.pubkey) .collect(); pubkeys.insert(clock::id()); + pubkeys.insert(epoch_schedule::id()); + #[allow(deprecated)] let transaction_accounts = pubkeys .iter() .map(|pubkey| { @@ -578,6 +601,8 @@ mod tests { create_account_shared_data_for_test(&StakeHistory::default()) } else if stake_config::check_id(pubkey) { config::create_account(0, &stake_config::Config::default()) + } else if epoch_schedule::check_id(pubkey) { + create_account_shared_data_for_test(&EpochSchedule::default()) } else if rent::check_id(pubkey) { create_account_shared_data_for_test(&Rent::default()) } else if *pubkey == invalid_stake_state_pubkey() { @@ -615,8 +640,9 @@ mod tests { ) } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_process_instruction(feature_set: Arc) { process_instruction_as_one_arg( Arc::clone(&feature_set), @@ -732,8 +758,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_spoofed_stake_accounts(feature_set: Arc) { process_instruction_as_one_arg( Arc::clone(&feature_set), @@ -860,8 +887,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_process_instruction_decode_bail(feature_set: Arc) { // these will not call stake_state, have bogus contents let stake_address = Pubkey::new_unique(); @@ -877,7 +905,9 @@ mod tests { let vote_account = AccountSharedData::new(0, 0, &solana_vote_program::id()); let clock_address = clock::id(); let clock_account = create_account_shared_data_for_test(&clock::Clock::default()); + #[allow(deprecated)] let config_address = stake_config::id(); + #[allow(deprecated)] let config_account = config::create_account(0, &stake_config::Config::default()); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); let minimum_delegation = crate::get_minimum_delegation(&feature_set); @@ -1126,8 +1156,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_checked_instructions(feature_set: Arc) { let stake_address = Pubkey::new_unique(); let staker = Pubkey::new_unique(); @@ -1488,8 +1519,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_initialize(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -1593,8 +1625,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_authorize(feature_set: Arc) { let authority_address = solana_sdk::pubkey::new_rand(); let authority_address_2 = solana_sdk::pubkey::new_rand(); @@ -1621,6 +1654,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let mut instruction_accounts = vec![ AccountMeta { @@ -1774,8 +1811,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_authorize_override(feature_set: Arc) { let authority_address = solana_sdk::pubkey::new_rand(); let mallory_address = solana_sdk::pubkey::new_rand(); @@ -1893,8 +1931,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_authorize_with_seed(feature_set: Arc) { let authority_base_address = solana_sdk::pubkey::new_rand(); let authority_address = solana_sdk::pubkey::new_rand(); @@ -2010,8 +2049,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_authorize_delegated_stake(feature_set: Arc) { let authority_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); @@ -2031,6 +2071,7 @@ mod tests { let mut vote_account_2 = vote_state::create_account(&vote_address_2, &solana_sdk::pubkey::new_rand(), 0, 100); vote_account_2.set_state(&VoteState::default()).unwrap(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -2051,7 +2092,12 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; + #[allow(deprecated)] let mut instruction_accounts = vec![ AccountMeta { pubkey: stake_address, @@ -2201,8 +2247,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_delegate(feature_set: Arc) { let mut vote_state = VoteState::default(); for i in 0..1000 { @@ -2241,6 +2288,7 @@ mod tests { epoch: 1, ..Clock::default() }; + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account.clone()), (vote_address, vote_account), @@ -2254,7 +2302,12 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; + #[allow(deprecated)] let mut instruction_accounts = vec![ AccountMeta { pubkey: stake_address, @@ -2442,8 +2495,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_redelegate_consider_balance_changes(feature_set: Arc) { let mut clock = Clock::default(); let rent = Rent::default(); @@ -2466,6 +2520,7 @@ mod tests { &id(), ) .unwrap(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -2483,7 +2538,12 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; + #[allow(deprecated)] let delegate_instruction_accounts = vec![ AccountMeta { pubkey: stake_address, @@ -2642,8 +2702,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let minimum_delegation = crate::get_minimum_delegation(&feature_set); @@ -2749,8 +2810,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_withdraw_stake(feature_set: Arc) { let recipient_address = solana_sdk::pubkey::new_rand(); let authority_address = solana_sdk::pubkey::new_rand(); @@ -2771,6 +2833,7 @@ mod tests { vote_account .set_state(&VoteStateVersions::new_current(VoteState::default())) .unwrap(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -2796,6 +2859,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let mut instruction_accounts = vec![ AccountMeta { @@ -2887,6 +2954,7 @@ mod tests { ); // Stake some lamports (available lamports for withdrawals will reduce to zero) + #[allow(deprecated)] let accounts = process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -3037,8 +3105,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_withdraw_stake_before_warmup(feature_set: Arc) { let recipient_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); @@ -3062,6 +3131,7 @@ mod tests { epoch: 16, ..Clock::default() }; + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -3075,6 +3145,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let instruction_accounts = vec![ AccountMeta { @@ -3105,6 +3179,7 @@ mod tests { ]; // Stake some lamports (available lamports for withdrawals will reduce to zero) + #[allow(deprecated)] let accounts = process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -3145,6 +3220,7 @@ mod tests { None, 0..clock.epoch, &[stake_from(&accounts[0]).unwrap().delegation], + None, ); transaction_accounts[4] = ( stake_history::id(), @@ -3164,8 +3240,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_withdraw_lockup(feature_set: Arc) { let recipient_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand(); @@ -3196,6 +3273,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let mut instruction_accounts = vec![ AccountMeta { @@ -3284,8 +3365,9 @@ mod tests { assert_eq!(from(&accounts[0]).unwrap(), StakeState::Uninitialized); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_withdraw_rent_exempt(feature_set: Arc) { let recipient_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand(); @@ -3316,6 +3398,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let instruction_accounts = vec![ AccountMeta { @@ -3376,8 +3462,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_deactivate(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let minimum_delegation = crate::get_minimum_delegation(&feature_set); @@ -3395,6 +3482,7 @@ mod tests { vote_account .set_state(&VoteStateVersions::new_current(VoteState::default())) .unwrap(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -3410,6 +3498,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let mut instruction_accounts = vec![ AccountMeta { @@ -3445,6 +3537,7 @@ mod tests { ); // Staking + #[allow(deprecated)] let accounts = process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -3500,8 +3593,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_set_lockup(feature_set: Arc) { let custodian_address = solana_sdk::pubkey::new_rand(); let authorized_address = solana_sdk::pubkey::new_rand(); @@ -3527,6 +3621,7 @@ mod tests { custodian: Some(custodian_address), })) .unwrap(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -3548,6 +3643,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let mut instruction_accounts = vec![ AccountMeta { @@ -3627,6 +3726,7 @@ mod tests { ); // Staking + #[allow(deprecated)] let accounts = process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -3783,8 +3883,9 @@ mod tests { /// Ensure that `initialize()` respects the minimum balance requirements /// - Assert 1: accounts with a balance equal-to the rent exemption initialize OK /// - Assert 2: accounts with a balance less-than the rent exemption do not initialize - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_initialize_minimum_balance(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -3838,8 +3939,9 @@ mod tests { /// withdrawing below the minimum delegation, then re-delegating successfully (see /// `test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation()` for /// more information.) - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_delegate_minimum_stake_delegation(feature_set: Arc) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); @@ -3852,6 +3954,7 @@ mod tests { let vote_address = solana_sdk::pubkey::new_rand(); let vote_account = vote_state::create_account(&vote_address, &solana_sdk::pubkey::new_rand(), 0, 100); + #[allow(deprecated)] let instruction_accounts = vec![ AccountMeta { pubkey: stake_address, @@ -3897,6 +4000,7 @@ mod tests { &id(), ) .unwrap(); + #[allow(deprecated)] process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -3915,6 +4019,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ], instruction_accounts.clone(), expected_result.clone().map_err(|e| e.into()), @@ -3933,8 +4041,9 @@ mod tests { /// EQ | LT | Err /// LT | EQ | Err /// LT | LT | Err - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_minimum_stake_delegation(feature_set: Arc) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); @@ -4025,8 +4134,9 @@ mod tests { /// delegation is OK /// - Assert 2: splitting the full amount from an account that has less than the minimum /// delegation is not OK - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_full_amount_minimum_stake_delegation(feature_set: Arc) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); @@ -4095,8 +4205,9 @@ mod tests { /// Ensure that `split()` correctly handles prefunded destination accounts from /// initialized stakes. When a destination account already has funds, ensure /// the minimum split amount reduces accordingly. - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_initialized_split_destination_minimum_balance(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4189,8 +4300,9 @@ mod tests { /// Ensure that `split()` correctly handles prefunded destination accounts from staked stakes. /// When a destination account already has funds, ensure the minimum split amount reduces /// accordingly. - #[test_case(feature_set_old_behavior(), &[Ok(()), Ok(())]; "old_behavior")] - #[test_case(feature_set_new_behavior(), &[ Err(StakeError::InsufficientDelegation.into()), Err(StakeError::InsufficientDelegation.into()) ] ; "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(), &[Ok(()), Ok(())]; "old_behavior")] + #[test_case(feature_set_old_warmup_cooldown(), &[ Err(StakeError::InsufficientDelegation.into()), Err(StakeError::InsufficientDelegation.into()) ] ; "new_behavior")] + #[test_case(feature_set_all_enabled(), &[Err(StakeError::InsufficientDelegation.into()), Err(StakeError::InsufficientDelegation.into())]; "all_enabled")] fn test_staked_split_destination_minimum_balance( feature_set: Arc, expected_results: &[Result<(), InstructionError>], @@ -4339,8 +4451,9 @@ mod tests { /// Ensure that `withdraw()` respects the minimum delegation requirements /// - Assert 1: withdrawing so remaining stake is equal-to the minimum is OK /// - Assert 2: withdrawing so remaining stake is less-than the minimum is not OK - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_withdraw_minimum_stake_delegation(feature_set: Arc) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); @@ -4400,6 +4513,7 @@ mod tests { .unwrap(); let withdraw_amount = (starting_stake_delegation + rewards_balance) - ending_stake_delegation; + #[allow(deprecated)] process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::Withdraw(withdraw_amount)).unwrap(), @@ -4425,6 +4539,10 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ], instruction_accounts.clone(), expected_result.clone(), @@ -4446,7 +4564,7 @@ mod tests { /// 5. Re-delegates, now with less than the minimum delegation, but it still succeeds #[test] fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() { - let feature_set = feature_set_new_behavior(); + let feature_set = feature_set_all_enabled(); let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4461,6 +4579,7 @@ mod tests { vote_state::create_account(&vote_address, &solana_sdk::pubkey::new_rand(), 0, 100); let recipient_address = solana_sdk::pubkey::new_rand(); let mut clock = Clock::default(); + #[allow(deprecated)] let mut transaction_accounts = vec![ (stake_address, stake_account), (vote_address, vote_account), @@ -4477,8 +4596,13 @@ mod tests { stake_config::id(), config::create_account(0, &stake_config::Config::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), (rent::id(), create_account_shared_data_for_test(&rent)), ]; + #[allow(deprecated)] let instruction_accounts = vec![ AccountMeta { pubkey: stake_address, @@ -4611,8 +4735,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_source_uninitialized(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4711,8 +4836,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_split_not_uninitialized(feature_set: Arc) { let stake_lamports = 42; let stake_address = solana_sdk::pubkey::new_rand(); @@ -4762,8 +4888,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_more_than_staked(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4818,8 +4945,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_with_rent(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4926,8 +5054,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_to_account_with_rent_exempt_reserve(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -5050,8 +5179,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_from_larger_sized_account(feature_set: Arc) { let rent = Rent::default(); let source_larger_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of() + 100); @@ -5180,8 +5310,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_from_smaller_sized_account(feature_set: Arc) { let rent = Rent::default(); let source_smaller_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -5256,8 +5387,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_100_percent_of_source(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -5350,8 +5482,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_100_percent_of_source_to_account_with_lamports(feature_set: Arc) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -5444,8 +5577,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_split_rent_exemptness(feature_set: Arc) { let rent = Rent::default(); let source_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of() + 100); @@ -5577,8 +5711,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let merge_from_address = solana_sdk::pubkey::new_rand(); @@ -5647,6 +5782,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; // Authorized staker signature required... @@ -5708,8 +5847,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge_self_fails(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let authorized_address = solana_sdk::pubkey::new_rand(); @@ -5785,8 +5925,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge_incorrect_authorized_staker(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let merge_from_address = solana_sdk::pubkey::new_rand(); @@ -5856,6 +5997,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; instruction_accounts[4].pubkey = wrong_authorized_address; @@ -5879,8 +6024,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge_invalid_account_data(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let merge_from_address = solana_sdk::pubkey::new_rand(); @@ -5947,6 +6093,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&StakeHistory::default()), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; process_instruction( @@ -5960,8 +6110,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge_fake_stake_source(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let merge_from_address = solana_sdk::pubkey::new_rand(); @@ -6031,8 +6182,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_merge_active_stake(feature_set: Arc) { let stake_address = solana_sdk::pubkey::new_rand(); let merge_from_address = solana_sdk::pubkey::new_rand(); @@ -6101,6 +6253,10 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&stake_history), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ]; let instruction_accounts = vec![ AccountMeta { @@ -6164,14 +6320,20 @@ mod tests { Ok(()), ); + let new_warmup_cooldown_rate_epoch = + feature_set.new_warmup_cooldown_rate_epoch(&EpochSchedule::default()); + // both activating fails loop { clock.epoch += 1; if clock.epoch == merge_from_activation_epoch { activating += merge_from_amount; } - let delta = - activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + let delta = activating.min( + (effective as f64 + * warmup_cooldown_rate(clock.epoch, new_warmup_cooldown_rate_epoch)) + as u64, + ); effective += delta; activating -= delta; stake_history.add( @@ -6187,8 +6349,18 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&stake_history), ); - if stake_amount == stake.stake(clock.epoch, Some(&stake_history)) - && merge_from_amount == merge_from_stake.stake(clock.epoch, Some(&stake_history)) + if stake_amount + == stake.stake( + clock.epoch, + Some(&stake_history), + new_warmup_cooldown_rate_epoch, + ) + && merge_from_amount + == merge_from_stake.stake( + clock.epoch, + Some(&stake_history), + new_warmup_cooldown_rate_epoch, + ) { break; } @@ -6215,8 +6387,11 @@ mod tests { // active/deactivating and deactivating/inactive mismatches fail loop { clock.epoch += 1; - let delta = - deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + let delta = deactivating.min( + (effective as f64 + * warmup_cooldown_rate(clock.epoch, new_warmup_cooldown_rate_epoch)) + as u64, + ); effective -= delta; deactivating -= delta; if clock.epoch == stake_deactivation_epoch { @@ -6264,8 +6439,16 @@ mod tests { stake_history::id(), create_account_shared_data_for_test(&stake_history), ); - if 0 == stake.stake(clock.epoch, Some(&stake_history)) - && 0 == merge_from_stake.stake(clock.epoch, Some(&stake_history)) + if 0 == stake.stake( + clock.epoch, + Some(&stake_history), + new_warmup_cooldown_rate_epoch, + ) && 0 + == merge_from_stake.stake( + clock.epoch, + Some(&stake_history), + new_warmup_cooldown_rate_epoch, + ) { break; } @@ -6286,8 +6469,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_get_minimum_delegation(feature_set: Arc) { let stake_address = Pubkey::new_unique(); let stake_account = create_default_stake_account(); @@ -6325,8 +6509,9 @@ mod tests { // The GetMinimumDelegation instruction does not take any accounts; so when it was added, // `process_instruction()` needed to be updated to *not* need a stake account passed in, which // changes the error *ordering* conditions. - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_stake_process_instruction_error_ordering(feature_set: Arc) { let rent = Rent::default(); let rent_address = rent::id(); @@ -6393,8 +6578,9 @@ mod tests { } } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_deactivate_delinquent(feature_set: Arc) { let reference_vote_address = Pubkey::new_unique(); let vote_address = Pubkey::new_unique(); @@ -6407,7 +6593,6 @@ mod tests { &vote_address, &VoteState::default(), 1, /* activation_epoch */ - &stake_config::Config::default(), ), StakeFlags::empty(), ); @@ -6607,7 +6792,6 @@ mod tests { &unrelated_vote_address, &VoteState::default(), 1, /* activation_epoch */ - &stake_config::Config::default(), ), StakeFlags::empty(), )) @@ -6660,8 +6844,9 @@ mod tests { ); } - #[test_case(feature_set_old_behavior(); "old_behavior")] - #[test_case(feature_set_new_behavior(); "new_behavior")] + #[test_case(feature_set_old_warmup_cooldown_no_minimum_delegation(); "old_warmup_cooldown_no_min_delegation")] + #[test_case(feature_set_old_warmup_cooldown(); "old_warmup_cooldown")] + #[test_case(feature_set_all_enabled(); "all_enabled")] fn test_redelegate(feature_set: Arc) { let feature_set = Arc::new(feature_set); @@ -6701,7 +6886,6 @@ mod tests { &vote_address, &VoteState::default(), activation_epoch, - &stake_config::Config::default(), ), StakeFlags::empty(), ); @@ -6712,7 +6896,11 @@ mod tests { initial_stake_state .delegation() .unwrap() - .stake_activating_and_deactivating(current_epoch, Some(&stake_history)) + .stake_activating_and_deactivating( + current_epoch, + Some(&stake_history), + None + ) ); } @@ -6742,6 +6930,7 @@ mod tests { uninitialized_stake_address: &Pubkey, uninitialized_stake_account: &AccountSharedData, expected_result| { + #[allow(deprecated)] process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::Redelegate).unwrap(), @@ -6769,6 +6958,10 @@ mod tests { ..Clock::default() }), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ], vec![ AccountMeta { @@ -6911,6 +7104,7 @@ mod tests { ), ]; for (deactivated_stake_account, expected_result) in deactivated_stake_accounts { + #[allow(deprecated)] process_instruction( Arc::clone(&feature_set), &serialize(&StakeInstruction::DelegateStake).unwrap(), @@ -6934,6 +7128,10 @@ mod tests { ..Clock::default() }), ), + ( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + ), ], vec![ AccountMeta { @@ -7191,9 +7389,11 @@ mod tests { activating: 0, deactivating: minimum_delegation + rent_exempt_reserve, }, - stake - .delegation - .stake_activating_and_deactivating(current_epoch, Some(&stake_history)) + stake.delegation.stake_activating_and_deactivating( + current_epoch, + Some(&stake_history), + None + ) ); deactivating_stake_account diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 448a4d811f..eaf7b2bff0 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -15,14 +15,14 @@ use { account_utils::StateMut, clock::{Clock, Epoch}, feature_set::{ - self, clean_up_delegation_errors, stake_merge_with_unmatched_credits_observed, - FeatureSet, + self, clean_up_delegation_errors, + reduce_stake_warmup_cooldown::NewWarmupCooldownRateEpoch, + stake_merge_with_unmatched_credits_observed, FeatureSet, }, instruction::{checked_add, InstructionError}, pubkey::Pubkey, rent::Rent, stake::{ - config::Config, instruction::{LockupArgs, StakeError}, program::id, stake_flags::StakeFlags, @@ -97,6 +97,16 @@ pub fn meta_from(account: &AccountSharedData) -> Option { from(account).and_then(|state: StakeState| state.meta()) } +pub(crate) fn new_warmup_cooldown_rate_epoch(invoke_context: &InvokeContext) -> Option { + let epoch_schedule = invoke_context + .get_sysvar_cache() + .get_epoch_schedule() + .unwrap(); + invoke_context + .feature_set + .new_warmup_cooldown_rate_epoch(epoch_schedule.as_ref()) +} + fn redelegate_stake( invoke_context: &InvokeContext, stake: &mut Stake, @@ -105,10 +115,10 @@ fn redelegate_stake( vote_state: &VoteState, clock: &Clock, stake_history: &StakeHistory, - config: &Config, ) -> Result<(), StakeError> { + let new_rate_activation_epoch = new_warmup_cooldown_rate_epoch(invoke_context); // If stake is currently active: - if stake.stake(clock.epoch, Some(stake_history)) != 0 { + if stake.stake(clock.epoch, Some(stake_history), new_rate_activation_epoch) != 0 { let stake_lamports_ok = if invoke_context .feature_set .is_active(&feature_set::stake_redelegate_instruction::id()) @@ -144,7 +154,6 @@ fn redelegate_stake( stake.delegation.activation_epoch = clock.epoch; stake.delegation.deactivation_epoch = std::u64::MAX; stake.delegation.voter_pubkey = *voter_pubkey; - stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate; stake.credits_observed = vote_state.credits(); Ok(()) } @@ -154,15 +163,9 @@ pub(crate) fn new_stake( voter_pubkey: &Pubkey, vote_state: &VoteState, activation_epoch: Epoch, - config: &Config, ) -> Stake { Stake { - delegation: Delegation::new( - voter_pubkey, - stake, - activation_epoch, - config.warmup_cooldown_rate, - ), + delegation: Delegation::new(voter_pubkey, stake, activation_epoch), credits_observed: vote_state.credits(), } } @@ -184,6 +187,7 @@ fn redeem_stake_rewards( vote_state: &VoteState, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, ) -> Option<(u64, u64)> { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( @@ -198,6 +202,7 @@ fn redeem_stake_rewards( vote_state, stake_history, inflation_point_calc_tracer.as_ref(), + new_rate_activation_epoch, ) .map(|calculated_stake_rewards| { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { @@ -220,12 +225,14 @@ fn calculate_stake_points( vote_state: &VoteState, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, ) -> u128 { calculate_stake_points_and_credits( stake, vote_state, stake_history, inflation_point_calc_tracer, + new_rate_activation_epoch, ) .points } @@ -245,6 +252,7 @@ fn calculate_stake_points_and_credits( new_vote_state: &VoteState, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, ) -> CalculatedStakePoints { let credits_in_stake = stake.credits_observed; let credits_in_vote = new_vote_state.credits(); @@ -298,7 +306,11 @@ fn calculate_stake_points_and_credits( for (epoch, final_epoch_credits, initial_epoch_credits) in new_vote_state.epoch_credits().iter().copied() { - let stake_amount = u128::from(stake.delegation.stake(epoch, stake_history)); + let stake_amount = u128::from(stake.delegation.stake( + epoch, + stake_history, + new_rate_activation_epoch, + )); // figure out how much this stake has seen that // for which the vote account has a record @@ -359,6 +371,7 @@ fn calculate_stake_rewards( vote_state: &VoteState, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, ) -> Option { // ensure to run to trigger (optional) inflation_point_calc_tracer let CalculatedStakePoints { @@ -370,6 +383,7 @@ fn calculate_stake_rewards( vote_state, stake_history, inflation_point_calc_tracer.as_ref(), + new_rate_activation_epoch, ); // Drive credits_observed forward unconditionally when rewards are disabled @@ -564,7 +578,6 @@ pub fn delegate( vote_account_index: IndexOfAccount, clock: &Clock, stake_history: &StakeHistory, - config: &Config, signers: &HashSet, feature_set: &FeatureSet, ) -> Result<(), InstructionError> { @@ -589,7 +602,6 @@ pub fn delegate( &vote_pubkey, &vote_state?.convert_to_current(), clock.epoch, - config, ); stake_account.set_state(&StakeState::Stake(meta, stake, StakeFlags::empty())) } @@ -605,7 +617,6 @@ pub fn delegate( &vote_state?.convert_to_current(), clock, stake_history, - config, )?; stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags)) } @@ -875,7 +886,6 @@ pub fn redelegate( stake_account: &mut BorrowedAccount, uninitialized_stake_account_index: IndexOfAccount, vote_account_index: IndexOfAccount, - config: &Config, signers: &HashSet, ) -> Result<(), InstructionError> { let clock = invoke_context.get_sysvar_cache().get_clock()?; @@ -930,9 +940,11 @@ pub fn redelegate( let (stake_meta, effective_stake) = if let StakeState::Stake(meta, stake, _stake_flags) = stake_account.get_state()? { let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?; - let status = stake - .delegation - .stake_activating_and_deactivating(clock.epoch, Some(&stake_history)); + let status = stake.delegation.stake_activating_and_deactivating( + clock.epoch, + Some(&stake_history), + new_warmup_cooldown_rate_epoch(invoke_context), + ); if status.effective == 0 || status.activating != 0 || status.deactivating != 0 { ic_msg!(invoke_context, "stake is not active"); return Err(StakeError::RedelegateTransientOrInactiveStake.into()); @@ -982,7 +994,6 @@ pub fn redelegate( &vote_pubkey, &vote_state.convert_to_current(), clock.epoch, - config, ), StakeFlags::empty(), ))?; @@ -1001,6 +1012,7 @@ pub fn withdraw( stake_history: &StakeHistory, withdraw_authority_index: IndexOfAccount, custodian_index: Option, + new_rate_activation_epoch: Option, ) -> Result<(), InstructionError> { let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index( instruction_context @@ -1020,7 +1032,9 @@ pub fn withdraw( .check(&signers, StakeAuthorize::Withdrawer)?; // if we have a deactivation epoch and we're in cooldown let staked = if clock.epoch >= stake.delegation.deactivation_epoch { - stake.delegation.stake(clock.epoch, Some(stake_history)) + stake + .delegation + .stake(clock.epoch, Some(stake_history), new_rate_activation_epoch) } else { // Assume full stake if the stake account hasn't been // de-activated, because in the future the exposed stake @@ -1307,9 +1321,11 @@ impl MergeKind { StakeState::Stake(meta, stake, stake_flags) => { // stake must not be in a transient state. Transient here meaning // activating or deactivating with non-zero effective stake. - let status = stake - .delegation - .stake_activating_and_deactivating(clock.epoch, Some(stake_history)); + let status = stake.delegation.stake_activating_and_deactivating( + clock.epoch, + Some(stake_history), + new_warmup_cooldown_rate_epoch(invoke_context), + ); match (status.effective, status.activating, status.deactivating) { (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports, *stake_flags)), @@ -1359,9 +1375,7 @@ impl MergeKind { if stake.voter_pubkey != source.voter_pubkey { ic_msg!(invoke_context, "Unable to merge due to voter mismatch"); Err(StakeError::MergeMismatch.into()) - } else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON - && stake.deactivation_epoch == Epoch::MAX - && source.deactivation_epoch == Epoch::MAX + } else if stake.deactivation_epoch == Epoch::MAX && source.deactivation_epoch == Epoch::MAX { Ok(()) } else { @@ -1547,13 +1561,16 @@ pub fn redeem_rewards( point_value: &PointValue, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, ) -> Result<(u64, u64), InstructionError> { if let StakeState::Stake(meta, mut stake, stake_flags) = stake_state { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { inflation_point_calc_tracer( - &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch( - stake.stake(rewarded_epoch, stake_history), - ), + &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake( + rewarded_epoch, + stake_history, + new_rate_activation_epoch, + )), ); inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve( meta.rent_exempt_reserve, @@ -1570,6 +1587,7 @@ pub fn redeem_rewards( vote_state, stake_history, inflation_point_calc_tracer, + new_rate_activation_epoch, ) { stake_account.checked_add_lamports(stakers_reward)?; stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags))?; @@ -1589,6 +1607,7 @@ pub fn calculate_points( stake_state: &StakeState, vote_state: &VoteState, stake_history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, ) -> Result { if let StakeState::Stake(_meta, stake, _stake_flags) = stake_state { Ok(calculate_stake_points( @@ -1596,6 +1615,7 @@ pub fn calculate_points( vote_state, stake_history, null_tracer(), + new_rate_activation_epoch, )) } else { Err(InstructionError::InvalidAccountData) @@ -1609,12 +1629,13 @@ pub fn new_stake_history_entry<'a, I>( epoch: Epoch, stakes: I, history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, ) -> StakeHistoryEntry where I: Iterator, { stakes.fold(StakeHistoryEntry::default(), |sum, stake| { - sum + stake.stake_activating_and_deactivating(epoch, history) + sum + stake.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch) }) } @@ -1623,6 +1644,7 @@ pub fn create_stake_history_from_delegations( bootstrap: Option, epochs: std::ops::Range, delegations: &[Delegation], + new_rate_activation_epoch: Option, ) -> StakeHistory { let mut stake_history = StakeHistory::default(); @@ -1641,6 +1663,7 @@ pub fn create_stake_history_from_delegations( epoch, delegations.iter().chain(bootstrap_delegation.iter()), Some(&stake_history), + new_rate_activation_epoch, ); stake_history.add(epoch, entry); } @@ -1737,7 +1760,6 @@ fn do_create_account( voter_pubkey, &vote_state, activation_epoch, - &Config::default(), ), StakeFlags::empty(), )) @@ -1754,10 +1776,13 @@ mod tests { solana_program_runtime::with_mock_invoke_context, solana_sdk::{ account::{create_account_shared_data_for_test, AccountSharedData}, + epoch_schedule::EpochSchedule, native_token, pubkey::Pubkey, - sysvar::SysvarId, + stake::state::warmup_cooldown_rate, + sysvar::{epoch_schedule, SysvarId}, }, + test_case::test_case, }; #[test] @@ -1933,24 +1958,31 @@ mod tests { }; // save this off so stake.config.warmup_rate changes don't break this test - let increment = (1_000_f64 * stake.warmup_cooldown_rate) as u64; + let increment = (1_000_f64 * warmup_cooldown_rate(0, None)) as u64; let mut stake_history = StakeHistory::default(); // assert that this stake follows step function if there's no history assert_eq!( - stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history),), + stake.stake_activating_and_deactivating( + stake.activation_epoch, + Some(&stake_history), + None + ), StakeActivationStatus::with_effective_and_activating(0, stake.stake), ); for epoch in stake.activation_epoch + 1..stake.deactivation_epoch { assert_eq!( - stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), + stake.stake_activating_and_deactivating(epoch, Some(&stake_history), None), StakeActivationStatus::with_effective(stake.stake), ); } // assert that this stake is full deactivating assert_eq!( - stake - .stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history),), + stake.stake_activating_and_deactivating( + stake.deactivation_epoch, + Some(&stake_history), + None + ), StakeActivationStatus::with_deactivating(stake.stake), ); // assert that this stake is fully deactivated if there's no history @@ -1958,6 +1990,7 @@ mod tests { stake.stake_activating_and_deactivating( stake.deactivation_epoch + 1, Some(&stake_history), + None ), StakeActivationStatus::default(), ); @@ -1971,7 +2004,7 @@ mod tests { ); // assert that this stake is broken, because above setup is broken assert_eq!( - stake.stake_activating_and_deactivating(1, Some(&stake_history)), + stake.stake_activating_and_deactivating(1, Some(&stake_history), None), StakeActivationStatus::with_effective_and_activating(0, stake.stake), ); @@ -1986,7 +2019,7 @@ mod tests { ); // assert that this stake is broken, because above setup is broken assert_eq!( - stake.stake_activating_and_deactivating(2, Some(&stake_history)), + stake.stake_activating_and_deactivating(2, Some(&stake_history), None), StakeActivationStatus::with_effective_and_activating( increment, stake.stake - increment @@ -2008,6 +2041,7 @@ mod tests { stake.stake_activating_and_deactivating( stake.deactivation_epoch + 1, Some(&stake_history), + None, ), StakeActivationStatus::with_deactivating(stake.stake), ); @@ -2026,6 +2060,7 @@ mod tests { stake.stake_activating_and_deactivating( stake.deactivation_epoch + 2, Some(&stake_history), + None, ), // hung, should be lower StakeActivationStatus::with_deactivating(stake.stake - increment), @@ -2092,8 +2127,11 @@ mod tests { assert_eq!( expected_stakes, (0..expected_stakes.len()) - .map(|epoch| stake - .stake_activating_and_deactivating(epoch as u64, Some(&stake_history),)) + .map(|epoch| stake.stake_activating_and_deactivating( + epoch as u64, + Some(&stake_history), + None, + )) .collect::>() ); } @@ -2220,7 +2258,11 @@ mod tests { let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> { (0..epoch_count) .map(|epoch| { - stake.stake_activating_and_deactivating(epoch as u64, Some(&stake_history)) + stake.stake_activating_and_deactivating( + epoch as u64, + Some(&stake_history), + None, + ) }) .collect::>() }; @@ -2319,7 +2361,7 @@ mod tests { }, ); - let effective_rate_limited = (effective as f64 * stake.warmup_cooldown_rate) as u64; + let effective_rate_limited = (effective as f64 * warmup_cooldown_rate(0, None)) as u64; if epoch < stake.deactivation_epoch { effective += effective_rate_limited.min(activating); other_activations.push(0); @@ -2340,7 +2382,7 @@ mod tests { (0, history.deactivating) }; assert_eq!( - stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), + stake.stake_activating_and_deactivating(epoch, Some(&stake_history), None), StakeActivationStatus { effective: expected_stake, activating: expected_activating, @@ -2362,16 +2404,16 @@ mod tests { let epochs = 7; // make boostrap stake smaller than warmup so warmup/cooldownn // increment is always smaller than 1 - let bootstrap = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64; + let bootstrap = (warmup_cooldown_rate(0, None) * 100.0 / 2.0) as u64; let stake_history = - create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations); + create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations, None); let mut max_stake = 0; let mut min_stake = 2; for epoch in 0..epochs { let stake = delegations .iter() - .map(|delegation| delegation.stake(epoch, Some(&stake_history))) + .map(|delegation| delegation.stake(epoch, Some(&stake_history), None)) .sum::(); max_stake = max_stake.max(stake); min_stake = min_stake.min(stake); @@ -2380,8 +2422,13 @@ mod tests { assert_eq!(min_stake, 0); } - #[test] - fn test_stake_warmup_cooldown() { + #[test_case(None ; "old rate")] + #[test_case(Some(1) ; "new rate activated in epoch 1")] + #[test_case(Some(10) ; "new rate activated in epoch 10")] + #[test_case(Some(30) ; "new rate activated in epoch 30")] + #[test_case(Some(50) ; "new rate activated in epoch 50")] + #[test_case(Some(60) ; "new rate activated in epoch 60")] + fn test_stake_warmup_cooldown(new_rate_activation_epoch: Option) { let delegations = [ Delegation { // never deactivates @@ -2424,13 +2471,18 @@ mod tests { // warming up and cooling down // a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down // when all alone, but the above overlap a lot - let epochs = 20; + let epochs = 60; - let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations); + let stake_history = create_stake_history_from_delegations( + None, + 0..epochs, + &delegations, + new_rate_activation_epoch, + ); let mut prev_total_effective_stake = delegations .iter() - .map(|delegation| delegation.stake(0, Some(&stake_history))) + .map(|delegation| delegation.stake(0, Some(&stake_history), new_rate_activation_epoch)) .sum::(); // uncomment and add ! for fun with graphing @@ -2438,7 +2490,9 @@ mod tests { for epoch in 1..epochs { let total_effective_stake = delegations .iter() - .map(|delegation| delegation.stake(epoch, Some(&stake_history))) + .map(|delegation| { + delegation.stake(epoch, Some(&stake_history), new_rate_activation_epoch) + }) .sum::(); let delta = if total_effective_stake > prev_total_effective_stake { @@ -2448,13 +2502,15 @@ mod tests { }; // uncomment and add ! for fun with graphing - //eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta); - //(0..(total_effective_stake as usize / (stakes.len() * 5))).for_each(|_| eprint("#")); - //eprintln(); + // eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta); + // (0..(total_effective_stake as usize / (delegations.len() * 5))).for_each(|_| eprint("#")); + // eprintln(); assert!( delta - <= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64) + <= ((prev_total_effective_stake as f64 + * warmup_cooldown_rate(epoch, new_rate_activation_epoch)) + as u64) .max(1) ); @@ -2473,7 +2529,6 @@ mod tests { &Pubkey::default(), &vote_state, std::u64::MAX, - &Config::default(), ); // this one can't collect now, credits_observed == vote_state.credits() @@ -2489,6 +2544,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2509,6 +2565,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2530,7 +2587,6 @@ mod tests { &Pubkey::default(), &vote_state, std::u64::MAX, - &Config::default(), ); // this one can't collect now, credits_observed == vote_state.credits() @@ -2546,6 +2602,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2559,7 +2616,7 @@ mod tests { // no overflow on points assert_eq!( u128::from(stake.delegation.stake) * epoch_slots, - calculate_stake_points(&stake, &vote_state, None, null_tracer()) + calculate_stake_points(&stake, &vote_state, None, null_tracer(), None) ); } @@ -2568,13 +2625,7 @@ mod tests { let mut vote_state = VoteState::default(); // assume stake.stake() is right // bootstrap means fully-vested stake at epoch 0 - let mut stake = new_stake( - 1, - &Pubkey::default(), - &vote_state, - std::u64::MAX, - &Config::default(), - ); + let mut stake = new_stake(1, &Pubkey::default(), &vote_state, std::u64::MAX); // this one can't collect now, credits_observed == vote_state.credits() assert_eq!( @@ -2589,6 +2640,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2613,6 +2665,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2634,6 +2687,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2658,6 +2712,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2680,6 +2735,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2704,6 +2760,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2722,6 +2779,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); vote_state.commission = 99; @@ -2737,6 +2795,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2759,6 +2818,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2781,6 +2841,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2790,7 +2851,7 @@ mod tests { new_credits_observed: 4, force_credits_update_with_skipped_reward: false, }, - calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) + calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None) ); // credits_observed is auto-rewinded when vote_state credits are assumed to have been @@ -2803,7 +2864,7 @@ mod tests { new_credits_observed: 4, force_credits_update_with_skipped_reward: true, }, - calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) + calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None) ); // this is new behavior 2; don't hint when credits both from stake and vote are identical stake.credits_observed = 4; @@ -2813,7 +2874,7 @@ mod tests { new_credits_observed: 4, force_credits_update_with_skipped_reward: false, }, - calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) + calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None) ); // get rewards and credits observed when not the activation epoch @@ -2836,6 +2897,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); @@ -2859,6 +2921,7 @@ mod tests { &vote_state, None, null_tracer(), + None, ) ); } @@ -2993,23 +3056,6 @@ mod tests { ) .is_err()); - let bad_warmup_cooldown_rate = Delegation { - warmup_cooldown_rate: good_delegation.warmup_cooldown_rate + f64::EPSILON, - ..good_delegation - }; - assert!(MergeKind::active_delegations_can_merge( - &invoke_context, - &good_delegation, - &bad_warmup_cooldown_rate - ) - .is_err()); - assert!(MergeKind::active_delegations_can_merge( - &invoke_context, - &bad_warmup_cooldown_rate, - &good_delegation - ) - .is_err()); - let bad_deactivation_epoch = Delegation { deactivation_epoch: 43, ..good_delegation @@ -3176,12 +3222,17 @@ mod tests { #[test] fn test_merge_kind_get_if_mergeable() { - with_mock_invoke_context!(invoke_context, transaction_context, Vec::new()); + let transaction_accounts = vec![( + epoch_schedule::id(), + create_account_shared_data_for_test(&EpochSchedule::default()), + )]; + with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let authority_pubkey = Pubkey::new_unique(); let initial_lamports = 4242424242; let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); let stake_lamports = rent_exempt_reserve + initial_lamports; + let new_rate_activation_epoch = Some(0); let meta = Meta { rent_exempt_reserve, @@ -3268,7 +3319,7 @@ mod tests { delegation: Delegation { stake: initial_lamports, activation_epoch: 1, - deactivation_epoch: 5, + deactivation_epoch: 9, ..Delegation::default() }, ..Stake::default() @@ -3292,8 +3343,10 @@ mod tests { // all paritially activated, transient epochs fail loop { clock.epoch += 1; - let delta = - activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + let delta = activating.min( + (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch)) + as u64, + ); effective += delta; activating -= delta; stake_history.add( @@ -3370,8 +3423,10 @@ mod tests { // all transient, deactivating epochs fail loop { clock.epoch += 1; - let delta = - deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + let delta = deactivating.min( + (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch)) + as u64, + ); effective -= delta; deactivating -= delta; stake_history.add( diff --git a/rpc-client/src/rpc_client.rs b/rpc-client/src/rpc_client.rs index ce6d012e69..3e60381a41 100644 --- a/rpc-client/src/rpc_client.rs +++ b/rpc-client/src/rpc_client.rs @@ -28,17 +28,18 @@ use { UiAccount, UiAccountEncoding, }, solana_rpc_client_api::{ - client_error::Result as ClientResult, + client_error::{Error as ClientError, ErrorKind, Result as ClientResult}, config::{RpcAccountInfoConfig, *}, request::{RpcRequest, TokenAccountsFilter}, response::*, }, solana_sdk::{ - account::Account, + account::{Account, ReadableAccount}, clock::{Epoch, Slot, UnixTimestamp}, commitment_config::CommitmentConfig, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, + feature::Feature, fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, message::{v0, Message as LegacyMessage}, @@ -4027,6 +4028,18 @@ impl RpcClient { (self.rpc_client.as_ref()).get_transport_stats() } + pub fn get_feature_activation_slot(&self, feature_id: &Pubkey) -> ClientResult> { + self.get_account(feature_id) + .and_then(|feature_account| { + bincode::deserialize(feature_account.data()).map_err(|_| { + ClientError::from(ErrorKind::Custom( + "Failed to deserialize feature account".to_string(), + )) + }) + }) + .map(|feature: Feature| feature.activated_at) + } + fn invoke>>(&self, f: F) -> ClientResult { // `block_on()` panics if called within an asynchronous execution context. Whereas // `block_in_place()` only panics if called from a current_thread runtime, which is the diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 67e1afb535..debbbb78fc 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -1760,12 +1760,17 @@ impl JsonRpcRequestProcessor { let stake_history = solana_sdk::account::from_account::(&stake_history_account) .ok_or_else(Error::internal_error)?; + let new_rate_activation_epoch = bank.new_warmup_cooldown_rate_epoch(); let StakeActivationStatus { effective, activating, deactivating, - } = delegation.stake_activating_and_deactivating(epoch, Some(&stake_history)); + } = delegation.stake_activating_and_deactivating( + epoch, + Some(&stake_history), + new_rate_activation_epoch, + ); let stake_activation_state = if deactivating > 0 { StakeActivationState::Deactivating } else if activating > 0 { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 7d4fb0eab5..6d3632be7a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -135,6 +135,7 @@ use { self, add_set_tx_loaded_accounts_data_size_instruction, enable_early_verification_of_account_modifications, include_loaded_accounts_data_size_in_fee_calculation, + reduce_stake_warmup_cooldown::NewWarmupCooldownRateEpoch, remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix, FeatureSet, }, @@ -1525,6 +1526,12 @@ impl Bank { new } + /// Epoch in which the new cooldown warmup rate for stake was activated + pub fn new_warmup_cooldown_rate_epoch(&self) -> Option { + self.feature_set + .new_warmup_cooldown_rate_epoch(&self.epoch_schedule) + } + /// process for the start of a new epoch fn process_new_epoch( &mut self, @@ -1549,7 +1556,11 @@ impl Bank { // update vote accounts with warmed up stakes before saving a // snapshot of stakes in epoch stakes let (_, activate_epoch_time) = measure!( - self.stakes_cache.activate_epoch(epoch, &thread_pool), + self.stakes_cache.activate_epoch( + epoch, + &thread_pool, + self.new_warmup_cooldown_rate_epoch() + ), "activate_epoch", ); @@ -3010,6 +3021,7 @@ impl Bank { stake_account.stake_state(), vote_state, Some(stake_history), + self.new_warmup_cooldown_rate_epoch(), ) .unwrap_or(0) }) @@ -3045,6 +3057,7 @@ impl Bank { stake_account.stake_state(), vote_state, Some(stake_history), + self.new_warmup_cooldown_rate_epoch(), ) .unwrap_or(0) }) @@ -3131,6 +3144,7 @@ impl Bank { &point_value, Some(stake_history), reward_calc_tracer.as_ref(), + self.new_warmup_cooldown_rate_epoch(), ); let post_lamport = stake_account.lamports(); @@ -3248,6 +3262,7 @@ impl Bank { &point_value, Some(stake_history), reward_calc_tracer.as_ref(), + self.new_warmup_cooldown_rate_epoch(), ); if let Ok((stakers_reward, voters_reward)) = redeemed { // track voter rewards @@ -3298,8 +3313,11 @@ impl Bank { let now = Instant::now(); let slot = self.slot(); let include_slot_in_hash = self.include_slot_in_hash(); - self.stakes_cache - .update_stake_accounts(thread_pool, stake_rewards); + self.stakes_cache.update_stake_accounts( + thread_pool, + stake_rewards, + self.new_warmup_cooldown_rate_epoch(), + ); assert!(!self.freeze_started()); thread_pool.install(|| { stake_rewards.par_chunks(512).for_each(|chunk| { @@ -6518,8 +6536,11 @@ impl Bank { assert!(!self.freeze_started()); let mut m = Measure::start("stakes_cache.check_and_store"); (0..accounts.len()).for_each(|i| { - self.stakes_cache - .check_and_store(accounts.pubkey(i), accounts.account(i)) + self.stakes_cache.check_and_store( + accounts.pubkey(i), + accounts.account(i), + self.new_warmup_cooldown_rate_epoch(), + ) }); self.rc.accounts.store_accounts_cached(accounts); m.stop(); @@ -7637,7 +7658,11 @@ impl Bank { .for_each(|(pubkey, account)| { // note that this could get timed to: self.rc.accounts.accounts_db.stats.stakes_cache_check_and_store_us, // but this code path is captured separately in ExecuteTimingType::UpdateStakesCacheUs - self.stakes_cache.check_and_store(pubkey, account); + self.stakes_cache.check_and_store( + pubkey, + account, + self.new_warmup_cooldown_rate_epoch(), + ); }); } diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 2e5fa95df7..5d3a1c2151 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -4152,7 +4152,7 @@ fn test_bank_epoch_vote_accounts() { // epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary // in the prior epoch (0 in this case) assert_eq!( - leader_stake.stake(0, None), + leader_stake.stake(0, None, None), vote_accounts.unwrap().get(&leader_vote_account).unwrap().0 ); @@ -4168,7 +4168,7 @@ fn test_bank_epoch_vote_accounts() { assert!(child.epoch_vote_accounts(epoch).is_some()); assert_eq!( - leader_stake.stake(child.epoch(), None), + leader_stake.stake(child.epoch(), None, None), child .epoch_vote_accounts(epoch) .unwrap() @@ -4186,7 +4186,7 @@ fn test_bank_epoch_vote_accounts() { ); assert!(child.epoch_vote_accounts(epoch).is_some()); assert_eq!( - leader_stake.stake(child.epoch(), None), + leader_stake.stake(child.epoch(), None, None), child .epoch_vote_accounts(epoch) .unwrap() diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 31adac6cfc..c83da33d13 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -65,7 +65,12 @@ impl StakesCache { self.0.read().unwrap() } - pub(crate) fn check_and_store(&self, pubkey: &Pubkey, account: &impl ReadableAccount) { + pub(crate) fn check_and_store( + &self, + pubkey: &Pubkey, + account: &impl ReadableAccount, + new_rate_activation_epoch: Option, + ) { // TODO: If the account is already cached as a vote or stake account // but the owner changes, then this needs to evict the account from // the cache. see: @@ -79,7 +84,7 @@ impl StakesCache { stakes.remove_vote_account(pubkey); } else if solana_stake_program::check_id(owner) { let mut stakes = self.0.write().unwrap(); - stakes.remove_stake_delegation(pubkey); + stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch); } return; } @@ -93,7 +98,7 @@ impl StakesCache { let _res = vote_account.vote_state(); } let mut stakes = self.0.write().unwrap(); - stakes.upsert_vote_account(pubkey, vote_account); + stakes.upsert_vote_account(pubkey, vote_account, new_rate_activation_epoch); } Err(_) => { let mut stakes = self.0.write().unwrap(); @@ -108,30 +113,41 @@ impl StakesCache { match StakeAccount::try_from(account.to_account_shared_data()) { Ok(stake_account) => { let mut stakes = self.0.write().unwrap(); - stakes.upsert_stake_delegation(*pubkey, stake_account); + stakes.upsert_stake_delegation( + *pubkey, + stake_account, + new_rate_activation_epoch, + ); } Err(_) => { let mut stakes = self.0.write().unwrap(); - stakes.remove_stake_delegation(pubkey); + stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch); } } } } - pub(crate) fn activate_epoch(&self, next_epoch: Epoch, thread_pool: &ThreadPool) { + pub(crate) fn activate_epoch( + &self, + next_epoch: Epoch, + thread_pool: &ThreadPool, + new_rate_activation_epoch: Option, + ) { let mut stakes = self.0.write().unwrap(); - stakes.activate_epoch(next_epoch, thread_pool) + stakes.activate_epoch(next_epoch, thread_pool, new_rate_activation_epoch) } pub(crate) fn update_stake_accounts( &self, thread_pool: &ThreadPool, stake_rewards: &[StakeReward], + new_rate_activation_epoch: Option, ) { - self.0 - .write() - .unwrap() - .update_stake_accounts(thread_pool, stake_rewards) + self.0.write().unwrap().update_stake_accounts( + thread_pool, + stake_rewards, + new_rate_activation_epoch, + ) } pub(crate) fn handle_invalid_keys( @@ -272,7 +288,12 @@ impl Stakes { &self.stake_history } - fn activate_epoch(&mut self, next_epoch: Epoch, thread_pool: &ThreadPool) { + fn activate_epoch( + &mut self, + next_epoch: Epoch, + thread_pool: &ThreadPool, + new_rate_activation_epoch: Option, + ) { let stake_delegations: Vec<_> = self.stake_delegations.values().collect(); // Wrap up the prev epoch by adding new stake history entry for the // prev epoch. @@ -281,8 +302,11 @@ impl Stakes { .par_iter() .fold(StakeActivationStatus::default, |acc, stake_account| { let delegation = stake_account.delegation(); - acc + delegation - .stake_activating_and_deactivating(self.epoch, Some(&self.stake_history)) + acc + delegation.stake_activating_and_deactivating( + self.epoch, + Some(&self.stake_history), + new_rate_activation_epoch, + ) }) .reduce(StakeActivationStatus::default, Add::add) }); @@ -296,6 +320,7 @@ impl Stakes { &self.vote_accounts, &stake_delegations, &self.stake_history, + new_rate_activation_epoch, ); } @@ -305,12 +330,15 @@ impl Stakes { voter_pubkey: &Pubkey, epoch: Epoch, stake_history: &StakeHistory, + new_rate_activation_epoch: Option, ) -> u64 { self.stake_delegations .values() .map(StakeAccount::delegation) .filter(|delegation| &delegation.voter_pubkey == voter_pubkey) - .map(|delegation| delegation.stake(epoch, Some(stake_history))) + .map(|delegation| { + delegation.stake(epoch, Some(stake_history), new_rate_activation_epoch) + }) .sum() } @@ -327,40 +355,71 @@ impl Stakes { self.vote_accounts.remove(vote_pubkey); } - fn remove_stake_delegation(&mut self, stake_pubkey: &Pubkey) { + fn remove_stake_delegation( + &mut self, + stake_pubkey: &Pubkey, + new_rate_activation_epoch: Option, + ) { if let Some(stake_account) = self.stake_delegations.remove(stake_pubkey) { let removed_delegation = stake_account.delegation(); - let removed_stake = removed_delegation.stake(self.epoch, Some(&self.stake_history)); + let removed_stake = removed_delegation.stake( + self.epoch, + Some(&self.stake_history), + new_rate_activation_epoch, + ); self.vote_accounts .sub_stake(&removed_delegation.voter_pubkey, removed_stake); } } - fn upsert_vote_account(&mut self, vote_pubkey: &Pubkey, vote_account: VoteAccount) { + fn upsert_vote_account( + &mut self, + vote_pubkey: &Pubkey, + vote_account: VoteAccount, + new_rate_activation_epoch: Option, + ) { debug_assert_ne!(vote_account.lamports(), 0u64); debug_assert!(vote_account.is_deserialized()); // unconditionally remove existing at first; there is no dependent calculated state for // votes, not like stakes (stake codepath maintains calculated stake value grouped by // delegated vote pubkey) let stake = match self.vote_accounts.remove(vote_pubkey) { - None => self.calculate_stake(vote_pubkey, self.epoch, &self.stake_history), + None => self.calculate_stake( + vote_pubkey, + self.epoch, + &self.stake_history, + new_rate_activation_epoch, + ), Some((stake, _)) => stake, }; let entry = (stake, vote_account); self.vote_accounts.insert(*vote_pubkey, entry); } - fn upsert_stake_delegation(&mut self, stake_pubkey: Pubkey, stake_account: StakeAccount) { + fn upsert_stake_delegation( + &mut self, + stake_pubkey: Pubkey, + stake_account: StakeAccount, + new_rate_activation_epoch: Option, + ) { debug_assert_ne!(stake_account.lamports(), 0u64); let delegation = stake_account.delegation(); let voter_pubkey = delegation.voter_pubkey; - let stake = delegation.stake(self.epoch, Some(&self.stake_history)); + let stake = delegation.stake( + self.epoch, + Some(&self.stake_history), + new_rate_activation_epoch, + ); match self.stake_delegations.insert(stake_pubkey, stake_account) { None => self.vote_accounts.add_stake(&voter_pubkey, stake), Some(old_stake_account) => { let old_delegation = old_stake_account.delegation(); let old_voter_pubkey = old_delegation.voter_pubkey; - let old_stake = old_delegation.stake(self.epoch, Some(&self.stake_history)); + let old_stake = old_delegation.stake( + self.epoch, + Some(&self.stake_history), + new_rate_activation_epoch, + ); if voter_pubkey != old_voter_pubkey || stake != old_stake { self.vote_accounts.sub_stake(&old_voter_pubkey, old_stake); self.vote_accounts.add_stake(&voter_pubkey, stake); @@ -369,7 +428,12 @@ impl Stakes { } } - fn update_stake_accounts(&mut self, thread_pool: &ThreadPool, stake_rewards: &[StakeReward]) { + fn update_stake_accounts( + &mut self, + thread_pool: &ThreadPool, + stake_rewards: &[StakeReward], + new_rate_activation_epoch: Option, + ) { let stake_delegations: Vec<_> = thread_pool.install(|| { stake_rewards .into_par_iter() @@ -393,6 +457,7 @@ impl Stakes { &self.vote_accounts, &stake_delegations, &self.stake_history, + new_rate_activation_epoch, ); } @@ -509,6 +574,7 @@ fn refresh_vote_accounts( vote_accounts: &VoteAccounts, stake_delegations: &[&StakeAccount], stake_history: &StakeHistory, + new_rate_activation_epoch: Option, ) -> VoteAccounts { type StakesHashMap = HashMap; fn merge(mut stakes: StakesHashMap, other: StakesHashMap) -> StakesHashMap { @@ -527,7 +593,7 @@ fn refresh_vote_accounts( .fold(HashMap::default, |mut delegated_stakes, stake_account| { let delegation = stake_account.delegation(); let entry = delegated_stakes.entry(delegation.voter_pubkey).or_default(); - *entry += delegation.stake(epoch, stake_history); + *entry += delegation.stake(epoch, stake_history, new_rate_activation_epoch); delegated_stakes }) .reduce(HashMap::default, merge) @@ -631,8 +697,8 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); let stake = stake_state::stake_from(&stake_account).unwrap(); { let stakes = stakes_cache.stakes(); @@ -640,26 +706,26 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, None) + stake.stake(i, None, None) ); } stake_account.set_lamports(42); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); { let stakes = stakes_cache.stakes(); let vote_accounts = stakes.vote_accounts(); assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, None) + stake.stake(i, None, None) ); // stays old stake, because only 10 is activated } // activate more let mut stake_account = create_stake_account(42, &vote_pubkey, &solana_sdk::pubkey::new_rand()); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); let stake = stake_state::stake_from(&stake_account).unwrap(); { let stakes = stakes_cache.stakes(); @@ -667,12 +733,12 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, None) + stake.stake(i, None, None) ); // now stake of 42 is activated } stake_account.set_lamports(0); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); { let stakes = stakes_cache.stakes(); let vote_accounts = stakes.vote_accounts(); @@ -691,14 +757,14 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) = create_staked_node_accounts(20); - stakes_cache.check_and_store(&vote11_pubkey, &vote11_account); - stakes_cache.check_and_store(&stake11_pubkey, &stake11_account); + stakes_cache.check_and_store(&vote11_pubkey, &vote11_account, None); + stakes_cache.check_and_store(&stake11_pubkey, &stake11_account, None); let vote11_node_pubkey = vote_state::from(&vote11_account).unwrap().node_pubkey; @@ -716,8 +782,8 @@ pub(crate) mod tests { let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); { let stakes = stakes_cache.stakes(); @@ -727,7 +793,7 @@ pub(crate) mod tests { } vote_account.set_lamports(0); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); { let stakes = stakes_cache.stakes(); @@ -737,7 +803,7 @@ pub(crate) mod tests { } vote_account.set_lamports(1); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); { let stakes = stakes_cache.stakes(); @@ -751,7 +817,7 @@ pub(crate) mod tests { let mut pushed = vote_account.data().to_vec(); pushed.push(0); vote_account.set_data(pushed); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); { let stakes = stakes_cache.stakes(); @@ -764,7 +830,7 @@ pub(crate) mod tests { let default_vote_state = VoteState::default(); let versioned = VoteStateVersions::new_current(default_vote_state); vote_state::to(&versioned, &mut vote_account).unwrap(); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); { let stakes = stakes_cache.stakes(); @@ -774,7 +840,7 @@ pub(crate) mod tests { } vote_account.set_data(cache_data); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); { let stakes = stakes_cache.stakes(); @@ -797,11 +863,11 @@ pub(crate) mod tests { let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&vote_pubkey2, &vote_account2); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&vote_pubkey2, &vote_account2, None); // delegates to vote_pubkey - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); let stake = stake_state::stake_from(&stake_account).unwrap(); @@ -811,14 +877,14 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, Some(&stakes.stake_history)) + stake.stake(stakes.epoch, Some(&stakes.stake_history), None) ); assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey2), 0); } // delegates to vote_pubkey2 - stakes_cache.check_and_store(&stake_pubkey, &stake_account2); + stakes_cache.check_and_store(&stake_pubkey, &stake_account2, None); { let stakes = stakes_cache.stakes(); @@ -828,7 +894,7 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey2), - stake.stake(stakes.epoch, Some(&stakes.stake_history)) + stake.stake(stakes.epoch, Some(&stakes.stake_history), None) ); } } @@ -845,11 +911,11 @@ pub(crate) mod tests { let stake_pubkey2 = solana_sdk::pubkey::new_rand(); let stake_account2 = create_stake_account(10, &vote_pubkey, &stake_pubkey2); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); // delegates to vote_pubkey - stakes_cache.check_and_store(&stake_pubkey, &stake_account); - stakes_cache.check_and_store(&stake_pubkey2, &stake_account2); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store(&stake_pubkey2, &stake_account2, None); { let stakes = stakes_cache.stakes(); @@ -866,8 +932,8 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); let stake = stake_state::stake_from(&stake_account).unwrap(); { @@ -875,17 +941,17 @@ pub(crate) mod tests { let vote_accounts = stakes.vote_accounts(); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, Some(&stakes.stake_history)) + stake.stake(stakes.epoch, Some(&stakes.stake_history), None) ); } let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap(); - stakes_cache.activate_epoch(3, &thread_pool); + stakes_cache.activate_epoch(3, &thread_pool, None); { let stakes = stakes_cache.stakes(); let vote_accounts = stakes.vote_accounts(); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, Some(&stakes.stake_history)) + stake.stake(stakes.epoch, Some(&stakes.stake_history), None) ); } } @@ -900,8 +966,8 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); { let stakes = stakes_cache.stakes(); @@ -914,6 +980,7 @@ pub(crate) mod tests { stakes_cache.check_and_store( &stake_pubkey, &AccountSharedData::new(1, 0, &stake::program::id()), + None, ); { let stakes = stakes_cache.stakes(); @@ -951,8 +1018,8 @@ pub(crate) mod tests { let genesis_epoch = 0; let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_warming_staked_node_accounts(10, genesis_epoch); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); { let stakes = stakes_cache.stakes(); @@ -962,7 +1029,7 @@ pub(crate) mod tests { let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap(); for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) { - stakes_cache.activate_epoch(epoch, &thread_pool); + stakes_cache.activate_epoch(epoch, &thread_pool, None); // vote_balance_and_staked() always remain to return same lamports // while vote_balance_and_warmed_staked() gradually increases let stakes = stakes_cache.stakes(); @@ -997,7 +1064,7 @@ pub(crate) mod tests { rng.gen_range(0, 101), // commission rng.gen_range(0, 1_000_000), // lamports ); - stakes_cache.check_and_store(&vote_pubkey, &vote_account); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); for _ in 0..rng.gen_range(10usize, 20) { let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::with_slots_per_epoch(rng.gen()); @@ -1008,7 +1075,7 @@ pub(crate) mod tests { &rent, rng.gen_range(0, 1_000_000), // lamports ); - stakes_cache.check_and_store(&stake_pubkey, &stake_account); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); } } let stakes: Stakes = stakes_cache.stakes().clone(); diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index 1feacee426..b99fed712f 100755 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -1,4 +1,5 @@ #![allow(clippy::integer_arithmetic)] + use { solana_runtime::{ bank::Bank, @@ -10,6 +11,7 @@ use { account::from_account, account_utils::StateMut, client::SyncClient, + epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH}, hash::Hash, message::Message, pubkey::Pubkey, @@ -98,6 +100,7 @@ fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool { ) .unwrap(), ), + bank.new_warmup_cooldown_rate_epoch(), ) } @@ -112,6 +115,7 @@ fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 { ) .unwrap(), ), + bank.new_warmup_cooldown_rate_epoch(), ) } @@ -292,6 +296,7 @@ fn test_stake_account_lifetime() { &solana_sdk::pubkey::new_rand(), 2_000_000_000, ); + genesis_config.epoch_schedule = EpochSchedule::new(MINIMUM_SLOTS_PER_EPOCH); genesis_config.rent = Rent::default(); let bank = Bank::new_for_tests(&genesis_config); let mint_pubkey = mint_keypair.pubkey(); diff --git a/sdk/macro/src/lib.rs b/sdk/macro/src/lib.rs index bce76e2a76..f72dcdfcf8 100644 --- a/sdk/macro/src/lib.rs +++ b/sdk/macro/src/lib.rs @@ -90,8 +90,8 @@ fn deprecated_id_to_tokens( #[cfg(test)] #[test] - fn test_id() { - #[allow(deprecated)] + #[allow(deprecated)] + fn test_id() { assert!(check_id(&id())); } }); diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index b1d331331b..ded97af633 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -575,6 +575,7 @@ pub mod sdk_ids { vote::program::id(), feature::id(), bpf_loader_deprecated::id(), + #[allow(deprecated)] stake::config::id(), ]; sdk_ids.extend(sysvar::ALL_IDS.iter()); diff --git a/sdk/program/src/stake/config.rs b/sdk/program/src/stake/config.rs index 9470b2940a..cd14ea73e6 100644 --- a/sdk/program/src/stake/config.rs +++ b/sdk/program/src/stake/config.rs @@ -1,15 +1,18 @@ //! config for staking //! carries variables that the stake program cares about -use serde_derive::{Deserialize, Serialize}; + +use { + super::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE}, + serde_derive::{Deserialize, Serialize}, +}; // stake config ID -crate::declare_id!("StakeConfig11111111111111111111111111111111"); - -// means that no more than RATE of current effective stake may be added or subtracted per -// epoch -pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25; -pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8; +crate::declare_deprecated_id!("StakeConfig11111111111111111111111111111111"); +#[deprecated( + since = "1.16.7", + note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead" +)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] pub struct Config { /// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake diff --git a/sdk/program/src/stake/instruction.rs b/sdk/program/src/stake/instruction.rs index d3b703f5ad..14a3df011a 100644 --- a/sdk/program/src/stake/instruction.rs +++ b/sdk/program/src/stake/instruction.rs @@ -1,3 +1,5 @@ +#[allow(deprecated)] +use crate::stake::config; use { crate::{ clock::{Epoch, UnixTimestamp}, @@ -5,7 +7,6 @@ use { instruction::{AccountMeta, Instruction}, pubkey::Pubkey, stake::{ - config, program::id, state::{Authorized, Lockup, StakeAuthorize, StakeState}, }, @@ -676,6 +677,7 @@ 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)] AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(*authorized_pubkey, true), ]; @@ -780,6 +782,7 @@ fn _redelegate( AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*uninitialized_stake_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false), + #[allow(deprecated)] AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(*authorized_pubkey, true), ]; diff --git a/sdk/program/src/stake/mod.rs b/sdk/program/src/stake/mod.rs index a1fc97f227..837a2c538b 100644 --- a/sdk/program/src/stake/mod.rs +++ b/sdk/program/src/stake/mod.rs @@ -2,6 +2,7 @@ //! //! [np]: https://docs.solana.com/developing/runtime-facilities/sysvars#stakehistory +#[allow(deprecated)] pub mod config; pub mod instruction; pub mod stake_flags; diff --git a/sdk/program/src/stake/state.rs b/sdk/program/src/stake/state.rs index d763651e1d..3a9d5cd276 100644 --- a/sdk/program/src/stake/state.rs +++ b/sdk/program/src/stake/state.rs @@ -1,11 +1,11 @@ #![allow(clippy::integer_arithmetic)] + use { crate::{ clock::{Clock, Epoch, UnixTimestamp}, instruction::InstructionError, pubkey::Pubkey, stake::{ - config::Config, instruction::{LockupArgs, StakeError}, stake_flags::StakeFlags, }, @@ -17,6 +17,20 @@ use { pub type StakeActivationStatus = StakeHistoryEntry; +// means that no more than RATE of current effective stake may be added or subtracted per +// epoch +pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25; +pub const NEW_WARMUP_COOLDOWN_RATE: f64 = 0.09; +pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8; + +pub fn warmup_cooldown_rate(current_epoch: Epoch, new_rate_activation_epoch: Option) -> f64 { + if current_epoch < new_rate_activation_epoch.unwrap_or(u64::MAX) { + DEFAULT_WARMUP_COOLDOWN_RATE + } else { + NEW_WARMUP_COOLDOWN_RATE + } +} + #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] #[allow(clippy::large_enum_variant)] pub enum StakeState { @@ -311,33 +325,32 @@ pub struct Delegation { /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated pub deactivation_epoch: Epoch, /// how much stake we can activate per-epoch as a fraction of currently effective stake + #[deprecated( + since = "1.16.7", + note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead" + )] pub warmup_cooldown_rate: f64, } impl Default for Delegation { fn default() -> Self { + #[allow(deprecated)] Self { voter_pubkey: Pubkey::default(), stake: 0, activation_epoch: 0, deactivation_epoch: std::u64::MAX, - warmup_cooldown_rate: Config::default().warmup_cooldown_rate, + warmup_cooldown_rate: 0.0, } } } impl Delegation { - pub fn new( - voter_pubkey: &Pubkey, - stake: u64, - activation_epoch: Epoch, - warmup_cooldown_rate: f64, - ) -> Self { + pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self { Self { voter_pubkey: *voter_pubkey, stake, activation_epoch, - warmup_cooldown_rate, ..Delegation::default() } } @@ -345,8 +358,13 @@ impl Delegation { self.activation_epoch == std::u64::MAX } - pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { - self.stake_activating_and_deactivating(epoch, history) + pub fn stake( + &self, + epoch: Epoch, + history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, + ) -> u64 { + self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch) .effective } @@ -355,9 +373,11 @@ impl Delegation { &self, target_epoch: Epoch, history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, ) -> StakeActivationStatus { // first, calculate an effective and activating stake - let (effective_stake, activating_stake) = self.stake_and_activating(target_epoch, history); + let (effective_stake, activating_stake) = + self.stake_and_activating(target_epoch, history, new_rate_activation_epoch); // then de-activate some portion if necessary if target_epoch < self.deactivation_epoch { @@ -404,10 +424,12 @@ impl Delegation { // this account is entitled to take let weight = current_effective_stake as f64 / prev_cluster_stake.deactivating as f64; + let warmup_cooldown_rate = + warmup_cooldown_rate(current_epoch, new_rate_activation_epoch); // portion of newly not-effective cluster stake I'm entitled to at current epoch let newly_not_effective_cluster_stake = - prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; + prev_cluster_stake.effective as f64 * warmup_cooldown_rate; let newly_not_effective_stake = ((weight * newly_not_effective_cluster_stake) as u64).max(1); @@ -441,6 +463,7 @@ impl Delegation { &self, target_epoch: Epoch, history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, ) -> (u64, u64) { let delegated_stake = self.stake; @@ -489,10 +512,12 @@ impl Delegation { let remaining_activating_stake = delegated_stake - current_effective_stake; let weight = remaining_activating_stake as f64 / prev_cluster_stake.activating as f64; + let warmup_cooldown_rate = + warmup_cooldown_rate(current_epoch, new_rate_activation_epoch); // portion of newly effective cluster stake I'm entitled to at current epoch let newly_effective_cluster_stake = - prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; + prev_cluster_stake.effective as f64 * warmup_cooldown_rate; let newly_effective_stake = ((weight * newly_effective_cluster_stake) as u64).max(1); @@ -544,8 +569,14 @@ pub struct Stake { } impl Stake { - pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { - self.delegation.stake(epoch, history) + pub fn stake( + &self, + epoch: Epoch, + history: Option<&StakeHistory>, + new_rate_activation_epoch: Option, + ) -> u64 { + self.delegation + .stake(epoch, history, new_rate_activation_epoch) } pub fn split( @@ -628,7 +659,7 @@ mod test { stake: u64::MAX, activation_epoch: Epoch::MAX, deactivation_epoch: Epoch::MAX, - warmup_cooldown_rate: f64::MAX, + ..Delegation::default() }, credits_observed: 1, }, @@ -663,7 +694,7 @@ mod test { stake: u64::MAX, activation_epoch: Epoch::MAX, deactivation_epoch: Epoch::MAX, - warmup_cooldown_rate: f64::MAX, + ..Default::default() }, credits_observed: 1, }, diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 6957b951e8..cd768991c7 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -672,6 +672,21 @@ pub mod last_restart_slot_sysvar { solana_sdk::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR"); } +pub mod reduce_stake_warmup_cooldown { + use solana_program::{epoch_schedule::EpochSchedule, stake_history::Epoch}; + solana_sdk::declare_id!("GwtDQBghCTBgmX2cpEGNPxTEBUTQRaDMGTr5qychdGMj"); + + pub trait NewWarmupCooldownRateEpoch { + fn new_warmup_cooldown_rate_epoch(&self, epoch_schedule: &EpochSchedule) -> Option; + } + impl NewWarmupCooldownRateEpoch for super::FeatureSet { + fn new_warmup_cooldown_rate_epoch(&self, epoch_schedule: &EpochSchedule) -> Option { + self.activated_slot(&id()) + .map(|slot| epoch_schedule.get_epoch(slot)) + } + } +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -835,6 +850,7 @@ lazy_static! { (checked_arithmetic_in_fee_validation::id(), "checked arithmetic in fee validation #31273"), (bpf_account_data_direct_mapping::id(), "use memory regions to map account data into the rbpf vm instead of copying the data"), (last_restart_slot_sysvar::id(), "enable new sysvar last_restart_slot"), + (reduce_stake_warmup_cooldown::id(), "reduce stake warmup cooldown from 25% to 9%"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()