diff --git a/program/src/helpers/delegate.rs b/program/src/helpers/delegate.rs index 2d30671c..194a34e0 100644 --- a/program/src/helpers/delegate.rs +++ b/program/src/helpers/delegate.rs @@ -1,5 +1,4 @@ use { - crate::PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, solana_account_info::AccountInfo, solana_clock::Epoch, solana_program_error::ProgramError, @@ -7,7 +6,6 @@ use { solana_stake_interface::{ error::StakeError, state::{Delegation, Meta, Stake}, - sysvar::stake_history::StakeHistorySysvar, }, }; @@ -29,46 +27,6 @@ pub(crate) fn new_stake( } } -pub(crate) fn redelegate_stake( - stake: &mut Stake, - stake_lamports: u64, - voter_pubkey: &Pubkey, - credits_observed: u64, - epoch: Epoch, - stake_history: &StakeHistorySysvar, -) -> Result<(), ProgramError> { - // If stake is currently active: - if stake.stake( - epoch, - stake_history, - PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, - ) != 0 - { - // If pubkey of new voter is the same as current, - // and we are scheduled to start deactivating this epoch, - // we rescind deactivation - if stake.delegation.voter_pubkey == *voter_pubkey - && epoch == stake.delegation.deactivation_epoch - { - stake.delegation.deactivation_epoch = u64::MAX; - return Ok(()); - } else { - // can't redelegate to another pubkey if stake is active. - return Err(StakeError::TooSoonToRedelegate.into()); - } - } - // Either the stake is freshly activated, is active but has been - // deactivated this epoch, or has fully de-activated. - // Redelegation implies either re-activation or un-deactivation - - stake.delegation.stake = stake_lamports; - stake.delegation.activation_epoch = epoch; - stake.delegation.deactivation_epoch = u64::MAX; - stake.delegation.voter_pubkey = *voter_pubkey; - stake.credits_observed = credits_observed; - Ok(()) -} - /// Ensure the stake delegation amount is valid. This checks that the account /// meets the minimum balance requirements of delegated stake. If not, return /// an error. diff --git a/program/src/processor.rs b/program/src/processor.rs index c046d86d..d9033c11 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -404,22 +404,45 @@ impl Processor { ) } StakeStateV2::Stake(meta, mut stake, flags) => { + // Only the staker may (re)delegate meta.authorized .check(&signers, StakeAuthorize::Staker) .map_err(to_program_error)?; + // Compute the maximum stake allowed to (re)delegate let ValidatedDelegatedInfo { stake_amount } = validate_delegated_amount(stake_account_info, &meta)?; - redelegate_stake( - &mut stake, - stake_amount, - vote_account_info.key, - vote_state.credits(), + // Get current activation status at this epoch + let effective_stake = stake.delegation.stake( clock.epoch, stake_history, - )?; + PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, + ); + + if effective_stake == 0 { + // The stake has no effective voting power this epoch. This means it is either: + // 1. Inactive (fully cooled down after a previous deactivation) + // 2. Still activating (was delegated for the first time this epoch) + stake = new_stake( + stake_amount, + vote_account_info.key, + vote_state.credits(), + clock.epoch, + ); + } else if clock.epoch == stake.delegation.deactivation_epoch + && stake.delegation.voter_pubkey == *vote_account_info.key + { + if stake_amount < stake.delegation.stake { + return Err(StakeError::InsufficientDelegation.into()); + } + stake.delegation.deactivation_epoch = u64::MAX; + } else { + // Not a valid state for redelegation + return Err(StakeError::TooSoonToRedelegate.into()); + } + // Persist the updated stake state back to the account. set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags)) } _ => Err(ProgramError::InvalidAccountData),