Refactor stake program into solana_program (#17906)

* Move stake state / instructions into solana_program

* Update account-decoder

* Update cli and runtime

* Update all other parts

* Commit Cargo.lock changes in programs/bpf

* Update cli stake instruction import

* Allow integer arithmetic

* Update ABI digest

* Bump rust mem instruction count

* Remove useless structs

* Move stake::id() -> stake::program::id()

* Re-export from solana_sdk and mark deprecated

* Address feedback

* Run cargo fmt
This commit is contained in:
Jon Cinque
2021-06-15 18:04:00 +02:00
committed by GitHub
parent 36b09db2d1
commit 1b1d34da59
59 changed files with 1765 additions and 1711 deletions

View File

@ -43,6 +43,7 @@ pub mod serialize_utils;
pub mod short_vec;
pub mod slot_hashes;
pub mod slot_history;
pub mod stake;
pub mod stake_history;
pub mod system_instruction;
pub mod system_program;

View File

@ -0,0 +1,28 @@
//! config for staking
//! carries variables that the stake program cares about
use serde_derive::{Deserialize, Serialize};
// stake config ID
crate::declare_id!("StakeConfig11111111111111111111111111111111");
// means that no more than RATE of current effective stake may be added or subtracted per
// epoch
pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config {
/// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake
pub warmup_cooldown_rate: f64,
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
pub slash_penalty: u8,
}
impl Default for Config {
fn default() -> Self {
Self {
warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
slash_penalty: DEFAULT_SLASH_PENALTY,
}
}
}

View File

@ -0,0 +1,508 @@
use {
crate::stake::{
config,
program::id,
state::{Authorized, Lockup, StakeAuthorize, StakeState},
},
crate::{
clock::{Epoch, UnixTimestamp},
decode_error::DecodeError,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_instruction, sysvar,
},
log::*,
num_derive::{FromPrimitive, ToPrimitive},
serde_derive::{Deserialize, Serialize},
thiserror::Error,
};
/// Reasons the stake might have had an error
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
pub enum StakeError {
#[error("not enough credits to redeem")]
NoCreditsToRedeem,
#[error("lockup has not yet expired")]
LockupInForce,
#[error("stake already deactivated")]
AlreadyDeactivated,
#[error("one re-delegation permitted per epoch")]
TooSoonToRedelegate,
#[error("split amount is more than is staked")]
InsufficientStake,
#[error("stake account with transient stake cannot be merged")]
MergeTransientStake,
#[error("stake account merge failed due to different authority, lockups or state")]
MergeMismatch,
#[error("custodian address not present")]
CustodianMissing,
#[error("custodian signature not present")]
CustodianSignatureMissing,
}
impl<E> DecodeError<E> for StakeError {
fn type_of() -> &'static str {
"StakeError"
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum StakeInstruction {
/// Initialize a stake with lockup and authorization information
///
/// # Account references
/// 0. [WRITE] Uninitialized stake account
/// 1. [] Rent sysvar
///
/// Authorized carries pubkeys that must sign staker transactions
/// and withdrawer transactions.
/// Lockup carries information about withdrawal restrictions
Initialize(Authorized, Lockup),
/// Authorize a key to manage stake or withdrawal
///
/// # Account references
/// 0. [WRITE] Stake account to be updated
/// 1. [] Clock sysvar
/// 2. [SIGNER] The stake or withdraw authority
/// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before
/// lockup expiration
Authorize(Pubkey, StakeAuthorize),
/// Delegate a stake to a particular vote account
///
/// # Account references
/// 0. [WRITE] Initialized stake account to be delegated
/// 1. [] Vote account to which this stake will be delegated
/// 2. [] Clock sysvar
/// 3. [] Stake history sysvar that carries stake warmup/cooldown history
/// 4. [] Address of config account that carries stake config
/// 5. [SIGNER] Stake authority
///
/// The entire balance of the staking account is staked. DelegateStake
/// can be called multiple times, but re-delegation is delayed
/// by one epoch
DelegateStake,
/// Split u64 tokens and stake off a stake account into another stake account.
///
/// # Account references
/// 0. [WRITE] Stake account to be split; must be in the Initialized or Stake state
/// 1. [WRITE] Uninitialized stake account that will take the split-off amount
/// 2. [SIGNER] Stake authority
Split(u64),
/// Withdraw unstaked lamports from the stake account
///
/// # Account references
/// 0. [WRITE] Stake account from which to withdraw
/// 1. [WRITE] Recipient account
/// 2. [] Clock sysvar
/// 3. [] Stake history sysvar that carries stake warmup/cooldown history
/// 4. [SIGNER] Withdraw authority
/// 5. Optional: [SIGNER] Lockup authority, if before lockup expiration
///
/// The u64 is the portion of the stake account balance to be withdrawn,
/// must be `<= StakeAccount.lamports - staked_lamports`.
Withdraw(u64),
/// Deactivates the stake in the account
///
/// # Account references
/// 0. [WRITE] Delegated stake account
/// 1. [] Clock sysvar
/// 2. [SIGNER] Stake authority
Deactivate,
/// Set stake lockup
///
/// If a lockup is not active, the withdraw authority may set a new lockup
/// If a lockup is active, the lockup custodian may update the lockup parameters
///
/// # Account references
/// 0. [WRITE] Initialized stake account
/// 1. [SIGNER] Lockup authority or withdraw authority
SetLockup(LockupArgs),
/// Merge two stake accounts.
///
/// Both accounts must have identical lockup and authority keys. A merge
/// is possible between two stakes in the following states with no additional
/// conditions:
///
/// * two deactivated stakes
/// * an inactive stake into an activating stake during its activation epoch
///
/// For the following cases, the voter pubkey and vote credits observed must match:
///
/// * two activated stakes
/// * two activating accounts that share an activation epoch, during the activation epoch
///
/// All other combinations of stake states will fail to merge, including all
/// "transient" states, where a stake is activating or deactivating with a
/// non-zero effective stake.
///
/// # Account references
/// 0. [WRITE] Destination stake account for the merge
/// 1. [WRITE] Source stake account for to merge. This account will be drained
/// 2. [] Clock sysvar
/// 3. [] Stake history sysvar that carries stake warmup/cooldown history
/// 4. [SIGNER] Stake authority
Merge,
/// Authorize a key to manage stake or withdrawal with a derived key
///
/// # Account references
/// 0. [WRITE] Stake account to be updated
/// 1. [SIGNER] Base key of stake or withdraw authority
/// 2. [] Clock sysvar
/// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before
/// lockup expiration
AuthorizeWithSeed(AuthorizeWithSeedArgs),
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct LockupArgs {
pub unix_timestamp: Option<UnixTimestamp>,
pub epoch: Option<Epoch>,
pub custodian: Option<Pubkey>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct AuthorizeWithSeedArgs {
pub new_authorized_pubkey: Pubkey,
pub stake_authorize: StakeAuthorize,
pub authority_seed: String,
pub authority_owner: Pubkey,
}
pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
Instruction::new_with_bincode(
id(),
&StakeInstruction::Initialize(*authorized, *lockup),
vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
)
}
pub fn create_account_with_seed(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
base: &Pubkey,
seed: &str,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> Vec<Instruction> {
vec![
system_instruction::create_account_with_seed(
from_pubkey,
stake_pubkey,
base,
seed,
lamports,
std::mem::size_of::<StakeState>() as u64,
&id(),
),
initialize(stake_pubkey, authorized, lockup),
]
}
pub fn create_account(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> Vec<Instruction> {
vec![
system_instruction::create_account(
from_pubkey,
stake_pubkey,
lamports,
std::mem::size_of::<StakeState>() as u64,
&id(),
),
initialize(stake_pubkey, authorized, lockup),
]
}
fn _split(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
lamports: u64,
split_stake_pubkey: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new(*split_stake_pubkey, false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
Instruction::new_with_bincode(id(), &StakeInstruction::Split(lamports), account_metas)
}
pub fn split(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
lamports: u64,
split_stake_pubkey: &Pubkey,
) -> Vec<Instruction> {
vec![
system_instruction::allocate(split_stake_pubkey, std::mem::size_of::<StakeState>() as u64),
system_instruction::assign(split_stake_pubkey, &id()),
_split(
stake_pubkey,
authorized_pubkey,
lamports,
split_stake_pubkey,
),
]
}
pub fn split_with_seed(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
lamports: u64,
split_stake_pubkey: &Pubkey, // derived using create_with_seed()
base: &Pubkey, // base
seed: &str, // seed
) -> Vec<Instruction> {
vec![
system_instruction::allocate_with_seed(
split_stake_pubkey,
base,
seed,
std::mem::size_of::<StakeState>() as u64,
&id(),
),
_split(
stake_pubkey,
authorized_pubkey,
lamports,
split_stake_pubkey,
),
]
}
pub fn merge(
destination_stake_pubkey: &Pubkey,
source_stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
) -> Vec<Instruction> {
let account_metas = vec![
AccountMeta::new(*destination_stake_pubkey, false),
AccountMeta::new(*source_stake_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
vec![Instruction::new_with_bincode(
id(),
&StakeInstruction::Merge,
account_metas,
)]
}
pub fn create_account_and_delegate_stake(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> Vec<Instruction> {
let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports);
instructions.push(delegate_stake(
stake_pubkey,
&authorized.staker,
vote_pubkey,
));
instructions
}
pub fn create_account_with_seed_and_delegate_stake(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
base: &Pubkey,
seed: &str,
vote_pubkey: &Pubkey,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> Vec<Instruction> {
let mut instructions = create_account_with_seed(
from_pubkey,
stake_pubkey,
base,
seed,
authorized,
lockup,
lamports,
);
instructions.push(delegate_stake(
stake_pubkey,
&authorized.staker,
vote_pubkey,
));
instructions
}
pub fn authorize(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
new_authorized_pubkey: &Pubkey,
stake_authorize: StakeAuthorize,
custodian_pubkey: Option<&Pubkey>,
) -> Instruction {
let mut account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
if let Some(custodian_pubkey) = custodian_pubkey {
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
}
Instruction::new_with_bincode(
id(),
&StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
account_metas,
)
}
pub fn authorize_with_seed(
stake_pubkey: &Pubkey,
authority_base: &Pubkey,
authority_seed: String,
authority_owner: &Pubkey,
new_authorized_pubkey: &Pubkey,
stake_authorize: StakeAuthorize,
custodian_pubkey: Option<&Pubkey>,
) -> Instruction {
let mut account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(*authority_base, true),
AccountMeta::new_readonly(sysvar::clock::id(), false),
];
if let Some(custodian_pubkey) = custodian_pubkey {
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
}
let args = AuthorizeWithSeedArgs {
new_authorized_pubkey: *new_authorized_pubkey,
stake_authorize,
authority_seed,
authority_owner: *authority_owner,
};
Instruction::new_with_bincode(
id(),
&StakeInstruction::AuthorizeWithSeed(args),
account_metas,
)
}
pub fn delegate_stake(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(config::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas)
}
pub fn withdraw(
stake_pubkey: &Pubkey,
withdrawer_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
custodian_pubkey: Option<&Pubkey>,
) -> Instruction {
let mut account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new(*to_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(*withdrawer_pubkey, true),
];
if let Some(custodian_pubkey) = custodian_pubkey {
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
}
Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas)
}
pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
}
pub fn set_lockup(
stake_pubkey: &Pubkey,
lockup: &LockupArgs,
custodian_pubkey: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(*custodian_pubkey, true),
];
Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instruction::InstructionError;
#[test]
fn test_custom_error_decode() {
use num_traits::FromPrimitive;
fn pretty_err<T>(err: InstructionError) -> String
where
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
{
if let InstructionError::Custom(code) = err {
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
format!(
"{:?}: {}::{:?} - {}",
err,
T::type_of(),
specific_error,
specific_error,
)
} else {
"".to_string()
}
}
assert_eq!(
"Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
)
}
}

View File

@ -0,0 +1,7 @@
pub mod config;
pub mod instruction;
pub mod state;
pub mod program {
crate::declare_id!("Stake11111111111111111111111111111111111111");
}

View File

@ -0,0 +1,533 @@
#![allow(clippy::integer_arithmetic)]
use {
crate::{
clock::{Clock, Epoch, UnixTimestamp},
instruction::InstructionError,
pubkey::Pubkey,
rent::Rent,
stake::{
config::Config,
instruction::{LockupArgs, StakeError},
},
stake_history::StakeHistory,
},
std::collections::HashSet,
};
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
#[allow(clippy::large_enum_variant)]
pub enum StakeState {
Uninitialized,
Initialized(Meta),
Stake(Meta, Stake),
RewardsPool,
}
impl Default for StakeState {
fn default() -> Self {
StakeState::Uninitialized
}
}
impl StakeState {
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
rent.minimum_balance(std::mem::size_of::<StakeState>())
}
pub fn stake(&self) -> Option<Stake> {
match self {
StakeState::Stake(_meta, stake) => Some(*stake),
_ => None,
}
}
pub fn delegation(&self) -> Option<Delegation> {
match self {
StakeState::Stake(_meta, stake) => Some(stake.delegation),
_ => None,
}
}
pub fn authorized(&self) -> Option<Authorized> {
match self {
StakeState::Stake(meta, _stake) => Some(meta.authorized),
StakeState::Initialized(meta) => Some(meta.authorized),
_ => None,
}
}
pub fn lockup(&self) -> Option<Lockup> {
self.meta().map(|meta| meta.lockup)
}
pub fn meta(&self) -> Option<Meta> {
match self {
StakeState::Stake(meta, _stake) => Some(*meta),
StakeState::Initialized(meta) => Some(*meta),
_ => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub enum StakeAuthorize {
Staker,
Withdrawer,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub struct Lockup {
/// UnixTimestamp at which this stake will allow withdrawal, unless the
/// transaction is signed by the custodian
pub unix_timestamp: UnixTimestamp,
/// epoch height at which this stake will allow withdrawal, unless the
/// transaction is signed by the custodian
pub epoch: Epoch,
/// custodian signature on a transaction exempts the operation from
/// lockup constraints
pub custodian: Pubkey,
}
impl Lockup {
pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool {
if custodian == Some(&self.custodian) {
return false;
}
self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub struct Authorized {
pub staker: Pubkey,
pub withdrawer: Pubkey,
}
impl Authorized {
pub fn auto(authorized: &Pubkey) -> Self {
Self {
staker: *authorized,
withdrawer: *authorized,
}
}
pub fn check(
&self,
signers: &HashSet<Pubkey>,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
match stake_authorize {
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
_ => Err(InstructionError::MissingRequiredSignature),
}
}
pub fn authorize(
&mut self,
signers: &HashSet<Pubkey>,
new_authorized: &Pubkey,
stake_authorize: StakeAuthorize,
lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>,
) -> Result<(), InstructionError> {
match stake_authorize {
StakeAuthorize::Staker => {
// Allow either the staker or the withdrawer to change the staker key
if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
return Err(InstructionError::MissingRequiredSignature);
}
self.staker = *new_authorized
}
StakeAuthorize::Withdrawer => {
if let Some((lockup, clock, custodian)) = lockup_custodian_args {
if lockup.is_in_force(&clock, None) {
match custodian {
None => {
return Err(StakeError::CustodianMissing.into());
}
Some(custodian) => {
if !signers.contains(custodian) {
return Err(StakeError::CustodianSignatureMissing.into());
}
if lockup.is_in_force(&clock, Some(custodian)) {
return Err(StakeError::LockupInForce.into());
}
}
}
}
}
self.check(signers, stake_authorize)?;
self.withdrawer = *new_authorized
}
}
Ok(())
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub struct Meta {
pub rent_exempt_reserve: u64,
pub authorized: Authorized,
pub lockup: Lockup,
}
impl Meta {
pub fn set_lockup(
&mut self,
lockup: &LockupArgs,
signers: &HashSet<Pubkey>,
clock: Option<&Clock>,
) -> Result<(), InstructionError> {
match clock {
None => {
// pre-stake_program_v4 behavior: custodian can set lockups at any time
if !signers.contains(&self.lockup.custodian) {
return Err(InstructionError::MissingRequiredSignature);
}
}
Some(clock) => {
// post-stake_program_v4 behavior:
// * custodian can update the lockup while in force
// * withdraw authority can set a new lockup
//
if self.lockup.is_in_force(clock, None) {
if !signers.contains(&self.lockup.custodian) {
return Err(InstructionError::MissingRequiredSignature);
}
} else if !signers.contains(&self.authorized.withdrawer) {
return Err(InstructionError::MissingRequiredSignature);
}
}
}
if let Some(unix_timestamp) = lockup.unix_timestamp {
self.lockup.unix_timestamp = unix_timestamp;
}
if let Some(epoch) = lockup.epoch {
self.lockup.epoch = epoch;
}
if let Some(custodian) = lockup.custodian {
self.lockup.custodian = custodian;
}
Ok(())
}
pub fn rewrite_rent_exempt_reserve(
&mut self,
rent: &Rent,
data_len: usize,
) -> Option<(u64, u64)> {
let corrected_rent_exempt_reserve = rent.minimum_balance(data_len);
if corrected_rent_exempt_reserve != self.rent_exempt_reserve {
// We forcibly update rent_excempt_reserve even
// if rent_exempt_reserve > account_balance, hoping user might restore
// rent_exempt status by depositing.
let (old, new) = (self.rent_exempt_reserve, corrected_rent_exempt_reserve);
self.rent_exempt_reserve = corrected_rent_exempt_reserve;
Some((old, new))
} else {
None
}
}
pub fn auto(authorized: &Pubkey) -> Self {
Self {
authorized: Authorized::auto(authorized),
..Meta::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub struct Delegation {
/// to whom the stake is delegated
pub voter_pubkey: Pubkey,
/// activated stake amount, set at delegate() time
pub stake: u64,
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
pub activation_epoch: Epoch,
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub deactivation_epoch: Epoch,
/// how much stake we can activate per-epoch as a fraction of currently effective stake
pub warmup_cooldown_rate: f64,
}
impl Default for Delegation {
fn default() -> Self {
Self {
voter_pubkey: Pubkey::default(),
stake: 0,
activation_epoch: 0,
deactivation_epoch: std::u64::MAX,
warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
}
}
}
impl Delegation {
pub fn new(
voter_pubkey: &Pubkey,
stake: u64,
activation_epoch: Epoch,
warmup_cooldown_rate: f64,
) -> Self {
Self {
voter_pubkey: *voter_pubkey,
stake,
activation_epoch,
warmup_cooldown_rate,
..Delegation::default()
}
}
pub fn is_bootstrap(&self) -> bool {
self.activation_epoch == std::u64::MAX
}
pub fn stake(
&self,
epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 {
self.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate)
.0
}
// returned tuple is (effective, activating, deactivating) stake
#[allow(clippy::comparison_chain)]
pub fn stake_activating_and_deactivating(
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64, u64) {
let delegated_stake = self.stake;
// first, calculate an effective and activating stake
let (effective_stake, activating_stake) =
self.stake_and_activating(target_epoch, history, fix_stake_deactivate);
// then de-activate some portion if necessary
if target_epoch < self.deactivation_epoch {
// not deactivated
(effective_stake, activating_stake, 0)
} else if target_epoch == self.deactivation_epoch {
// can only deactivate what's activated
(effective_stake, 0, effective_stake.min(delegated_stake))
} else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
history.and_then(|history| {
history
.get(&self.deactivation_epoch)
.map(|cluster_stake_at_deactivation_epoch| {
(
history,
self.deactivation_epoch,
cluster_stake_at_deactivation_epoch,
)
})
})
{
// target_epoch > self.deactivation_epoch
// loop from my deactivation epoch until the target epoch
// current effective stake is updated using its previous epoch's cluster stake
let mut current_epoch;
let mut current_effective_stake = effective_stake;
loop {
current_epoch = prev_epoch + 1;
// if there is no deactivating stake at prev epoch, we should have been
// fully undelegated at this moment
if prev_cluster_stake.deactivating == 0 {
break;
}
// I'm trying to get to zero, how much of the deactivation in stake
// this account is entitled to take
let weight =
current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
// portion of newly not-effective cluster stake I'm entitled to at current epoch
let newly_not_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
let newly_not_effective_stake =
((weight * newly_not_effective_cluster_stake) as u64).max(1);
current_effective_stake =
current_effective_stake.saturating_sub(newly_not_effective_stake);
if current_effective_stake == 0 {
break;
}
if current_epoch >= target_epoch {
break;
}
if let Some(current_cluster_stake) = history.get(&current_epoch) {
prev_epoch = current_epoch;
prev_cluster_stake = current_cluster_stake;
} else {
break;
}
}
// deactivating stake should equal to all of currently remaining effective stake
(current_effective_stake, 0, current_effective_stake)
} else {
// no history or I've dropped out of history, so assume fully deactivated
(0, 0, 0)
}
}
// returned tuple is (effective, activating) stake
fn stake_and_activating(
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64) {
let delegated_stake = self.stake;
if self.is_bootstrap() {
// fully effective immediately
(delegated_stake, 0)
} else if fix_stake_deactivate && self.activation_epoch == self.deactivation_epoch {
// activated but instantly deactivated; no stake at all regardless of target_epoch
// this must be after the bootstrap check and before all-is-activating check
(0, 0)
} else if target_epoch == self.activation_epoch {
// all is activating
(0, delegated_stake)
} else if target_epoch < self.activation_epoch {
// not yet enabled
(0, 0)
} else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
history.and_then(|history| {
history
.get(&self.activation_epoch)
.map(|cluster_stake_at_activation_epoch| {
(
history,
self.activation_epoch,
cluster_stake_at_activation_epoch,
)
})
})
{
// target_epoch > self.activation_epoch
// loop from my activation epoch until the target epoch summing up my entitlement
// current effective stake is updated using its previous epoch's cluster stake
let mut current_epoch;
let mut current_effective_stake = 0;
loop {
current_epoch = prev_epoch + 1;
// if there is no activating stake at prev epoch, we should have been
// fully effective at this moment
if prev_cluster_stake.activating == 0 {
break;
}
// how much of the growth in stake this account is
// entitled to take
let remaining_activating_stake = delegated_stake - current_effective_stake;
let weight =
remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
// portion of newly effective cluster stake I'm entitled to at current epoch
let newly_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
let newly_effective_stake =
((weight * newly_effective_cluster_stake) as u64).max(1);
current_effective_stake += newly_effective_stake;
if current_effective_stake >= delegated_stake {
current_effective_stake = delegated_stake;
break;
}
if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
break;
}
if let Some(current_cluster_stake) = history.get(&current_epoch) {
prev_epoch = current_epoch;
prev_cluster_stake = current_cluster_stake;
} else {
break;
}
}
(
current_effective_stake,
delegated_stake - current_effective_stake,
)
} else {
// no history or I've dropped out of history, so assume fully effective
(delegated_stake, 0)
}
}
pub fn rewrite_stake(
&mut self,
account_balance: u64,
rent_exempt_balance: u64,
) -> Option<(u64, u64)> {
// note that this will intentionally overwrite innocent
// deactivated-then-immeditealy-withdrawn stake accounts as well
// this is chosen to minimize the risks from complicated logic,
// over some unneeded rewrites
let corrected_stake = account_balance.saturating_sub(rent_exempt_balance);
if self.stake != corrected_stake {
// this could result in creating a 0-staked account;
// rewards and staking calc can handle it.
let (old, new) = (self.stake, corrected_stake);
self.stake = corrected_stake;
Some((old, new))
} else {
None
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub struct Stake {
pub delegation: Delegation,
/// credits observed is credits from vote account state when delegated or redeemed
pub credits_observed: u64,
}
impl Stake {
pub fn stake(
&self,
epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 {
self.delegation.stake(epoch, history, fix_stake_deactivate)
}
pub fn split(
&mut self,
remaining_stake_delta: u64,
split_stake_amount: u64,
) -> Result<Self, StakeError> {
if remaining_stake_delta > self.delegation.stake {
return Err(StakeError::InsufficientStake);
}
self.delegation.stake -= remaining_stake_delta;
let new = Self {
delegation: Delegation {
stake: split_stake_amount,
..self.delegation
},
..*self
};
Ok(new)
}
pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
if self.delegation.deactivation_epoch != std::u64::MAX {
Err(StakeError::AlreadyDeactivated)
} else {
self.delegation.deactivation_epoch = epoch;
Ok(())
}
}
}