Add GetMinimumDelegation stake program instruction (#24020)

This commit is contained in:
Brooks Prumo
2022-04-02 00:11:10 -05:00
committed by GitHub
parent 792bbf75ab
commit 2af6753808
7 changed files with 178 additions and 92 deletions

View File

@ -1,11 +1,11 @@
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] #![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
#![allow(clippy::integer_arithmetic)] #![allow(clippy::integer_arithmetic)]
use solana_sdk::genesis_config::GenesisConfig;
#[deprecated( #[deprecated(
since = "1.8.0", since = "1.8.0",
note = "Please use `solana_sdk::stake::program::id` or `solana_program::stake::program::id` instead" note = "Please use `solana_sdk::stake::program::id` or `solana_program::stake::program::id` instead"
)] )]
pub use solana_sdk::stake::program::{check_id, id}; pub use solana_sdk::stake::program::{check_id, id};
use solana_sdk::{feature_set::FeatureSet, genesis_config::GenesisConfig};
pub mod config; pub mod config;
pub mod stake_instruction; pub mod stake_instruction;
@ -14,3 +14,15 @@ pub mod stake_state;
pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 { pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 {
config::add_genesis_account(genesis_config) config::add_genesis_account(genesis_config)
} }
/// The minimum stake amount that can be delegated, in lamports.
/// NOTE: This is also used to calculate the minimum balance of a stake account, which is the
/// rent exempt reserve _plus_ the minimum stake delegation.
#[inline(always)]
pub(crate) fn get_minimum_delegation(_feature_set: &FeatureSet) -> u64 {
// If/when the minimum delegation amount is changed, the `feature_set` parameter will be used
// to chose the correct value. And since the MINIMUM_STAKE_DELEGATION constant cannot be
// removed, use it here as to not duplicate magic constants.
#[allow(deprecated)]
solana_sdk::stake::MINIMUM_STAKE_DELEGATION
}

View File

@ -44,7 +44,7 @@ pub fn process_instruction(
match limited_deserialize(data)? { match limited_deserialize(data)? {
StakeInstruction::Initialize(authorized, lockup) => { StakeInstruction::Initialize(authorized, lockup) => {
let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
me.initialize(&authorized, &lockup, &rent) me.initialize(&authorized, &lockup, &rent, &invoke_context.feature_set)
} }
StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
instruction_context.check_number_of_instruction_accounts(3)?; instruction_context.check_number_of_instruction_accounts(3)?;
@ -183,6 +183,7 @@ pub fn process_instruction(
&stake_history, &stake_history,
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?, keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?,
keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(), keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(),
&invoke_context.feature_set,
) )
} }
StakeInstruction::Deactivate => { StakeInstruction::Deactivate => {
@ -213,7 +214,12 @@ pub fn process_instruction(
let rent = let rent =
get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
me.initialize(&authorized, &Lockup::default(), &rent) me.initialize(
&authorized,
&Lockup::default(),
&rent,
&invoke_context.feature_set,
)
} else { } else {
Err(InstructionError::InvalidInstructionData) Err(InstructionError::InvalidInstructionData)
} }
@ -311,6 +317,20 @@ pub fn process_instruction(
Err(InstructionError::InvalidInstructionData) Err(InstructionError::InvalidInstructionData)
} }
} }
StakeInstruction::GetMinimumDelegation => {
let feature_set = invoke_context.feature_set.as_ref();
if !feature_set.is_active(
&feature_set::add_get_minimum_delegation_instruction_to_stake_program::id(),
) {
return Err(InstructionError::InvalidInstructionData);
}
let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes());
invoke_context
.transaction_context
.set_return_data(id(), minimum_delegation)
}
} }
} }
@ -330,6 +350,7 @@ mod tests {
account::{self, AccountSharedData, ReadableAccount, WritableAccount}, account::{self, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut, account_utils::StateMut,
clock::{Epoch, UnixTimestamp}, clock::{Epoch, UnixTimestamp},
feature_set::FeatureSet,
instruction::{AccountMeta, Instruction}, instruction::{AccountMeta, Instruction},
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
@ -337,7 +358,6 @@ mod tests {
config as stake_config, config as stake_config,
instruction::{self, LockupArgs}, instruction::{self, LockupArgs},
state::{Authorized, Lockup, StakeAuthorize}, state::{Authorized, Lockup, StakeAuthorize},
MINIMUM_STAKE_DELEGATION,
}, },
system_program, system_program,
sysvar::{self, stake_history::StakeHistory}, sysvar::{self, stake_history::StakeHistory},
@ -665,7 +685,8 @@ mod tests {
let config_address = stake_config::id(); let config_address = stake_config::id();
let config_account = config::create_account(0, &stake_config::Config::default()); let config_account = config::create_account(0, &stake_config::Config::default());
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let withdrawal_amount = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let withdrawal_amount = rent_exempt_reserve + minimum_delegation;
// gets the "is_empty()" check // gets the "is_empty()" check
process_instruction( process_instruction(
@ -880,6 +901,7 @@ mod tests {
let rent_address = sysvar::rent::id(); let rent_address = sysvar::rent::id();
let rent_account = account::create_account_shared_data_for_test(&rent); let rent_account = account::create_account_shared_data_for_test(&rent);
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
// Test InitializeChecked with non-signing withdrawer // Test InitializeChecked with non-signing withdrawer
let mut instruction = let mut instruction =
@ -892,7 +914,7 @@ mod tests {
// Test InitializeChecked with withdrawer signer // Test InitializeChecked with withdrawer signer
let stake_account = AccountSharedData::new( let stake_account = AccountSharedData::new(
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, rent_exempt_reserve + minimum_delegation,
std::mem::size_of::<crate::stake_state::StakeState>(), std::mem::size_of::<crate::stake_state::StakeState>(),
&id(), &id(),
); );
@ -1214,7 +1236,8 @@ mod tests {
fn test_stake_initialize() { fn test_stake_initialize() {
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = rent_exempt_reserve + minimum_delegation;
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let stake_account = let stake_account =
AccountSharedData::new(stake_lamports, std::mem::size_of::<StakeState>(), &id()); AccountSharedData::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
@ -2345,7 +2368,8 @@ mod tests {
#[test] #[test]
fn test_split() { fn test_split() {
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let stake_lamports = MINIMUM_STAKE_DELEGATION * 2; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = minimum_delegation * 2;
let split_to_address = solana_sdk::pubkey::new_rand(); let split_to_address = solana_sdk::pubkey::new_rand();
let split_to_account = AccountSharedData::new_data_with_space( let split_to_account = AccountSharedData::new_data_with_space(
0, 0,
@ -2451,7 +2475,8 @@ mod tests {
let authority_address = solana_sdk::pubkey::new_rand(); let authority_address = solana_sdk::pubkey::new_rand();
let custodian_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand();
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let stake_lamports = MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = minimum_delegation;
let stake_account = AccountSharedData::new_data_with_space( let stake_account = AccountSharedData::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Uninitialized, &StakeState::Uninitialized,
@ -2979,7 +3004,8 @@ mod tests {
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = 7 * MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = 7 * minimum_delegation;
let stake_account = AccountSharedData::new_data_with_space( let stake_account = AccountSharedData::new_data_with_space(
stake_lamports + rent_exempt_reserve, stake_lamports + rent_exempt_reserve,
&StakeState::Initialized(Meta { &StakeState::Initialized(Meta {
@ -3034,7 +3060,7 @@ mod tests {
// should pass, withdrawing account down to minimum balance // should pass, withdrawing account down to minimum balance
process_instruction( process_instruction(
&serialize(&StakeInstruction::Withdraw( &serialize(&StakeInstruction::Withdraw(
stake_lamports - MINIMUM_STAKE_DELEGATION, stake_lamports - minimum_delegation,
)) ))
.unwrap(), .unwrap(),
transaction_accounts.clone(), transaction_accounts.clone(),
@ -3053,7 +3079,7 @@ mod tests {
// should fail, withdrawal that would leave less than rent-exempt reserve // should fail, withdrawal that would leave less than rent-exempt reserve
process_instruction( process_instruction(
&serialize(&StakeInstruction::Withdraw( &serialize(&StakeInstruction::Withdraw(
stake_lamports + MINIMUM_STAKE_DELEGATION, stake_lamports + minimum_delegation,
)) ))
.unwrap(), .unwrap(),
transaction_accounts.clone(), transaction_accounts.clone(),
@ -3195,7 +3221,8 @@ mod tests {
let custodian_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand();
let authorized_address = solana_sdk::pubkey::new_rand(); let authorized_address = solana_sdk::pubkey::new_rand();
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let stake_lamports = MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = minimum_delegation;
let stake_account = AccountSharedData::new_data_with_space( let stake_account = AccountSharedData::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Uninitialized, &StakeState::Uninitialized,
@ -3458,11 +3485,13 @@ mod tests {
); );
} }
/// Ensure that `initialize()` respects the MINIMUM_STAKE_DELEGATION requirements /// Ensure that `initialize()` respects the minimum delegation requirements
/// - Assert 1: accounts with a balance equal-to the minimum initialize OK /// - Assert 1: accounts with a balance equal-to the minimum initialize OK
/// - Assert 2: accounts with a balance less-than the minimum do not initialize /// - Assert 2: accounts with a balance less-than the minimum do not initialize
#[test] #[test]
fn test_initialize_minimum_stake_delegation() { fn test_initialize_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
@ -3484,9 +3513,9 @@ mod tests {
}, },
]; ];
for (stake_delegation, expected_result) in [ for (stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, Ok(())), (minimum_delegation, Ok(())),
( (
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
] { ] {
@ -3510,19 +3539,21 @@ mod tests {
} }
} }
/// Ensure that `delegate()` respects the MINIMUM_STAKE_DELEGATION requirements /// Ensure that `delegate()` respects the minimum delegation requirements
/// - Assert 1: delegating an amount equal-to the minimum delegates OK /// - Assert 1: delegating an amount equal-to the minimum delegates OK
/// - Assert 2: delegating an amount less-than the minimum delegates OK /// - Assert 2: delegating an amount less-than the minimum delegates OK
/// Also test both asserts above over both StakeState::{Initialized and Stake}, since the logic /// Also test both asserts above over both StakeState::{Initialized and Stake}, since the logic
/// is slightly different for the variants. /// is slightly different for the variants.
/// ///
/// NOTE: Even though new stake accounts must have a minimum balance that is at least /// NOTE: Even though new stake accounts must have a minimum balance that is at least
/// MINIMUM_STAKE_DELEGATION (plus rent exempt reserve), the current behavior allows /// the minimum delegation (plus rent exempt reserve), the current behavior allows
/// withdrawing below the minimum delegation, then re-delegating successfully (see /// withdrawing below the minimum delegation, then re-delegating successfully (see
/// `test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation()` for /// `test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation()` for
/// more information.) /// more information.)
#[test] #[test]
fn test_delegate_minimum_stake_delegation() { fn test_delegate_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
@ -3561,8 +3592,8 @@ mod tests {
}, },
]; ];
for (stake_delegation, expected_result) in [ for (stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, Ok(())), (minimum_delegation, Ok(())),
(MINIMUM_STAKE_DELEGATION - 1, Ok(())), (minimum_delegation - 1, Ok(())),
] { ] {
for stake_state in &[ for stake_state in &[
StakeState::Initialized(meta), StakeState::Initialized(meta),
@ -3600,7 +3631,7 @@ mod tests {
} }
} }
/// Ensure that `split()` respects the MINIMUM_STAKE_DELEGATION requirements. This applies to /// Ensure that `split()` respects the minimum delegation requirements. This applies to
/// both the source and destination acounts. Thus, we have four permutations possible based on /// both the source and destination acounts. Thus, we have four permutations possible based on
/// if each account's post-split delegation is equal-to (EQ) or less-than (LT) the minimum: /// if each account's post-split delegation is equal-to (EQ) or less-than (LT) the minimum:
/// ///
@ -3612,6 +3643,8 @@ mod tests {
/// LT | LT | Err /// LT | LT | Err
#[test] #[test]
fn test_split_minimum_stake_delegation() { fn test_split_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let source_address = Pubkey::new_unique(); let source_address = Pubkey::new_unique();
@ -3640,20 +3673,20 @@ mod tests {
}, },
]; ];
for (source_stake_delegation, dest_stake_delegation, expected_result) in [ for (source_stake_delegation, dest_stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, MINIMUM_STAKE_DELEGATION, Ok(())), (minimum_delegation, minimum_delegation, Ok(())),
( (
MINIMUM_STAKE_DELEGATION, minimum_delegation,
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
( (
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
MINIMUM_STAKE_DELEGATION, minimum_delegation,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
( (
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
] { ] {
@ -3692,7 +3725,7 @@ mod tests {
} }
} }
/// Ensure that splitting the full amount from an account respects the MINIMUM_STAKE_DELEGATION /// Ensure that splitting the full amount from an account respects the minimum delegation
/// requirements. This ensures that we are future-proofing/testing any raises to the minimum /// requirements. This ensures that we are future-proofing/testing any raises to the minimum
/// delegation. /// delegation.
/// - Assert 1: splitting the full amount from an account that has at least the minimum /// - Assert 1: splitting the full amount from an account that has at least the minimum
@ -3701,6 +3734,8 @@ mod tests {
/// delegation is not OK /// delegation is not OK
#[test] #[test]
fn test_split_full_amount_minimum_stake_delegation() { fn test_split_full_amount_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let source_address = Pubkey::new_unique(); let source_address = Pubkey::new_unique();
@ -3729,9 +3764,9 @@ mod tests {
}, },
]; ];
for (stake_delegation, expected_result) in [ for (stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, Ok(())), (minimum_delegation, Ok(())),
( (
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
] { ] {
@ -3767,6 +3802,8 @@ mod tests {
/// account already has funds, ensure the minimum split amount reduces accordingly. /// account already has funds, ensure the minimum split amount reduces accordingly.
#[test] #[test]
fn test_split_destination_minimum_stake_delegation() { fn test_split_destination_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let source_address = Pubkey::new_unique(); let source_address = Pubkey::new_unique();
@ -3790,66 +3827,54 @@ mod tests {
for (destination_starting_balance, split_amount, expected_result) in [ for (destination_starting_balance, split_amount, expected_result) in [
// split amount must be non zero // split amount must be non zero
( (
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, rent_exempt_reserve + minimum_delegation,
0, 0,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
// any split amount is OK when destination account is already fully funded // any split amount is OK when destination account is already fully funded
(rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, 1, Ok(())), (rent_exempt_reserve + minimum_delegation, 1, Ok(())),
// if destination is only short by 1 lamport, then split amount can be 1 lamport // if destination is only short by 1 lamport, then split amount can be 1 lamport
( (rent_exempt_reserve + minimum_delegation - 1, 1, Ok(())),
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
1,
Ok(()),
),
// destination short by 2 lamports, so 1 isn't enough (non-zero split amount) // destination short by 2 lamports, so 1 isn't enough (non-zero split amount)
( (
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 2, rent_exempt_reserve + minimum_delegation - 2,
1, 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
// destination is rent exempt, so split enough for minimum delegation // destination is rent exempt, so split enough for minimum delegation
(rent_exempt_reserve, MINIMUM_STAKE_DELEGATION, Ok(())), (rent_exempt_reserve, minimum_delegation, Ok(())),
// destination is rent exempt, but split amount less than minimum delegation // destination is rent exempt, but split amount less than minimum delegation
( (
rent_exempt_reserve, rent_exempt_reserve,
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
// destination is not rent exempt, so split enough for rent and minimum delegation // destination is not rent exempt, so split enough for rent and minimum delegation
( (rent_exempt_reserve - 1, minimum_delegation + 1, Ok(())),
rent_exempt_reserve - 1,
MINIMUM_STAKE_DELEGATION + 1,
Ok(()),
),
// destination is not rent exempt, but split amount only for minimum delegation // destination is not rent exempt, but split amount only for minimum delegation
( (
rent_exempt_reserve - 1, rent_exempt_reserve - 1,
MINIMUM_STAKE_DELEGATION, minimum_delegation,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
// destination has smallest non-zero balance, so can split the minimum balance // destination has smallest non-zero balance, so can split the minimum balance
// requirements minus what destination already has // requirements minus what destination already has
( (1, rent_exempt_reserve + minimum_delegation - 1, Ok(())),
1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
Ok(()),
),
// destination has smallest non-zero balance, but cannot split less than the minimum // destination has smallest non-zero balance, but cannot split less than the minimum
// balance requirements minus what destination already has // balance requirements minus what destination already has
( (
1, 1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 2, rent_exempt_reserve + minimum_delegation - 2,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
// destination has zero lamports, so split must be at least rent exempt reserve plus // destination has zero lamports, so split must be at least rent exempt reserve plus
// minimum delegation // minimum delegation
(0, rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, Ok(())), (0, rent_exempt_reserve + minimum_delegation, Ok(())),
// destination has zero lamports, but split amount is less than rent exempt reserve // destination has zero lamports, but split amount is less than rent exempt reserve
// plus minimum delegation // plus minimum delegation
( (
0, 0,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, rent_exempt_reserve + minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
] { ] {
@ -3907,7 +3932,7 @@ mod tests {
expected_destination_stake_delegation, expected_destination_stake_delegation,
destination_stake.delegation.stake destination_stake.delegation.stake
); );
assert!(destination_stake.delegation.stake >= MINIMUM_STAKE_DELEGATION,); assert!(destination_stake.delegation.stake >= minimum_delegation,);
} else { } else {
panic!("destination state must be StakeStake::Stake after successful split when source is also StakeState::Stake!"); panic!("destination state must be StakeStake::Stake after successful split when source is also StakeState::Stake!");
} }
@ -3917,11 +3942,13 @@ mod tests {
} }
} }
/// Ensure that `withdraw()` respects the MINIMUM_STAKE_DELEGATION requirements /// Ensure that `withdraw()` respects the minimum delegation requirements
/// - Assert 1: withdrawing so remaining stake is equal-to the minimum is OK /// - 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 /// - Assert 2: withdrawing so remaining stake is less-than the minimum is not OK
#[test] #[test]
fn test_withdraw_minimum_stake_delegation() { fn test_withdraw_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
@ -3957,11 +3984,11 @@ mod tests {
is_writable: false, is_writable: false,
}, },
]; ];
let starting_stake_delegation = MINIMUM_STAKE_DELEGATION; let starting_stake_delegation = minimum_delegation;
for (ending_stake_delegation, expected_result) in [ for (ending_stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, Ok(())), (minimum_delegation, Ok(())),
( (
MINIMUM_STAKE_DELEGATION - 1, minimum_delegation - 1,
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
), ),
] { ] {
@ -4023,11 +4050,13 @@ mod tests {
/// 5. Re-delegates, now with less than the minimum delegation, but it still succeeds /// 5. Re-delegates, now with less than the minimum delegation, but it still succeeds
#[test] #[test]
fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() { fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() {
let feature_set = FeatureSet::all_enabled();
let minimum_delegation = crate::get_minimum_delegation(&feature_set);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand();
let stake_account = AccountSharedData::new( let stake_account = AccountSharedData::new(
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, rent_exempt_reserve + minimum_delegation,
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
); );
@ -4150,7 +4179,7 @@ mod tests {
account::create_account_shared_data_for_test(&clock), account::create_account_shared_data_for_test(&clock),
); );
let withdraw_amount = let withdraw_amount =
accounts[0].lamports() - (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1); accounts[0].lamports() - (rent_exempt_reserve + minimum_delegation - 1);
process_instruction( process_instruction(
&serialize(&StakeInstruction::Withdraw(withdraw_amount)).unwrap(), &serialize(&StakeInstruction::Withdraw(withdraw_amount)).unwrap(),
transaction_accounts.clone(), transaction_accounts.clone(),

View File

@ -14,7 +14,9 @@ use {
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
account_utils::{State, StateMut}, account_utils::{State, StateMut},
clock::{Clock, Epoch}, clock::{Clock, Epoch},
feature_set::{stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar}, feature_set::{
stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar, FeatureSet,
},
instruction::{checked_add, InstructionError}, instruction::{checked_add, InstructionError},
keyed_account::KeyedAccount, keyed_account::KeyedAccount,
pubkey::Pubkey, pubkey::Pubkey,
@ -23,7 +25,6 @@ use {
config::Config, config::Config,
instruction::{LockupArgs, StakeError}, instruction::{LockupArgs, StakeError},
program::id, program::id,
MINIMUM_STAKE_DELEGATION,
}, },
stake_history::{StakeHistory, StakeHistoryEntry}, stake_history::{StakeHistory, StakeHistoryEntry},
}, },
@ -370,6 +371,7 @@ pub trait StakeAccount {
authorized: &Authorized, authorized: &Authorized,
lockup: &Lockup, lockup: &Lockup,
rent: &Rent, rent: &Rent,
feature_set: &FeatureSet,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn authorize( fn authorize(
&self, &self,
@ -429,6 +431,7 @@ pub trait StakeAccount {
stake_history: &StakeHistory, stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount, withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>, custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
} }
@ -438,13 +441,15 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
authorized: &Authorized, authorized: &Authorized,
lockup: &Lockup, lockup: &Lockup,
rent: &Rent, rent: &Rent,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if self.data_len()? != std::mem::size_of::<StakeState>() { if self.data_len()? != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData); return Err(InstructionError::InvalidAccountData);
} }
if let StakeState::Uninitialized = self.state()? { if let StakeState::Uninitialized = self.state()? {
let rent_exempt_reserve = rent.minimum_balance(self.data_len()?); let rent_exempt_reserve = rent.minimum_balance(self.data_len()?);
let minimum_balance = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation;
if self.lamports()? >= minimum_balance { if self.lamports()? >= minimum_balance {
self.set_state(&StakeState::Initialized(Meta { self.set_state(&StakeState::Initialized(Meta {
@ -760,6 +765,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake_history: &StakeHistory, stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount, withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>, custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut signers = HashSet::new(); let mut signers = HashSet::new();
let withdraw_authority_pubkey = withdraw_authority let withdraw_authority_pubkey = withdraw_authority
@ -788,7 +794,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
meta.authorized meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?; .check(&signers, StakeAuthorize::Withdrawer)?;
// stake accounts must have a balance >= rent_exempt_reserve + minimum_stake_delegation // stake accounts must have a balance >= rent_exempt_reserve + minimum_stake_delegation
let reserve = checked_add(meta.rent_exempt_reserve, MINIMUM_STAKE_DELEGATION)?; let reserve = checked_add(
meta.rent_exempt_reserve,
crate::get_minimum_delegation(feature_set),
)?;
(meta.lockup, reserve, false) (meta.lockup, reserve, false)
} }
@ -887,9 +896,10 @@ fn validate_split_amount(
// EITHER at least the minimum balance, OR zero (in this case the source // EITHER at least the minimum balance, OR zero (in this case the source
// account is transferring all lamports to new destination account, and the source // account is transferring all lamports to new destination account, and the source
// account will be closed) // account will be closed)
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let source_minimum_balance = source_meta let source_minimum_balance = source_meta
.rent_exempt_reserve .rent_exempt_reserve
.saturating_add(MINIMUM_STAKE_DELEGATION); .saturating_add(minimum_delegation);
let source_remaining_balance = source_lamports.saturating_sub(lamports); let source_remaining_balance = source_lamports.saturating_sub(lamports);
if source_remaining_balance == 0 { if source_remaining_balance == 0 {
// full amount is a withdrawal // full amount is a withdrawal
@ -920,7 +930,7 @@ fn validate_split_amount(
) )
}; };
let destination_minimum_balance = let destination_minimum_balance =
destination_rent_exempt_reserve.saturating_add(MINIMUM_STAKE_DELEGATION); destination_rent_exempt_reserve.saturating_add(minimum_delegation);
let destination_balance_deficit = let destination_balance_deficit =
destination_minimum_balance.saturating_sub(destination_lamports); destination_minimum_balance.saturating_sub(destination_lamports);
if lamports < destination_balance_deficit { if lamports < destination_balance_deficit {
@ -928,7 +938,7 @@ fn validate_split_amount(
} }
// If the source account is already staked, the destination will end up staked as well. Verify // If the source account is already staked, the destination will end up staked as well. Verify
// the destination account's delegation amount is at least MINIMUM_STAKE_DELEGATION. // the destination account's delegation amount is at least the minimum delegation.
// //
// The *delegation* requirements are different than the *balance* requirements. If the // The *delegation* requirements are different than the *balance* requirements. If the
// destination account is prefunded with a balance of `rent exempt reserve + minimum stake // destination account is prefunded with a balance of `rent exempt reserve + minimum stake
@ -937,7 +947,7 @@ fn validate_split_amount(
// account, the split amount must be at least the minimum stake delegation. So if the minimum // account, the split amount must be at least the minimum stake delegation. So if the minimum
// stake delegation was 10 lamports, then a split amount of 1 lamport would not meet the // stake delegation was 10 lamports, then a split amount of 1 lamport would not meet the
// *delegation* requirements. // *delegation* requirements.
if source_stake.is_some() && lamports < MINIMUM_STAKE_DELEGATION { if source_stake.is_some() && lamports < minimum_delegation {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
@ -2525,8 +2535,9 @@ mod tests {
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_account = AccountSharedData::new_ref_data_with_space( let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Uninitialized, &StakeState::Uninitialized,
@ -2554,7 +2565,7 @@ mod tests {
&invoke_context, &invoke_context,
stake_lamports / 2, stake_lamports / 2,
&split_stake_keyed_account, &split_stake_keyed_account,
&HashSet::default() // no signers &HashSet::default(), // no signers
), ),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
@ -2677,8 +2688,9 @@ mod tests {
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_account = AccountSharedData::new_ref_data_with_space( let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Stake( &StakeState::Stake(
@ -2723,7 +2735,8 @@ mod tests {
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_balance = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation;
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = minimum_balance * 2; let stake_lamports = minimum_balance * 2;
@ -2767,7 +2780,7 @@ mod tests {
&invoke_context, &invoke_context,
rent_exempt_reserve, rent_exempt_reserve,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers,
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -2778,7 +2791,7 @@ mod tests {
&invoke_context, &invoke_context,
stake_lamports - rent_exempt_reserve, stake_lamports - rent_exempt_reserve,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers,
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -2793,7 +2806,7 @@ mod tests {
&invoke_context, &invoke_context,
stake_lamports - minimum_balance, stake_lamports - minimum_balance,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers,
), ),
Ok(()) Ok(())
); );
@ -2832,7 +2845,8 @@ mod tests {
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
@ -2851,8 +2865,8 @@ mod tests {
0, 0,
rent_exempt_reserve - 1, rent_exempt_reserve - 1,
rent_exempt_reserve, rent_exempt_reserve,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, rent_exempt_reserve + minimum_delegation,
]; ];
for initial_balance in split_lamport_balances { for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space( let split_stake_account = AccountSharedData::new_ref_data_with_space(
@ -2952,7 +2966,8 @@ mod tests {
let source_larger_rent_exempt_reserve = let source_larger_rent_exempt_reserve =
rent.minimum_balance(std::mem::size_of::<StakeState>() + 100); rent.minimum_balance(std::mem::size_of::<StakeState>() + 100);
let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = (source_larger_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = (source_larger_rent_exempt_reserve + minimum_delegation) * 2;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
@ -2975,8 +2990,8 @@ mod tests {
0, 0,
split_rent_exempt_reserve - 1, split_rent_exempt_reserve - 1,
split_rent_exempt_reserve, split_rent_exempt_reserve,
split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, split_rent_exempt_reserve + minimum_delegation - 1,
split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, split_rent_exempt_reserve + minimum_delegation,
]; ];
for initial_balance in split_lamport_balances { for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space( let split_stake_account = AccountSharedData::new_ref_data_with_space(
@ -3154,7 +3169,8 @@ mod tests {
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = rent_exempt_reserve + minimum_delegation;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
@ -3247,7 +3263,8 @@ mod tests {
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = rent_exempt_reserve + minimum_delegation;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
@ -3266,8 +3283,8 @@ mod tests {
0, 0,
rent_exempt_reserve - 1, rent_exempt_reserve - 1,
rent_exempt_reserve, rent_exempt_reserve,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, rent_exempt_reserve + minimum_delegation,
]; ];
for initial_balance in split_lamport_balances { for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space( let split_stake_account = AccountSharedData::new_ref_data_with_space(
@ -3336,7 +3353,8 @@ mod tests {
let source_rent_exempt_reserve = let source_rent_exempt_reserve =
rent.minimum_balance(std::mem::size_of::<StakeState>() + 100); rent.minimum_balance(std::mem::size_of::<StakeState>() + 100);
let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = source_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = source_rent_exempt_reserve + minimum_delegation;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();

View File

@ -222,6 +222,15 @@ pub enum StakeInstruction {
/// 1. `[SIGNER]` Lockup authority or withdraw authority /// 1. `[SIGNER]` Lockup authority or withdraw authority
/// 2. Optional: `[SIGNER]` New lockup authority /// 2. Optional: `[SIGNER]` New lockup authority
SetLockupChecked(LockupCheckedArgs), SetLockupChecked(LockupCheckedArgs),
/// Get the minimum stake delegation, in lamports
///
/// # Account references
/// None
///
/// The minimum delegation will be returned via the transaction context's returndata.
/// Use `get_return_data()` to retrieve the result.
GetMinimumDelegation,
} }
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
@ -678,6 +687,14 @@ pub fn set_lockup_checked(
) )
} }
pub fn get_minimum_delegation() -> Instruction {
Instruction::new_with_bincode(
id(),
&StakeInstruction::GetMinimumDelegation,
Vec::default(),
)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use {super::*, crate::instruction::InstructionError}; use {super::*, crate::instruction::InstructionError};

View File

@ -6,7 +6,8 @@ pub mod program {
crate::declare_id!("Stake11111111111111111111111111111111111111"); crate::declare_id!("Stake11111111111111111111111111111111111111");
} }
/// The minimum stake amount that can be delegated, in lamports. #[deprecated(
/// NOTE: This is also used to calculate the minimum balance of a stake account, which is the since = "1.10.6",
/// rent exempt reserve _plus_ the minimum stake delegation. note = "This constant may be outdated, please use `solana_stake_program::get_minimum_delegation` instead"
)]
pub const MINIMUM_STAKE_DELEGATION: u64 = 1; pub const MINIMUM_STAKE_DELEGATION: u64 = 1;

View File

@ -339,6 +339,10 @@ pub mod stake_split_uses_rent_sysvar {
solana_sdk::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV"); solana_sdk::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV");
} }
pub mod add_get_minimum_delegation_instruction_to_stake_program {
solana_sdk::declare_id!("St8k9dVXP97xT6faW24YmRSYConLbhsMJA4TJTBLmMT");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -418,6 +422,7 @@ lazy_static! {
(disable_deprecated_loader::id(), "disable the deprecated BPF loader"), (disable_deprecated_loader::id(), "disable the deprecated BPF loader"),
(check_slice_translation_size::id(), "check size when translating slices"), (check_slice_translation_size::id(), "check size when translating slices"),
(stake_split_uses_rent_sysvar::id(), "stake split instruction uses rent sysvar"), (stake_split_uses_rent_sysvar::id(), "stake split instruction uses rent sysvar"),
(add_get_minimum_delegation_instruction_to_stake_program::id(), "add GetMinimumDelegation instruction to stake program"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()

View File

@ -3,7 +3,7 @@ use {
check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
}, },
bincode::deserialize, bincode::deserialize,
serde_json::{json, Map}, serde_json::{json, Map, Value},
solana_sdk::{ solana_sdk::{
instruction::CompiledInstruction, message::AccountKeys, instruction::CompiledInstruction, message::AccountKeys,
stake::instruction::StakeInstruction, stake::instruction::StakeInstruction,
@ -269,6 +269,10 @@ pub fn parse_stake(
}), }),
}) })
} }
StakeInstruction::GetMinimumDelegation => Ok(ParsedInstructionEnum {
instruction_type: "getMinimumDelegation".to_string(),
info: Value::default(),
}),
} }
} }