diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 13eb3c21031031..8445782f840931 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -72,7 +72,7 @@ use { system_program, transaction::{MessageHash, SanitizedTransaction, SimpleAddressLoader}, }, - solana_stake_program::stake_state::{self, PointValue}, + solana_stake_program::{points::PointValue, stake_state}, solana_unified_scheduler_pool::DefaultSchedulerPool, solana_vote_program::{ self, @@ -2589,7 +2589,7 @@ fn main() { new_credits_observed: Option, skipped_reasons: String, } - use solana_stake_program::stake_state::InflationPointCalculationEvent; + use solana_stake_program::points::InflationPointCalculationEvent; let stake_calculation_details: DashMap = DashMap::new(); let last_point_value = Arc::new(RwLock::new(None)); diff --git a/programs/stake/src/lib.rs b/programs/stake/src/lib.rs index b6d2ff478432b6..5f0f6c5f1342f6 100644 --- a/programs/stake/src/lib.rs +++ b/programs/stake/src/lib.rs @@ -12,6 +12,9 @@ use solana_sdk::{ }; pub mod config; +pub mod points; +#[doc(hidden)] +pub mod rewards; pub mod stake_instruction; pub mod stake_state; diff --git a/programs/stake/src/points.rs b/programs/stake/src/points.rs new file mode 100644 index 00000000000000..d19bd3435a366d --- /dev/null +++ b/programs/stake/src/points.rs @@ -0,0 +1,249 @@ +//! Information about points calculation based on stake state. +//! Used by `solana-runtime`. + +use { + solana_sdk::{ + clock::Epoch, + instruction::InstructionError, + pubkey::Pubkey, + stake::state::{Delegation, Stake, StakeStateV2}, + stake_history::StakeHistory, + }, + solana_vote_program::vote_state::VoteState, + std::cmp::Ordering, +}; + +/// captures a rewards round as lamports to be awarded +/// and the total points over which those lamports +/// are to be distributed +// basically read as rewards/points, but in integers instead of as an f64 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PointValue { + pub rewards: u64, // lamports to split + pub points: u128, // over these points +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct CalculatedStakePoints { + pub(crate) points: u128, + pub(crate) new_credits_observed: u64, + pub(crate) force_credits_update_with_skipped_reward: bool, +} + +#[derive(Debug)] +pub enum InflationPointCalculationEvent { + CalculatedPoints(u64, u128, u128, u128), + SplitRewards(u64, u64, u64, PointValue), + EffectiveStakeAtRewardedEpoch(u64), + RentExemptReserve(u64), + Delegation(Delegation, Pubkey), + Commission(u8), + CreditsObserved(u64, Option), + Skipped(SkippedReason), +} + +pub(crate) fn null_tracer() -> Option { + None:: +} + +#[derive(Debug)] +pub enum SkippedReason { + DisabledInflation, + JustActivated, + TooEarlyUnfairSplit, + ZeroPoints, + ZeroPointValue, + ZeroReward, + ZeroCreditsAndReturnZero, + ZeroCreditsAndReturnCurrent, + ZeroCreditsAndReturnRewinded, +} + +impl From for InflationPointCalculationEvent { + fn from(reason: SkippedReason) -> Self { + InflationPointCalculationEvent::Skipped(reason) + } +} + +// utility function, used by runtime +#[doc(hidden)] +pub fn calculate_points( + stake_state: &StakeStateV2, + vote_state: &VoteState, + stake_history: &StakeHistory, + new_rate_activation_epoch: Option, +) -> Result { + if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state { + Ok(calculate_stake_points( + stake, + vote_state, + stake_history, + null_tracer(), + new_rate_activation_epoch, + )) + } else { + Err(InstructionError::InvalidAccountData) + } +} + +fn calculate_stake_points( + stake: &Stake, + vote_state: &VoteState, + stake_history: &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 +} + +/// for a given stake and vote_state, calculate how many +/// points were earned (credits * stake) and new value +/// for credits_observed were the points paid +pub(crate) fn calculate_stake_points_and_credits( + stake: &Stake, + new_vote_state: &VoteState, + stake_history: &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(); + // if there is no newer credits since observed, return no point + match credits_in_vote.cmp(&credits_in_stake) { + Ordering::Less => { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into()); + } + // Don't adjust stake.activation_epoch for simplicity: + // - generally fast-forwarding stake.activation_epoch forcibly (for + // artificial re-activation with re-warm-up) skews the stake + // history sysvar. And properly handling all the cases + // regarding deactivation epoch/warm-up/cool-down without + // introducing incentive skew is hard. + // - Conceptually, it should be acceptable for the staked SOLs at + // the recreated vote to receive rewards again immediately after + // rewind even if it looks like instant activation. That's + // because it must have passed the required warmed-up at least + // once in the past already + // - Also such a stake account remains to be a part of overall + // effective stake calculation even while the vote account is + // missing for (indefinite) time or remains to be pre-remove + // credits score. It should be treated equally to staking with + // delinquent validator with no differentiation. + + // hint with true to indicate some exceptional credits handling is needed + return CalculatedStakePoints { + points: 0, + new_credits_observed: credits_in_vote, + force_credits_update_with_skipped_reward: true, + }; + } + Ordering::Equal => { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into()); + } + // don't hint caller and return current value if credits remain unchanged (= delinquent) + return CalculatedStakePoints { + points: 0, + new_credits_observed: credits_in_stake, + force_credits_update_with_skipped_reward: false, + }; + } + Ordering::Greater => {} + } + + let mut points = 0; + let mut new_credits_observed = credits_in_stake; + + 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, + new_rate_activation_epoch, + )); + + // figure out how much this stake has seen that + // for which the vote account has a record + let earned_credits = if credits_in_stake < initial_epoch_credits { + // the staker observed the entire epoch + final_epoch_credits - initial_epoch_credits + } else if credits_in_stake < final_epoch_credits { + // the staker registered sometime during the epoch, partial credit + final_epoch_credits - new_credits_observed + } else { + // the staker has already observed or been redeemed this epoch + // or was activated after this epoch + 0 + }; + let earned_credits = u128::from(earned_credits); + + // don't want to assume anything about order of the iterator... + new_credits_observed = new_credits_observed.max(final_epoch_credits); + + // finally calculate points for this epoch + let earned_points = stake_amount * earned_credits; + points += earned_points; + + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints( + epoch, + stake_amount, + earned_credits, + earned_points, + )); + } + } + + CalculatedStakePoints { + points, + new_credits_observed, + force_credits_update_with_skipped_reward: false, + } +} + +#[cfg(test)] +mod tests { + use {super::*, crate::stake_state::new_stake, solana_sdk::native_token}; + + #[test] + fn test_stake_state_calculate_points_with_typical_values() { + let mut vote_state = VoteState::default(); + + // bootstrap means fully-vested stake at epoch 0 with + // 10_000_000 SOL is a big but not unreasaonable stake + let stake = new_stake( + native_token::sol_to_lamports(10_000_000f64), + &Pubkey::default(), + &vote_state, + std::u64::MAX, + ); + + let epoch_slots: u128 = 14 * 24 * 3600 * 160; + // put 193,536,000 credits in at epoch 0, typical for a 14-day epoch + // this loop takes a few seconds... + for _ in 0..epoch_slots { + vote_state.increment_credits(0, 1); + } + + // no overflow on points + assert_eq!( + u128::from(stake.delegation.stake) * epoch_slots, + calculate_stake_points( + &stake, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None + ) + ); + } +} diff --git a/programs/stake/src/rewards.rs b/programs/stake/src/rewards.rs new file mode 100644 index 00000000000000..08416996520289 --- /dev/null +++ b/programs/stake/src/rewards.rs @@ -0,0 +1,647 @@ +//! Information about stake and voter rewards based on stake state. +//! Used by `solana-runtime`. + +use { + crate::points::{ + calculate_stake_points_and_credits, CalculatedStakePoints, InflationPointCalculationEvent, + PointValue, SkippedReason, + }, + solana_sdk::{ + account::{AccountSharedData, WritableAccount}, + account_utils::StateMut, + clock::Epoch, + instruction::InstructionError, + stake::{ + instruction::StakeError, + state::{Stake, StakeStateV2}, + }, + stake_history::StakeHistory, + }, + solana_vote_program::vote_state::VoteState, +}; + +#[derive(Debug, PartialEq, Eq)] +struct CalculatedStakeRewards { + staker_rewards: u64, + voter_rewards: u64, + new_credits_observed: u64, +} + +// utility function, used by runtime +// returns a tuple of (stakers_reward,voters_reward) +#[doc(hidden)] +pub fn redeem_rewards( + rewarded_epoch: Epoch, + stake_state: StakeStateV2, + stake_account: &mut AccountSharedData, + vote_state: &VoteState, + point_value: &PointValue, + stake_history: &StakeHistory, + inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, +) -> Result<(u64, u64), InstructionError> { + if let StakeStateV2::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, + new_rate_activation_epoch, + )), + ); + inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve( + meta.rent_exempt_reserve, + )); + inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission( + vote_state.commission, + )); + } + + if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards( + rewarded_epoch, + &mut stake, + point_value, + vote_state, + stake_history, + inflation_point_calc_tracer, + new_rate_activation_epoch, + ) { + stake_account.checked_add_lamports(stakers_reward)?; + stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))?; + + Ok((stakers_reward, voters_reward)) + } else { + Err(StakeError::NoCreditsToRedeem.into()) + } + } else { + Err(InstructionError::InvalidAccountData) + } +} + +fn redeem_stake_rewards( + rewarded_epoch: Epoch, + stake: &mut Stake, + point_value: &PointValue, + vote_state: &VoteState, + stake_history: &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( + stake.credits_observed, + None, + )); + } + calculate_stake_rewards( + rewarded_epoch, + stake, + point_value, + 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 { + inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( + stake.credits_observed, + Some(calculated_stake_rewards.new_credits_observed), + )); + } + stake.credits_observed = calculated_stake_rewards.new_credits_observed; + stake.delegation.stake += calculated_stake_rewards.staker_rewards; + ( + calculated_stake_rewards.staker_rewards, + calculated_stake_rewards.voter_rewards, + ) + }) +} + +/// for a given stake and vote_state, calculate what distributions and what updates should be made +/// returns a tuple in the case of a payout of: +/// * staker_rewards to be distributed +/// * voter_rewards to be distributed +/// * new value for credits_observed in the stake +/// returns None if there's no payout or if any deserved payout is < 1 lamport +fn calculate_stake_rewards( + rewarded_epoch: Epoch, + stake: &Stake, + point_value: &PointValue, + vote_state: &VoteState, + stake_history: &StakeHistory, + inflation_point_calc_tracer: Option, + new_rate_activation_epoch: Option, +) -> Option { + // ensure to run to trigger (optional) inflation_point_calc_tracer + let CalculatedStakePoints { + points, + new_credits_observed, + mut force_credits_update_with_skipped_reward, + } = calculate_stake_points_and_credits( + stake, + vote_state, + stake_history, + inflation_point_calc_tracer.as_ref(), + new_rate_activation_epoch, + ); + + // Drive credits_observed forward unconditionally when rewards are disabled + // or when this is the stake's activation epoch + if point_value.rewards == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into()); + } + force_credits_update_with_skipped_reward = true; + } else if stake.delegation.activation_epoch == rewarded_epoch { + // not assert!()-ed; but points should be zero + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::JustActivated.into()); + } + force_credits_update_with_skipped_reward = true; + } + + if force_credits_update_with_skipped_reward { + return Some(CalculatedStakeRewards { + staker_rewards: 0, + voter_rewards: 0, + new_credits_observed, + }); + } + + if points == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into()); + } + return None; + } + if point_value.points == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into()); + } + return None; + } + + let rewards = points + .checked_mul(u128::from(point_value.rewards)) + .unwrap() + .checked_div(point_value.points) + .unwrap(); + + let rewards = u64::try_from(rewards).unwrap(); + + // don't bother trying to split if fractional lamports got truncated + if rewards == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::ZeroReward.into()); + } + return None; + } + let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards); + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards( + rewards, + voter_rewards, + staker_rewards, + (*point_value).clone(), + )); + } + + if (voter_rewards == 0 || staker_rewards == 0) && is_split { + // don't collect if we lose a whole lamport somewhere + // is_split means there should be tokens on both sides, + // uncool to move credits_observed if one side didn't get paid + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { + inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into()); + } + return None; + } + + Some(CalculatedStakeRewards { + staker_rewards, + voter_rewards, + new_credits_observed, + }) +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{points::null_tracer, stake_state::new_stake}, + solana_sdk::{native_token, pubkey::Pubkey}, + }; + + #[test] + fn test_stake_state_redeem_rewards() { + let mut vote_state = VoteState::default(); + // assume stake.stake() is right + // bootstrap means fully-vested stake at epoch 0 + let stake_lamports = 1; + let mut stake = new_stake( + stake_lamports, + &Pubkey::default(), + &vote_state, + std::u64::MAX, + ); + + // this one can't collect now, credits_observed == vote_state.credits() + assert_eq!( + None, + redeem_stake_rewards( + 0, + &mut stake, + &PointValue { + rewards: 1_000_000_000, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // put 2 credits in at epoch 0 + vote_state.increment_credits(0, 1); + vote_state.increment_credits(0, 1); + + // this one should be able to collect exactly 2 + assert_eq!( + Some((stake_lamports * 2, 0)), + redeem_stake_rewards( + 0, + &mut stake, + &PointValue { + rewards: 1, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + assert_eq!( + stake.delegation.stake, + stake_lamports + (stake_lamports * 2) + ); + assert_eq!(stake.credits_observed, 2); + } + + #[test] + fn test_stake_state_calculate_rewards() { + 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); + + // this one can't collect now, credits_observed == vote_state.credits() + assert_eq!( + None, + calculate_stake_rewards( + 0, + &stake, + &PointValue { + rewards: 1_000_000_000, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // put 2 credits in at epoch 0 + vote_state.increment_credits(0, 1); + vote_state.increment_credits(0, 1); + + // this one should be able to collect exactly 2 + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake * 2, + voter_rewards: 0, + new_credits_observed: 2, + }), + calculate_stake_rewards( + 0, + &stake, + &PointValue { + rewards: 2, + points: 2 // all his + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + stake.credits_observed = 1; + // this one should be able to collect exactly 1 (already observed one) + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake, + voter_rewards: 0, + new_credits_observed: 2, + }), + calculate_stake_rewards( + 0, + &stake, + &PointValue { + rewards: 1, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // put 1 credit in epoch 1 + vote_state.increment_credits(1, 1); + + stake.credits_observed = 2; + // this one should be able to collect the one just added + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake, + voter_rewards: 0, + new_credits_observed: 3, + }), + calculate_stake_rewards( + 1, + &stake, + &PointValue { + rewards: 2, + points: 2 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // put 1 credit in epoch 2 + vote_state.increment_credits(2, 1); + // this one should be able to collect 2 now + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake * 2, + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 2, + points: 2 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + stake.credits_observed = 0; + // this one should be able to collect everything from t=0 a warmed up stake of 2 + // (2 credits at stake of 1) + (1 credit at a stake of 2) + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake * 2 // epoch 0 + + stake.delegation.stake // epoch 1 + + stake.delegation.stake, // epoch 2 + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 4, + points: 4 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // same as above, but is a really small commission out of 32 bits, + // verify that None comes back on small redemptions where no one gets paid + vote_state.commission = 1; + assert_eq!( + None, // would be Some((0, 2 * 1 + 1 * 2, 4)), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 4, + points: 4 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + vote_state.commission = 99; + assert_eq!( + None, // would be Some((0, 2 * 1 + 1 * 2, 4)), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 4, + points: 4 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // now one with inflation disabled. no one gets paid, but we still need + // to advance the stake state's credits_observed field to prevent back- + // paying rewards when inflation is turned on. + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: 0, + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 0, + points: 4 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // credits_observed remains at previous level when vote_state credits are + // not advancing and inflation is disabled + stake.credits_observed = 4; + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: 0, + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 0, + points: 4 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + assert_eq!( + CalculatedStakePoints { + points: 0, + new_credits_observed: 4, + force_credits_update_with_skipped_reward: false, + }, + calculate_stake_points_and_credits( + &stake, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None + ) + ); + + // credits_observed is auto-rewinded when vote_state credits are assumed to have been + // recreated + stake.credits_observed = 1000; + // this is new behavior 1; return the post-recreation rewinded credits from the vote account + assert_eq!( + CalculatedStakePoints { + points: 0, + new_credits_observed: 4, + force_credits_update_with_skipped_reward: true, + }, + calculate_stake_points_and_credits( + &stake, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None + ) + ); + // this is new behavior 2; don't hint when credits both from stake and vote are identical + stake.credits_observed = 4; + assert_eq!( + CalculatedStakePoints { + points: 0, + new_credits_observed: 4, + force_credits_update_with_skipped_reward: false, + }, + calculate_stake_points_and_credits( + &stake, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None + ) + ); + + // get rewards and credits observed when not the activation epoch + vote_state.commission = 0; + stake.credits_observed = 3; + stake.delegation.activation_epoch = 1; + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: stake.delegation.stake, // epoch 2 + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 1, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + + // credits_observed is moved forward for the stake's activation epoch, + // and no rewards are perceived + stake.delegation.activation_epoch = 2; + stake.credits_observed = 3; + assert_eq!( + Some(CalculatedStakeRewards { + staker_rewards: 0, + voter_rewards: 0, + new_credits_observed: 4, + }), + calculate_stake_rewards( + 2, + &stake, + &PointValue { + rewards: 1, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + } + + #[test] + fn test_stake_state_calculate_points_with_typical_values() { + let vote_state = VoteState::default(); + + // bootstrap means fully-vested stake at epoch 0 with + // 10_000_000 SOL is a big but not unreasaonable stake + let stake = new_stake( + native_token::sol_to_lamports(10_000_000f64), + &Pubkey::default(), + &vote_state, + std::u64::MAX, + ); + + // this one can't collect now, credits_observed == vote_state.credits() + assert_eq!( + None, + calculate_stake_rewards( + 0, + &stake, + &PointValue { + rewards: 1_000_000_000, + points: 1 + }, + &vote_state, + &StakeHistory::default(), + null_tracer(), + None, + ) + ); + } +} diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 17232d083f06ec..eda238f93dd8d7 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -11,7 +11,7 @@ pub use solana_sdk::stake::state::*; use { solana_program_runtime::{ic_msg, invoke_context::InvokeContext}, solana_sdk::{ - account::{AccountSharedData, ReadableAccount, WritableAccount}, + account::{AccountSharedData, ReadableAccount}, account_utils::StateMut, clock::{Clock, Epoch}, feature_set::{self, FeatureSet}, @@ -30,44 +30,9 @@ use { }, }, solana_vote_program::vote_state::{self, VoteState, VoteStateVersions}, - std::{cmp::Ordering, collections::HashSet, convert::TryFrom}, + std::{collections::HashSet, convert::TryFrom}, }; -#[derive(Debug)] -pub enum SkippedReason { - DisabledInflation, - JustActivated, - TooEarlyUnfairSplit, - ZeroPoints, - ZeroPointValue, - ZeroReward, - ZeroCreditsAndReturnZero, - ZeroCreditsAndReturnCurrent, - ZeroCreditsAndReturnRewinded, -} - -impl From for InflationPointCalculationEvent { - fn from(reason: SkippedReason) -> Self { - InflationPointCalculationEvent::Skipped(reason) - } -} - -#[derive(Debug)] -pub enum InflationPointCalculationEvent { - CalculatedPoints(u64, u128, u128, u128), - SplitRewards(u64, u64, u64, PointValue), - EffectiveStakeAtRewardedEpoch(u64), - RentExemptReserve(u64), - Delegation(Delegation, Pubkey), - Commission(u8), - CreditsObserved(u64, Option), - Skipped(SkippedReason), -} - -pub(crate) fn null_tracer() -> Option { - None:: -} - // utility function, used by Stakes, tests pub fn from>(account: &T) -> Option { account.state().ok() @@ -179,300 +144,6 @@ pub(crate) fn new_stake( } } -/// captures a rewards round as lamports to be awarded -/// and the total points over which those lamports -/// are to be distributed -// basically read as rewards/points, but in integers instead of as an f64 -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PointValue { - pub rewards: u64, // lamports to split - pub points: u128, // over these points -} - -fn redeem_stake_rewards( - rewarded_epoch: Epoch, - stake: &mut Stake, - point_value: &PointValue, - vote_state: &VoteState, - stake_history: &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( - stake.credits_observed, - None, - )); - } - calculate_stake_rewards( - rewarded_epoch, - stake, - point_value, - 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 { - inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( - stake.credits_observed, - Some(calculated_stake_rewards.new_credits_observed), - )); - } - stake.credits_observed = calculated_stake_rewards.new_credits_observed; - stake.delegation.stake += calculated_stake_rewards.staker_rewards; - ( - calculated_stake_rewards.staker_rewards, - calculated_stake_rewards.voter_rewards, - ) - }) -} - -fn calculate_stake_points( - stake: &Stake, - vote_state: &VoteState, - stake_history: &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 -} - -#[derive(Debug, PartialEq, Eq)] -struct CalculatedStakePoints { - points: u128, - new_credits_observed: u64, - force_credits_update_with_skipped_reward: bool, -} - -/// for a given stake and vote_state, calculate how many -/// points were earned (credits * stake) and new value -/// for credits_observed were the points paid -fn calculate_stake_points_and_credits( - stake: &Stake, - new_vote_state: &VoteState, - stake_history: &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(); - // if there is no newer credits since observed, return no point - match credits_in_vote.cmp(&credits_in_stake) { - Ordering::Less => { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into()); - } - // Don't adjust stake.activation_epoch for simplicity: - // - generally fast-forwarding stake.activation_epoch forcibly (for - // artificial re-activation with re-warm-up) skews the stake - // history sysvar. And properly handling all the cases - // regarding deactivation epoch/warm-up/cool-down without - // introducing incentive skew is hard. - // - Conceptually, it should be acceptable for the staked SOLs at - // the recreated vote to receive rewards again immediately after - // rewind even if it looks like instant activation. That's - // because it must have passed the required warmed-up at least - // once in the past already - // - Also such a stake account remains to be a part of overall - // effective stake calculation even while the vote account is - // missing for (indefinite) time or remains to be pre-remove - // credits score. It should be treated equally to staking with - // delinquent validator with no differentiation. - - // hint with true to indicate some exceptional credits handling is needed - return CalculatedStakePoints { - points: 0, - new_credits_observed: credits_in_vote, - force_credits_update_with_skipped_reward: true, - }; - } - Ordering::Equal => { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into()); - } - // don't hint caller and return current value if credits remain unchanged (= delinquent) - return CalculatedStakePoints { - points: 0, - new_credits_observed: credits_in_stake, - force_credits_update_with_skipped_reward: false, - }; - } - Ordering::Greater => {} - } - - let mut points = 0; - let mut new_credits_observed = credits_in_stake; - - 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, - new_rate_activation_epoch, - )); - - // figure out how much this stake has seen that - // for which the vote account has a record - let earned_credits = if credits_in_stake < initial_epoch_credits { - // the staker observed the entire epoch - final_epoch_credits - initial_epoch_credits - } else if credits_in_stake < final_epoch_credits { - // the staker registered sometime during the epoch, partial credit - final_epoch_credits - new_credits_observed - } else { - // the staker has already observed or been redeemed this epoch - // or was activated after this epoch - 0 - }; - let earned_credits = u128::from(earned_credits); - - // don't want to assume anything about order of the iterator... - new_credits_observed = new_credits_observed.max(final_epoch_credits); - - // finally calculate points for this epoch - let earned_points = stake_amount * earned_credits; - points += earned_points; - - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints( - epoch, - stake_amount, - earned_credits, - earned_points, - )); - } - } - - CalculatedStakePoints { - points, - new_credits_observed, - force_credits_update_with_skipped_reward: false, - } -} - -#[derive(Debug, PartialEq, Eq)] -struct CalculatedStakeRewards { - staker_rewards: u64, - voter_rewards: u64, - new_credits_observed: u64, -} - -/// for a given stake and vote_state, calculate what distributions and what updates should be made -/// returns a tuple in the case of a payout of: -/// * staker_rewards to be distributed -/// * voter_rewards to be distributed -/// * new value for credits_observed in the stake -/// returns None if there's no payout or if any deserved payout is < 1 lamport -fn calculate_stake_rewards( - rewarded_epoch: Epoch, - stake: &Stake, - point_value: &PointValue, - vote_state: &VoteState, - stake_history: &StakeHistory, - inflation_point_calc_tracer: Option, - new_rate_activation_epoch: Option, -) -> Option { - // ensure to run to trigger (optional) inflation_point_calc_tracer - let CalculatedStakePoints { - points, - new_credits_observed, - mut force_credits_update_with_skipped_reward, - } = calculate_stake_points_and_credits( - stake, - vote_state, - stake_history, - inflation_point_calc_tracer.as_ref(), - new_rate_activation_epoch, - ); - - // Drive credits_observed forward unconditionally when rewards are disabled - // or when this is the stake's activation epoch - if point_value.rewards == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into()); - } - force_credits_update_with_skipped_reward = true; - } else if stake.delegation.activation_epoch == rewarded_epoch { - // not assert!()-ed; but points should be zero - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::JustActivated.into()); - } - force_credits_update_with_skipped_reward = true; - } - - if force_credits_update_with_skipped_reward { - return Some(CalculatedStakeRewards { - staker_rewards: 0, - voter_rewards: 0, - new_credits_observed, - }); - } - - if points == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into()); - } - return None; - } - if point_value.points == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into()); - } - return None; - } - - let rewards = points - .checked_mul(u128::from(point_value.rewards)) - .unwrap() - .checked_div(point_value.points) - .unwrap(); - - let rewards = u64::try_from(rewards).unwrap(); - - // don't bother trying to split if fractional lamports got truncated - if rewards == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::ZeroReward.into()); - } - return None; - } - let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards); - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards( - rewards, - voter_rewards, - staker_rewards, - (*point_value).clone(), - )); - } - - if (voter_rewards == 0 || staker_rewards == 0) && is_split { - // don't collect if we lose a whole lamport somewhere - // is_split means there should be tokens on both sides, - // uncool to move credits_observed if one side didn't get paid - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { - inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into()); - } - return None; - } - - Some(CalculatedStakeRewards { - staker_rewards, - voter_rewards, - new_credits_observed, - }) -} - pub fn initialize( stake_account: &mut BorrowedAccount, authorized: &Authorized, @@ -1577,78 +1248,6 @@ fn stake_weighted_credits_observed( } } -// utility function, used by runtime -// returns a tuple of (stakers_reward,voters_reward) -#[doc(hidden)] -pub fn redeem_rewards( - rewarded_epoch: Epoch, - stake_state: StakeStateV2, - stake_account: &mut AccountSharedData, - vote_state: &VoteState, - point_value: &PointValue, - stake_history: &StakeHistory, - inflation_point_calc_tracer: Option, - new_rate_activation_epoch: Option, -) -> Result<(u64, u64), InstructionError> { - if let StakeStateV2::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, - new_rate_activation_epoch, - )), - ); - inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve( - meta.rent_exempt_reserve, - )); - inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission( - vote_state.commission, - )); - } - - if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards( - rewarded_epoch, - &mut stake, - point_value, - vote_state, - stake_history, - inflation_point_calc_tracer, - new_rate_activation_epoch, - ) { - stake_account.checked_add_lamports(stakers_reward)?; - stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))?; - - Ok((stakers_reward, voters_reward)) - } else { - Err(StakeError::NoCreditsToRedeem.into()) - } - } else { - Err(InstructionError::InvalidAccountData) - } -} - -// utility function, used by runtime -#[doc(hidden)] -pub fn calculate_points( - stake_state: &StakeStateV2, - vote_state: &VoteState, - stake_history: &StakeHistory, - new_rate_activation_epoch: Option, -) -> Result { - if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state { - Ok(calculate_stake_points( - stake, - vote_state, - stake_history, - null_tracer(), - new_rate_activation_epoch, - )) - } else { - Err(InstructionError::InvalidAccountData) - } -} - pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64)); // utility function, used by runtime::Stakes, tests @@ -1804,7 +1403,6 @@ mod tests { solana_sdk::{ account::{create_account_shared_data_for_test, AccountSharedData}, epoch_schedule::EpochSchedule, - native_token, pubkey::Pubkey, stake::state::warmup_cooldown_rate, sysvar::{epoch_schedule, SysvarId}, @@ -2526,438 +2124,6 @@ mod tests { } } - #[test] - fn test_stake_state_redeem_rewards() { - let mut vote_state = VoteState::default(); - // assume stake.stake() is right - // bootstrap means fully-vested stake at epoch 0 - let stake_lamports = 1; - let mut stake = new_stake( - stake_lamports, - &Pubkey::default(), - &vote_state, - std::u64::MAX, - ); - - // this one can't collect now, credits_observed == vote_state.credits() - assert_eq!( - None, - redeem_stake_rewards( - 0, - &mut stake, - &PointValue { - rewards: 1_000_000_000, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // put 2 credits in at epoch 0 - vote_state.increment_credits(0, 1); - vote_state.increment_credits(0, 1); - - // this one should be able to collect exactly 2 - assert_eq!( - Some((stake_lamports * 2, 0)), - redeem_stake_rewards( - 0, - &mut stake, - &PointValue { - rewards: 1, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - assert_eq!( - stake.delegation.stake, - stake_lamports + (stake_lamports * 2) - ); - assert_eq!(stake.credits_observed, 2); - } - - #[test] - fn test_stake_state_calculate_points_with_typical_values() { - let mut vote_state = VoteState::default(); - - // bootstrap means fully-vested stake at epoch 0 with - // 10_000_000 SOL is a big but not unreasaonable stake - let stake = new_stake( - native_token::sol_to_lamports(10_000_000f64), - &Pubkey::default(), - &vote_state, - std::u64::MAX, - ); - - // this one can't collect now, credits_observed == vote_state.credits() - assert_eq!( - None, - calculate_stake_rewards( - 0, - &stake, - &PointValue { - rewards: 1_000_000_000, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - let epoch_slots: u128 = 14 * 24 * 3600 * 160; - // put 193,536,000 credits in at epoch 0, typical for a 14-day epoch - // this loop takes a few seconds... - for _ in 0..epoch_slots { - vote_state.increment_credits(0, 1); - } - - // no overflow on points - assert_eq!( - u128::from(stake.delegation.stake) * epoch_slots, - calculate_stake_points( - &stake, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None - ) - ); - } - - #[test] - fn test_stake_state_calculate_rewards() { - 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); - - // this one can't collect now, credits_observed == vote_state.credits() - assert_eq!( - None, - calculate_stake_rewards( - 0, - &stake, - &PointValue { - rewards: 1_000_000_000, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // put 2 credits in at epoch 0 - vote_state.increment_credits(0, 1); - vote_state.increment_credits(0, 1); - - // this one should be able to collect exactly 2 - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake * 2, - voter_rewards: 0, - new_credits_observed: 2, - }), - calculate_stake_rewards( - 0, - &stake, - &PointValue { - rewards: 2, - points: 2 // all his - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - stake.credits_observed = 1; - // this one should be able to collect exactly 1 (already observed one) - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake, - voter_rewards: 0, - new_credits_observed: 2, - }), - calculate_stake_rewards( - 0, - &stake, - &PointValue { - rewards: 1, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // put 1 credit in epoch 1 - vote_state.increment_credits(1, 1); - - stake.credits_observed = 2; - // this one should be able to collect the one just added - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake, - voter_rewards: 0, - new_credits_observed: 3, - }), - calculate_stake_rewards( - 1, - &stake, - &PointValue { - rewards: 2, - points: 2 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // put 1 credit in epoch 2 - vote_state.increment_credits(2, 1); - // this one should be able to collect 2 now - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake * 2, - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 2, - points: 2 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - stake.credits_observed = 0; - // this one should be able to collect everything from t=0 a warmed up stake of 2 - // (2 credits at stake of 1) + (1 credit at a stake of 2) - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake * 2 // epoch 0 - + stake.delegation.stake // epoch 1 - + stake.delegation.stake, // epoch 2 - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 4, - points: 4 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // same as above, but is a really small commission out of 32 bits, - // verify that None comes back on small redemptions where no one gets paid - vote_state.commission = 1; - assert_eq!( - None, // would be Some((0, 2 * 1 + 1 * 2, 4)), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 4, - points: 4 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - vote_state.commission = 99; - assert_eq!( - None, // would be Some((0, 2 * 1 + 1 * 2, 4)), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 4, - points: 4 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // now one with inflation disabled. no one gets paid, but we still need - // to advance the stake state's credits_observed field to prevent back- - // paying rewards when inflation is turned on. - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: 0, - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 0, - points: 4 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // credits_observed remains at previous level when vote_state credits are - // not advancing and inflation is disabled - stake.credits_observed = 4; - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: 0, - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 0, - points: 4 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - assert_eq!( - CalculatedStakePoints { - points: 0, - new_credits_observed: 4, - force_credits_update_with_skipped_reward: false, - }, - calculate_stake_points_and_credits( - &stake, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None - ) - ); - - // credits_observed is auto-rewinded when vote_state credits are assumed to have been - // recreated - stake.credits_observed = 1000; - // this is new behavior 1; return the post-recreation rewinded credits from the vote account - assert_eq!( - CalculatedStakePoints { - points: 0, - new_credits_observed: 4, - force_credits_update_with_skipped_reward: true, - }, - calculate_stake_points_and_credits( - &stake, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None - ) - ); - // this is new behavior 2; don't hint when credits both from stake and vote are identical - stake.credits_observed = 4; - assert_eq!( - CalculatedStakePoints { - points: 0, - new_credits_observed: 4, - force_credits_update_with_skipped_reward: false, - }, - calculate_stake_points_and_credits( - &stake, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None - ) - ); - - // get rewards and credits observed when not the activation epoch - vote_state.commission = 0; - stake.credits_observed = 3; - stake.delegation.activation_epoch = 1; - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: stake.delegation.stake, // epoch 2 - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 1, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - - // credits_observed is moved forward for the stake's activation epoch, - // and no rewards are perceived - stake.delegation.activation_epoch = 2; - stake.credits_observed = 3; - assert_eq!( - Some(CalculatedStakeRewards { - staker_rewards: 0, - voter_rewards: 0, - new_credits_observed: 4, - }), - calculate_stake_rewards( - 2, - &stake, - &PointValue { - rewards: 1, - points: 1 - }, - &vote_state, - &StakeHistory::default(), - null_tracer(), - None, - ) - ); - } - #[test] fn test_lockup_is_expired() { let custodian = solana_sdk::pubkey::new_rand(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d72e3771cb4408..84c9e2093ebf50 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -156,8 +156,9 @@ use { }, transaction_context::{TransactionAccount, TransactionReturnData}, }, - solana_stake_program::stake_state::{ - self, InflationPointCalculationEvent, PointValue, StakeStateV2, + solana_stake_program::{ + points::{InflationPointCalculationEvent, PointValue}, + stake_state::StakeStateV2, }, solana_svm::{ account_loader::{TransactionCheckResult, TransactionLoadResult}, @@ -2989,7 +2990,7 @@ impl Bank { return 0; }; - stake_state::calculate_points( + solana_stake_program::points::calculate_points( stake_account.stake_state(), vote_state, stake_history, @@ -3026,7 +3027,7 @@ impl Bank { delegations .par_iter() .map(|(_stake_pubkey, stake_account)| { - stake_state::calculate_points( + solana_stake_program::points::calculate_points( stake_account.stake_state(), vote_state, stake_history, @@ -3106,7 +3107,7 @@ impl Bank { let pre_lamport = stake_account.lamports(); - let redeemed = stake_state::redeem_rewards( + let redeemed = solana_stake_program::rewards::redeem_rewards( rewarded_epoch, stake_state, &mut stake_account, @@ -3154,7 +3155,7 @@ impl Bank { }); } else { debug!( - "stake_state::redeem_rewards() failed for {}: {:?}", + "solana_stake_program::rewards::redeem_rewards() failed for {}: {:?}", stake_pubkey, redeemed ); } @@ -3225,7 +3226,7 @@ impl Bank { }); let (mut stake_account, stake_state) = <(AccountSharedData, StakeStateV2)>::from(stake_account); - let redeemed = stake_state::redeem_rewards( + let redeemed = solana_stake_program::rewards::redeem_rewards( rewarded_epoch, stake_state, &mut stake_account, @@ -3261,7 +3262,7 @@ impl Bank { }); } else { debug!( - "stake_state::redeem_rewards() failed for {}: {:?}", + "solana_stake_program::rewards::redeem_rewards() failed for {}: {:?}", stake_pubkey, redeemed ); }