diff --git a/interface/src/config.rs b/interface/src/config.rs deleted file mode 100644 index 6b7ddfc1..00000000 --- a/interface/src/config.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! config for staking -//! carries variables that the stake program cares about - -#[deprecated( - since = "1.16.7", - note = "Please use `solana_sdk::stake::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE}` instead" -)] -pub use super::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE}; -use serde_derive::{Deserialize, Serialize}; - -// stake config ID -crate::declare_deprecated_id!("StakeConfig11111111111111111111111111111111"); - -#[deprecated( - since = "1.16.7", - note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead" -)] -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -pub struct Config { - /// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake - pub warmup_cooldown_rate: f64, - /// percentage of stake lost when slash, expressed as a portion of 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, - } - } -} diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs deleted file mode 100644 index fff1811b..00000000 --- a/interface/src/instruction.rs +++ /dev/null @@ -1,954 +0,0 @@ -// Remove the following `allow` when the `Redelegate` variant is renamed to -// `Unused` starting from v3. -// Required to avoid warnings from uses of deprecated types during trait derivations. -#![allow(deprecated)] - -use { - crate::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - pubkey::Pubkey, - stake::{ - config, - program::id, - state::{Authorized, Lockup, StakeAuthorize, StakeStateV2}, - }, - system_instruction, sysvar, - }, - log::*, - num_derive::{FromPrimitive, ToPrimitive}, - serde_derive::{Deserialize, Serialize}, - solana_clock::{Epoch, UnixTimestamp}, - solana_decode_error::DecodeError, - thiserror::Error, -}; - -/// Reasons the stake might have had an error -#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum StakeError { - // 0 - #[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, - - // 5 - #[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, - - #[error("insufficient voting activity in the reference vote account")] - InsufficientReferenceVotes, - - // 10 - #[error("stake account is not delegated to the provided vote account")] - VoteAddressMismatch, - - #[error( - "stake account has not been delinquent for the minimum epochs required for deactivation" - )] - MinimumDelinquentEpochsForDeactivationNotMet, - - #[error("delegation amount is less than the minimum")] - InsufficientDelegation, - - #[error("stake account with transient or inactive stake cannot be redelegated")] - RedelegateTransientOrInactiveStake, - - #[error("stake redelegation to the same vote account is not permitted")] - RedelegateToSameVoteAccount, - - // 15 - #[error("redelegated stake must be fully activated before deactivation")] - RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted, - - #[error("stake action is not permitted while the epoch rewards period is active")] - EpochRewardsActive, -} - -impl From for ProgramError { - fn from(e: StakeError) -> Self { - ProgramError::Custom(e as u32) - } -} - -impl DecodeError for StakeError { - fn type_of() -> &'static str { - "StakeError" - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, 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. `[]` Unused account, formerly the 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), - - /// Initialize a stake with authorization information - /// - /// This instruction is similar to `Initialize` except that the withdraw authority - /// must be a signer, and no lockup is applied to the account. - /// - /// # Account references - /// 0. `[WRITE]` Uninitialized stake account - /// 1. `[]` Rent sysvar - /// 2. `[]` The stake authority - /// 3. `[SIGNER]` The withdraw authority - /// - InitializeChecked, - - /// Authorize a key to manage stake or withdrawal - /// - /// This instruction behaves like `Authorize` with the additional requirement that the new - /// stake or withdraw authority must also be a signer. - /// - /// # Account references - /// 0. `[WRITE]` Stake account to be updated - /// 1. `[]` Clock sysvar - /// 2. `[SIGNER]` The stake or withdraw authority - /// 3. `[SIGNER]` The new stake or withdraw authority - /// 4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before - /// lockup expiration - AuthorizeChecked(StakeAuthorize), - - /// Authorize a key to manage stake or withdrawal with a derived key - /// - /// This instruction behaves like `AuthorizeWithSeed` with the additional requirement that - /// the new stake or withdraw authority must also be a signer. - /// - /// # Account references - /// 0. `[WRITE]` Stake account to be updated - /// 1. `[SIGNER]` Base key of stake or withdraw authority - /// 2. `[]` Clock sysvar - /// 3. `[SIGNER]` The new stake or withdraw authority - /// 4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before - /// lockup expiration - AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs), - - /// Set stake lockup - /// - /// This instruction behaves like `SetLockup` with the additional requirement that - /// the new lockup authority also be a signer. - /// - /// 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 - /// 2. Optional: `[SIGNER]` New lockup authority - SetLockupChecked(LockupCheckedArgs), - - /// Get the minimum stake delegation, in lamports - /// - /// # Account references - /// None - /// - /// Returns the minimum delegation as a little-endian encoded u64 value. - /// Programs can use the [`get_minimum_delegation()`] helper function to invoke and - /// retrieve the return value for this instruction. - /// - /// [`get_minimum_delegation()`]: super::tools::get_minimum_delegation - GetMinimumDelegation, - - /// Deactivate stake delegated to a vote account that has been delinquent for at least - /// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs. - /// - /// No signer is required for this instruction as it is a common good to deactivate abandoned - /// stake. - /// - /// # Account references - /// 0. `[WRITE]` Delegated stake account - /// 1. `[]` Delinquent vote account for the delegated stake account - /// 2. `[]` Reference vote account that has voted at least once in the last - /// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs - DeactivateDelinquent, - - /// Redelegate activated stake to another vote account. - /// - /// Upon success: - /// * the balance of the delegated stake account will be reduced to the undelegated amount in - /// the account (rent exempt minimum and any additional lamports not part of the delegation), - /// and scheduled for deactivation. - /// * the provided uninitialized stake account will receive the original balance of the - /// delegated stake account, minus the rent exempt minimum, and scheduled for activation to - /// the provided vote account. Any existing lamports in the uninitialized stake account - /// will also be included in the re-delegation. - /// - /// # Account references - /// 0. `[WRITE]` Delegated stake account to be redelegated. The account must be fully - /// activated and carry a balance greater than or equal to the minimum delegation amount - /// plus rent exempt minimum - /// 1. `[WRITE]` Uninitialized stake account that will hold the redelegated stake - /// 2. `[]` Vote account to which this stake will be re-delegated - /// 3. `[]` Unused account, formerly the stake config - /// 4. `[SIGNER]` Stake authority - /// - #[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] - Redelegate, - - /// Move stake between accounts with the same authorities and lockups, using Staker authority. - /// - /// The source account must be fully active. If its entire delegation is moved, it immediately - /// becomes inactive. Otherwise, at least the minimum delegation of active stake must remain. - /// - /// The destination account must be fully active or fully inactive. If it is active, it must - /// be delegated to the same vote account as the source. If it is inactive, it - /// immediately becomes active, and must contain at least the minimum delegation. The - /// destination must be pre-funded with the rent-exempt reserve. - /// - /// This instruction only affects or moves active stake. Additional unstaked lamports are never - /// moved, activated, or deactivated, and accounts are never deallocated. - /// - /// # Account references - /// 0. `[WRITE]` Active source stake account - /// 1. `[WRITE]` Active or inactive destination stake account - /// 2. `[SIGNER]` Stake authority - /// - /// The u64 is the portion of the stake to move, which may be the entire delegation - MoveStake(u64), - - /// Move unstaked lamports between accounts with the same authorities and lockups, using Staker - /// authority. - /// - /// The source account must be fully active or fully inactive. The destination may be in any - /// mergeable state (active, inactive, or activating, but not in warmup cooldown). Only lamports that - /// are neither backing a delegation nor required for rent-exemption may be moved. - /// - /// # Account references - /// 0. `[WRITE]` Active or inactive source stake account - /// 1. `[WRITE]` Mergeable destination stake account - /// 2. `[SIGNER]` Stake authority - /// - /// The u64 is the portion of available lamports to move - MoveLamports(u64), -} - -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -pub struct LockupArgs { - pub unix_timestamp: Option, - pub epoch: Option, - pub custodian: Option, -} - -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -pub struct LockupCheckedArgs { - pub unix_timestamp: Option, - pub epoch: Option, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct AuthorizeWithSeedArgs { - pub new_authorized_pubkey: Pubkey, - pub stake_authorize: StakeAuthorize, - pub authority_seed: String, - pub authority_owner: Pubkey, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct AuthorizeCheckedWithSeedArgs { - 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 initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction { - Instruction::new_with_bincode( - id(), - &StakeInstruction::InitializeChecked, - vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - AccountMeta::new_readonly(authorized.staker, false), - AccountMeta::new_readonly(authorized.withdrawer, true), - ], - ) -} - -pub fn create_account_with_seed( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - authorized: &Authorized, - lockup: &Lockup, - lamports: u64, -) -> Vec { - vec![ - system_instruction::create_account_with_seed( - from_pubkey, - stake_pubkey, - base, - seed, - lamports, - StakeStateV2::size_of() 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 { - vec![ - system_instruction::create_account( - from_pubkey, - stake_pubkey, - lamports, - StakeStateV2::size_of() as u64, - &id(), - ), - initialize(stake_pubkey, authorized, lockup), - ] -} - -pub fn create_account_with_seed_checked( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - authorized: &Authorized, - lamports: u64, -) -> Vec { - vec![ - system_instruction::create_account_with_seed( - from_pubkey, - stake_pubkey, - base, - seed, - lamports, - StakeStateV2::size_of() as u64, - &id(), - ), - initialize_checked(stake_pubkey, authorized), - ] -} - -pub fn create_account_checked( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - authorized: &Authorized, - lamports: u64, -) -> Vec { - vec![ - system_instruction::create_account( - from_pubkey, - stake_pubkey, - lamports, - StakeStateV2::size_of() as u64, - &id(), - ), - initialize_checked(stake_pubkey, authorized), - ] -} - -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 { - vec![ - system_instruction::allocate(split_stake_pubkey, StakeStateV2::size_of() 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 { - vec![ - system_instruction::allocate_with_seed( - split_stake_pubkey, - base, - seed, - StakeStateV2::size_of() 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 { - 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 { - 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 { - 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_checked( - 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), - AccountMeta::new_readonly(*new_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::AuthorizeChecked(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 authorize_checked_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), - AccountMeta::new_readonly(*new_authorized_pubkey, true), - ]; - - if let Some(custodian_pubkey) = custodian_pubkey { - account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); - } - - let args = AuthorizeCheckedWithSeedArgs { - stake_authorize, - authority_seed, - authority_owner: *authority_owner, - }; - - Instruction::new_with_bincode( - id(), - &StakeInstruction::AuthorizeCheckedWithSeed(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), - // For backwards compatibility we pass the stake config, although this account is unused - 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) -} - -pub fn set_lockup_checked( - stake_pubkey: &Pubkey, - lockup: &LockupArgs, - custodian_pubkey: &Pubkey, -) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(*custodian_pubkey, true), - ]; - - let lockup_checked = LockupCheckedArgs { - unix_timestamp: lockup.unix_timestamp, - epoch: lockup.epoch, - }; - if let Some(new_custodian) = lockup.custodian { - account_metas.push(AccountMeta::new_readonly(new_custodian, true)); - } - Instruction::new_with_bincode( - id(), - &StakeInstruction::SetLockupChecked(lockup_checked), - account_metas, - ) -} - -pub fn get_minimum_delegation() -> Instruction { - Instruction::new_with_bincode( - id(), - &StakeInstruction::GetMinimumDelegation, - Vec::default(), - ) -} - -pub fn deactivate_delinquent_stake( - stake_account: &Pubkey, - delinquent_vote_account: &Pubkey, - reference_vote_account: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_account, false), - AccountMeta::new_readonly(*delinquent_vote_account, false), - AccountMeta::new_readonly(*reference_vote_account, false), - ]; - Instruction::new_with_bincode(id(), &StakeInstruction::DeactivateDelinquent, account_metas) -} - -fn _redelegate( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - uninitialized_stake_pubkey: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new(*uninitialized_stake_pubkey, false), - AccountMeta::new_readonly(*vote_pubkey, false), - // For backwards compatibility we pass the stake config, although this account is unused - AccountMeta::new_readonly(config::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode(id(), &StakeInstruction::Redelegate, account_metas) -} - -#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] -pub fn redelegate( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - uninitialized_stake_pubkey: &Pubkey, -) -> Vec { - vec![ - system_instruction::allocate(uninitialized_stake_pubkey, StakeStateV2::size_of() as u64), - system_instruction::assign(uninitialized_stake_pubkey, &id()), - _redelegate( - stake_pubkey, - authorized_pubkey, - vote_pubkey, - uninitialized_stake_pubkey, - ), - ] -} - -#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")] -pub fn redelegate_with_seed( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - uninitialized_stake_pubkey: &Pubkey, // derived using create_with_seed() - base: &Pubkey, // base - seed: &str, // seed -) -> Vec { - vec![ - system_instruction::allocate_with_seed( - uninitialized_stake_pubkey, - base, - seed, - StakeStateV2::size_of() as u64, - &id(), - ), - _redelegate( - stake_pubkey, - authorized_pubkey, - vote_pubkey, - uninitialized_stake_pubkey, - ), - ] -} - -pub fn move_stake( - source_stake_pubkey: &Pubkey, - destination_stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - lamports: u64, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*source_stake_pubkey, false), - AccountMeta::new(*destination_stake_pubkey, false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - - Instruction::new_with_bincode(id(), &StakeInstruction::MoveStake(lamports), account_metas) -} - -pub fn move_lamports( - source_stake_pubkey: &Pubkey, - destination_stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - lamports: u64, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*source_stake_pubkey, false), - AccountMeta::new(*destination_stake_pubkey, false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - - Instruction::new_with_bincode( - id(), - &StakeInstruction::MoveLamports(lamports), - account_metas, - ) -} - -#[cfg(test)] -mod tests { - use {super::*, crate::instruction::InstructionError}; - - #[test] - fn test_custom_error_decode() { - use num_traits::FromPrimitive; - fn pretty_err(err: InstructionError) -> String - where - T: 'static + std::error::Error + DecodeError + 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::NoCreditsToRedeem.into()) - ) - } -} diff --git a/interface/src/lib.rs b/interface/src/lib.rs deleted file mode 100644 index 31f13d45..00000000 --- a/interface/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! The [stake native program][np]. -//! -//! [np]: https://docs.solanalabs.com/runtime/sysvars#stakehistory - -#[allow(deprecated)] -pub mod config; -pub mod instruction; -pub mod stake_flags; -pub mod state; -pub mod tools; - -pub mod program { - crate::declare_id!("Stake11111111111111111111111111111111111111"); -} - -/// The minimum number of epochs before stake account that is delegated to a delinquent vote -/// account may be unstaked with `StakeInstruction::DeactivateDelinquent` -pub const MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION: usize = 5; diff --git a/interface/src/stake_flags.rs b/interface/src/stake_flags.rs deleted file mode 100644 index 072305d4..00000000 --- a/interface/src/stake_flags.rs +++ /dev/null @@ -1,140 +0,0 @@ -#[cfg(feature = "borsh")] -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; - -/// Additional flags for stake state. -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Serialize, Deserialize, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Debug)] -pub struct StakeFlags { - bits: u8, -} - -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for StakeFlags { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - bits: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} - -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for StakeFlags { - fn declaration() -> borsh0_10::schema::Declaration { - "StakeFlags".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([( - "bits".to_string(), - ::declaration(), - )]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - } -} - -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for StakeFlags { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.bits, writer)?; - Ok(()) - } -} - -/// Currently, only bit 1 is used. The other 7 bits are reserved for future usage. -impl StakeFlags { - /// Stake must be fully activated before deactivation is allowed (bit 1). - #[deprecated( - since = "2.1.0", - note = "This flag will be removed because it was only used for `redelegate`, which will not be enabled." - )] - pub const MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED: Self = - Self { bits: 0b0000_0001 }; - - pub const fn empty() -> Self { - Self { bits: 0 } - } - - pub const fn contains(&self, other: Self) -> bool { - (self.bits & other.bits) == other.bits - } - - pub fn remove(&mut self, other: Self) { - self.bits &= !other.bits; - } - - pub fn set(&mut self, other: Self) { - self.bits |= other.bits; - } - - pub const fn union(self, other: Self) -> Self { - Self { - bits: self.bits | other.bits, - } - } -} - -impl Default for StakeFlags { - fn default() -> Self { - StakeFlags::empty() - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[allow(deprecated)] - fn test_stake_flags() { - let mut f = StakeFlags::empty(); - assert!(!f.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - f.set(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED); - assert!(f.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - f.remove(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED); - assert!(!f.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - let f1 = StakeFlags::empty(); - let f2 = StakeFlags::empty(); - let f3 = f1.union(f2); - assert!(!f3.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - let f1 = StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED; - let f2 = StakeFlags::empty(); - let f3 = f1.union(f2); - assert!(f3.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - let f1 = StakeFlags::empty(); - let f2 = StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED; - let f3 = f1.union(f2); - assert!(f3.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - - let f1 = StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED; - let f2 = StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED; - let f3 = f1.union(f2); - assert!(f3.contains(StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED)); - } -} diff --git a/interface/src/stake_history.rs b/interface/src/stake_history.rs deleted file mode 100644 index 9438d004..00000000 --- a/interface/src/stake_history.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! A type to hold data for the [`StakeHistory` sysvar][sv]. -//! -//! [sv]: https://docs.solanalabs.com/runtime/sysvars#stakehistory -//! -//! The sysvar ID is declared in [`sysvar::stake_history`]. -//! -//! [`sysvar::stake_history`]: crate::sysvar::stake_history - -pub use solana_clock::Epoch; -use std::ops::Deref; - -pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down - -#[repr(C)] -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone)] -pub struct StakeHistoryEntry { - pub effective: u64, // effective stake at this epoch - pub activating: u64, // sum of portion of stakes not fully warmed up - pub deactivating: u64, // requested to be cooled down, not fully deactivated yet -} - -impl StakeHistoryEntry { - pub fn with_effective(effective: u64) -> Self { - Self { - effective, - ..Self::default() - } - } - - pub fn with_effective_and_activating(effective: u64, activating: u64) -> Self { - Self { - effective, - activating, - ..Self::default() - } - } - - pub fn with_deactivating(deactivating: u64) -> Self { - Self { - effective: deactivating, - deactivating, - ..Self::default() - } - } -} - -impl std::ops::Add for StakeHistoryEntry { - type Output = StakeHistoryEntry; - fn add(self, rhs: StakeHistoryEntry) -> Self::Output { - Self { - effective: self.effective.saturating_add(rhs.effective), - activating: self.activating.saturating_add(rhs.activating), - deactivating: self.deactivating.saturating_add(rhs.deactivating), - } - } -} - -#[repr(C)] -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone)] -pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>); - -impl StakeHistory { - pub fn get(&self, epoch: Epoch) -> Option<&StakeHistoryEntry> { - self.binary_search_by(|probe| epoch.cmp(&probe.0)) - .ok() - .map(|index| &self[index].1) - } - - pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) { - match self.binary_search_by(|probe| epoch.cmp(&probe.0)) { - Ok(index) => (self.0)[index] = (epoch, entry), - Err(index) => (self.0).insert(index, (epoch, entry)), - } - (self.0).truncate(MAX_ENTRIES); - } -} - -impl Deref for StakeHistory { - type Target = Vec<(Epoch, StakeHistoryEntry)>; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub trait StakeHistoryGetEntry { - fn get_entry(&self, epoch: Epoch) -> Option; -} - -impl StakeHistoryGetEntry for StakeHistory { - fn get_entry(&self, epoch: Epoch) -> Option { - self.binary_search_by(|probe| epoch.cmp(&probe.0)) - .ok() - .map(|index| self[index].1.clone()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_stake_history() { - let mut stake_history = StakeHistory::default(); - - for i in 0..MAX_ENTRIES as u64 + 1 { - stake_history.add( - i, - StakeHistoryEntry { - activating: i, - ..StakeHistoryEntry::default() - }, - ); - } - assert_eq!(stake_history.len(), MAX_ENTRIES); - assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1); - assert_eq!(stake_history.get(0), None); - assert_eq!( - stake_history.get(1), - Some(&StakeHistoryEntry { - activating: 1, - ..StakeHistoryEntry::default() - }) - ); - } -} diff --git a/interface/src/state.rs b/interface/src/state.rs deleted file mode 100644 index 4fee1bb0..00000000 --- a/interface/src/state.rs +++ /dev/null @@ -1,1301 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] -#![deny(clippy::wildcard_enum_match_arm)] -// Remove the following `allow` when `StakeState` is removed, required to avoid -// warnings from uses of deprecated types during trait derivations. -#![allow(deprecated)] - -#[cfg(feature = "borsh")] -use borsh::{io, BorshDeserialize, BorshSchema, BorshSerialize}; -use { - crate::{ - instruction::InstructionError, - pubkey::Pubkey, - stake::{ - instruction::{LockupArgs, StakeError}, - stake_flags::StakeFlags, - }, - stake_history::{StakeHistoryEntry, StakeHistoryGetEntry}, - }, - solana_clock::{Clock, Epoch, UnixTimestamp}, - std::collections::HashSet, -}; - -pub type StakeActivationStatus = StakeHistoryEntry; - -// means that no more than RATE of current effective stake may be added or subtracted per -// epoch -pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25; -pub const NEW_WARMUP_COOLDOWN_RATE: f64 = 0.09; -pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * u8::MAX as usize) / 100) as u8; - -pub fn warmup_cooldown_rate(current_epoch: Epoch, new_rate_activation_epoch: Option) -> f64 { - if current_epoch < new_rate_activation_epoch.unwrap_or(u64::MAX) { - DEFAULT_WARMUP_COOLDOWN_RATE - } else { - NEW_WARMUP_COOLDOWN_RATE - } -} - -#[cfg(feature = "borsh")] -macro_rules! impl_borsh_stake_state { - ($borsh:ident) => { - impl $borsh::BorshDeserialize for StakeState { - fn deserialize_reader(reader: &mut R) -> io::Result { - let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?; - match enum_value { - 0 => Ok(StakeState::Uninitialized), - 1 => { - let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?; - Ok(StakeState::Initialized(meta)) - } - 2 => { - let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?; - let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?; - Ok(StakeState::Stake(meta, stake)) - } - 3 => Ok(StakeState::RewardsPool), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid enum value", - )), - } - } - } - impl $borsh::BorshSerialize for StakeState { - fn serialize(&self, writer: &mut W) -> io::Result<()> { - match self { - StakeState::Uninitialized => writer.write_all(&0u32.to_le_bytes()), - StakeState::Initialized(meta) => { - writer.write_all(&1u32.to_le_bytes())?; - $borsh::BorshSerialize::serialize(&meta, writer) - } - StakeState::Stake(meta, stake) => { - writer.write_all(&2u32.to_le_bytes())?; - $borsh::BorshSerialize::serialize(&meta, writer)?; - $borsh::BorshSerialize::serialize(&stake, writer) - } - StakeState::RewardsPool => writer.write_all(&3u32.to_le_bytes()), - } - } - } - }; -} -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)] -#[allow(clippy::large_enum_variant)] -#[deprecated( - since = "1.17.0", - note = "Please use `StakeStateV2` instead, and match the third `StakeFlags` field when matching `StakeStateV2::Stake` to resolve any breakage. For example, `if let StakeState::Stake(meta, stake)` becomes `if let StakeStateV2::Stake(meta, stake, _stake_flags)`." -)] -pub enum StakeState { - #[default] - Uninitialized, - Initialized(Meta), - Stake(Meta, Stake), - RewardsPool, -} -#[cfg(feature = "borsh")] -impl_borsh_stake_state!(borsh); -#[cfg(feature = "borsh")] -impl_borsh_stake_state!(borsh0_10); -impl StakeState { - /// The fixed number of bytes used to serialize each stake account - pub const fn size_of() -> usize { - 200 // see test_size_of - } - - pub fn stake(&self) -> Option { - match self { - Self::Stake(_meta, stake) => Some(*stake), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn delegation(&self) -> Option { - match self { - Self::Stake(_meta, stake) => Some(stake.delegation), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn authorized(&self) -> Option { - match self { - Self::Stake(meta, _stake) => Some(meta.authorized), - Self::Initialized(meta) => Some(meta.authorized), - Self::Uninitialized | Self::RewardsPool => None, - } - } - - pub fn lockup(&self) -> Option { - self.meta().map(|meta| meta.lockup) - } - - pub fn meta(&self) -> Option { - match self { - Self::Stake(meta, _stake) => Some(*meta), - Self::Initialized(meta) => Some(*meta), - Self::Uninitialized | Self::RewardsPool => None, - } - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)] -#[allow(clippy::large_enum_variant)] -pub enum StakeStateV2 { - #[default] - Uninitialized, - Initialized(Meta), - Stake(Meta, Stake, StakeFlags), - RewardsPool, -} -#[cfg(feature = "borsh")] -macro_rules! impl_borsh_stake_state_v2 { - ($borsh:ident) => { - impl $borsh::BorshDeserialize for StakeStateV2 { - fn deserialize_reader(reader: &mut R) -> io::Result { - let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?; - match enum_value { - 0 => Ok(StakeStateV2::Uninitialized), - 1 => { - let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?; - Ok(StakeStateV2::Initialized(meta)) - } - 2 => { - let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?; - let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?; - let stake_flags: StakeFlags = - $borsh::BorshDeserialize::deserialize_reader(reader)?; - Ok(StakeStateV2::Stake(meta, stake, stake_flags)) - } - 3 => Ok(StakeStateV2::RewardsPool), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid enum value", - )), - } - } - } - impl $borsh::BorshSerialize for StakeStateV2 { - fn serialize(&self, writer: &mut W) -> io::Result<()> { - match self { - StakeStateV2::Uninitialized => writer.write_all(&0u32.to_le_bytes()), - StakeStateV2::Initialized(meta) => { - writer.write_all(&1u32.to_le_bytes())?; - $borsh::BorshSerialize::serialize(&meta, writer) - } - StakeStateV2::Stake(meta, stake, stake_flags) => { - writer.write_all(&2u32.to_le_bytes())?; - $borsh::BorshSerialize::serialize(&meta, writer)?; - $borsh::BorshSerialize::serialize(&stake, writer)?; - $borsh::BorshSerialize::serialize(&stake_flags, writer) - } - StakeStateV2::RewardsPool => writer.write_all(&3u32.to_le_bytes()), - } - } - } - }; -} -#[cfg(feature = "borsh")] -impl_borsh_stake_state_v2!(borsh); -#[cfg(feature = "borsh")] -impl_borsh_stake_state_v2!(borsh0_10); - -impl StakeStateV2 { - /// The fixed number of bytes used to serialize each stake account - pub const fn size_of() -> usize { - 200 // see test_size_of - } - - pub fn stake(&self) -> Option { - match self { - Self::Stake(_meta, stake, _stake_flags) => Some(*stake), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn stake_ref(&self) -> Option<&Stake> { - match self { - Self::Stake(_meta, stake, _stake_flags) => Some(stake), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn delegation(&self) -> Option { - match self { - Self::Stake(_meta, stake, _stake_flags) => Some(stake.delegation), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn delegation_ref(&self) -> Option<&Delegation> { - match self { - StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(&stake.delegation), - Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None, - } - } - - pub fn authorized(&self) -> Option { - match self { - Self::Stake(meta, _stake, _stake_flags) => Some(meta.authorized), - Self::Initialized(meta) => Some(meta.authorized), - Self::Uninitialized | Self::RewardsPool => None, - } - } - - pub fn lockup(&self) -> Option { - self.meta().map(|meta| meta.lockup) - } - - pub fn meta(&self) -> Option { - match self { - Self::Stake(meta, _stake, _stake_flags) => Some(*meta), - Self::Initialized(meta) => Some(*meta), - Self::Uninitialized | Self::RewardsPool => None, - } - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -pub enum StakeAuthorize { - Staker, - Withdrawer, -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -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 - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for Lockup { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - unix_timestamp: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - custodian: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for Lockup { - fn declaration() -> borsh0_10::schema::Declaration { - "Lockup".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([ - ( - "unix_timestamp".to_string(), - ::declaration(), - ), - ( - "epoch".to_string(), - ::declaration(), - ), - ( - "custodian".to_string(), - ::declaration(), - ), - ]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for Lockup { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.unix_timestamp, writer)?; - borsh0_10::BorshSerialize::serialize(&self.epoch, writer)?; - borsh0_10::BorshSerialize::serialize(&self.custodian, writer)?; - Ok(()) - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -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, - stake_authorize: StakeAuthorize, - ) -> Result<(), InstructionError> { - let authorized_signer = match stake_authorize { - StakeAuthorize::Staker => &self.staker, - StakeAuthorize::Withdrawer => &self.withdrawer, - }; - - if signers.contains(authorized_signer) { - Ok(()) - } else { - Err(InstructionError::MissingRequiredSignature) - } - } - - pub fn authorize( - &mut self, - signers: &HashSet, - 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(()) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for Authorized { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - staker: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - withdrawer: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for Authorized { - fn declaration() -> borsh0_10::schema::Declaration { - "Authorized".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([ - ( - "staker".to_string(), - ::declaration(), - ), - ( - "withdrawer".to_string(), - ::declaration(), - ), - ]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for Authorized { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.staker, writer)?; - borsh0_10::BorshSerialize::serialize(&self.withdrawer, writer)?; - Ok(()) - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -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, - clock: &Clock, - ) -> Result<(), InstructionError> { - // 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 auto(authorized: &Pubkey) -> Self { - Self { - authorized: Authorized::auto(authorized), - ..Meta::default() - } - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for Meta { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - rent_exempt_reserve: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - authorized: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - lockup: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for Meta { - fn declaration() -> borsh0_10::schema::Declaration { - "Meta".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([ - ( - "rent_exempt_reserve".to_string(), - ::declaration(), - ), - ( - "authorized".to_string(), - ::declaration(), - ), - ( - "lockup".to_string(), - ::declaration(), - ), - ]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for Meta { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.rent_exempt_reserve, writer)?; - borsh0_10::BorshSerialize::serialize(&self.authorized, writer)?; - borsh0_10::BorshSerialize::serialize(&self.lockup, writer)?; - Ok(()) - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] -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 - #[deprecated( - since = "1.16.7", - note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead" - )] - pub warmup_cooldown_rate: f64, -} - -impl Default for Delegation { - fn default() -> Self { - #[allow(deprecated)] - Self { - voter_pubkey: Pubkey::default(), - stake: 0, - activation_epoch: 0, - deactivation_epoch: u64::MAX, - warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE, - } - } -} - -impl Delegation { - pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self { - Self { - voter_pubkey: *voter_pubkey, - stake, - activation_epoch, - ..Delegation::default() - } - } - pub fn is_bootstrap(&self) -> bool { - self.activation_epoch == u64::MAX - } - - pub fn stake( - &self, - epoch: Epoch, - history: &T, - new_rate_activation_epoch: Option, - ) -> u64 { - self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch) - .effective - } - - #[allow(clippy::comparison_chain)] - pub fn stake_activating_and_deactivating( - &self, - target_epoch: Epoch, - history: &T, - new_rate_activation_epoch: Option, - ) -> StakeActivationStatus { - // first, calculate an effective and activating stake - let (effective_stake, activating_stake) = - self.stake_and_activating(target_epoch, history, new_rate_activation_epoch); - - // then de-activate some portion if necessary - if target_epoch < self.deactivation_epoch { - // not deactivated - if activating_stake == 0 { - StakeActivationStatus::with_effective(effective_stake) - } else { - StakeActivationStatus::with_effective_and_activating( - effective_stake, - activating_stake, - ) - } - } else if target_epoch == self.deactivation_epoch { - // can only deactivate what's activated - StakeActivationStatus::with_deactivating(effective_stake) - } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history - .get_entry(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; - let warmup_cooldown_rate = - warmup_cooldown_rate(current_epoch, new_rate_activation_epoch); - - // portion of newly not-effective cluster stake I'm entitled to at current epoch - let newly_not_effective_cluster_stake = - prev_cluster_stake.effective as f64 * 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_entry(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 - StakeActivationStatus::with_deactivating(current_effective_stake) - } else { - // no history or I've dropped out of history, so assume fully deactivated - StakeActivationStatus::default() - } - } - - // returned tuple is (effective, activating) stake - fn stake_and_activating( - &self, - target_epoch: Epoch, - history: &T, - new_rate_activation_epoch: Option, - ) -> (u64, u64) { - let delegated_stake = self.stake; - - if self.is_bootstrap() { - // fully effective immediately - (delegated_stake, 0) - } else if 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 - .get_entry(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; - let warmup_cooldown_rate = - warmup_cooldown_rate(current_epoch, new_rate_activation_epoch); - - // portion of newly effective cluster stake I'm entitled to at current epoch - let newly_effective_cluster_stake = - prev_cluster_stake.effective as f64 * 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_entry(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) - } - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for Delegation { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - voter_pubkey: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - stake: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - activation_epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - deactivation_epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - warmup_cooldown_rate: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for Delegation { - fn declaration() -> borsh0_10::schema::Declaration { - "Delegation".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([ - ( - "voter_pubkey".to_string(), - ::declaration(), - ), - ( - "stake".to_string(), - ::declaration(), - ), - ( - "activation_epoch".to_string(), - ::declaration(), - ), - ( - "deactivation_epoch".to_string(), - ::declaration(), - ), - ( - "warmup_cooldown_rate".to_string(), - ::declaration(), - ), - ]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for Delegation { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.voter_pubkey, writer)?; - borsh0_10::BorshSerialize::serialize(&self.stake, writer)?; - borsh0_10::BorshSerialize::serialize(&self.activation_epoch, writer)?; - borsh0_10::BorshSerialize::serialize(&self.deactivation_epoch, writer)?; - borsh0_10::BorshSerialize::serialize(&self.warmup_cooldown_rate, writer)?; - Ok(()) - } -} - -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)] -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: &T, - new_rate_activation_epoch: Option, - ) -> u64 { - self.delegation - .stake(epoch, history, new_rate_activation_epoch) - } - - pub fn split( - &mut self, - remaining_stake_delta: u64, - split_stake_amount: u64, - ) -> Result { - 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 != u64::MAX { - Err(StakeError::AlreadyDeactivated) - } else { - self.delegation.deactivation_epoch = epoch; - Ok(()) - } - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::de::BorshDeserialize for Stake { - fn deserialize_reader( - reader: &mut R, - ) -> ::core::result::Result { - Ok(Self { - delegation: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - credits_observed: borsh0_10::BorshDeserialize::deserialize_reader(reader)?, - }) - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::BorshSchema for Stake { - fn declaration() -> borsh0_10::schema::Declaration { - "Stake".to_string() - } - fn add_definitions_recursively( - definitions: &mut borsh0_10::maybestd::collections::HashMap< - borsh0_10::schema::Declaration, - borsh0_10::schema::Definition, - >, - ) { - let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec( - borsh0_10::maybestd::boxed::Box::new([ - ( - "delegation".to_string(), - ::declaration(), - ), - ( - "credits_observed".to_string(), - ::declaration(), - ), - ]), - )); - let definition = borsh0_10::schema::Definition::Struct { fields }; - Self::add_definition( - ::declaration(), - definition, - definitions, - ); - ::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } -} -#[cfg(feature = "borsh")] -impl borsh0_10::ser::BorshSerialize for Stake { - fn serialize( - &self, - writer: &mut W, - ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> { - borsh0_10::BorshSerialize::serialize(&self.delegation, writer)?; - borsh0_10::BorshSerialize::serialize(&self.credits_observed, writer)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - #[cfg(feature = "borsh")] - use crate::borsh1::try_from_slice_unchecked; - use {super::*, assert_matches::assert_matches, bincode::serialize}; - - #[cfg(feature = "borsh")] - fn check_borsh_deserialization(stake: StakeStateV2) { - let serialized = serialize(&stake).unwrap(); - let deserialized = StakeStateV2::try_from_slice(&serialized).unwrap(); - assert_eq!(stake, deserialized); - } - - #[cfg(feature = "borsh")] - fn check_borsh_serialization(stake: StakeStateV2) { - let bincode_serialized = serialize(&stake).unwrap(); - let borsh_serialized = borsh::to_vec(&stake).unwrap(); - assert_eq!(bincode_serialized, borsh_serialized); - } - - #[cfg(feature = "borsh")] - #[test] - fn test_size_of() { - assert_eq!(StakeStateV2::size_of(), std::mem::size_of::()); - } - - #[cfg(feature = "borsh")] - #[test] - fn bincode_vs_borsh_deserialization() { - check_borsh_deserialization(StakeStateV2::Uninitialized); - check_borsh_deserialization(StakeStateV2::RewardsPool); - check_borsh_deserialization(StakeStateV2::Initialized(Meta { - rent_exempt_reserve: u64::MAX, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - })); - check_borsh_deserialization(StakeStateV2::Stake( - Meta { - rent_exempt_reserve: 1, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - }, - Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_unique(), - stake: u64::MAX, - activation_epoch: Epoch::MAX, - deactivation_epoch: Epoch::MAX, - ..Delegation::default() - }, - credits_observed: 1, - }, - StakeFlags::empty(), - )); - } - - #[cfg(feature = "borsh")] - #[test] - fn bincode_vs_borsh_serialization() { - check_borsh_serialization(StakeStateV2::Uninitialized); - check_borsh_serialization(StakeStateV2::RewardsPool); - check_borsh_serialization(StakeStateV2::Initialized(Meta { - rent_exempt_reserve: u64::MAX, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - })); - #[allow(deprecated)] - check_borsh_serialization(StakeStateV2::Stake( - Meta { - rent_exempt_reserve: 1, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - }, - Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_unique(), - stake: u64::MAX, - activation_epoch: Epoch::MAX, - deactivation_epoch: Epoch::MAX, - ..Default::default() - }, - credits_observed: 1, - }, - StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, - )); - } - - #[cfg(feature = "borsh")] - #[test] - fn borsh_deserialization_live_data() { - let data = [ - 1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, - 119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, - 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120, - 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]; - // As long as we get the 4-byte enum and the first field right, then - // we're sure the rest works out - let deserialized = try_from_slice_unchecked::(&data).unwrap(); - assert_matches!( - deserialized, - StakeStateV2::Initialized(Meta { - rent_exempt_reserve: 2282880, - .. - }) - ); - } - - #[test] - fn stake_flag_member_offset() { - const FLAG_OFFSET: usize = 196; - let check_flag = |flag, expected| { - let stake = StakeStateV2::Stake( - Meta { - rent_exempt_reserve: 1, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - }, - Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_unique(), - stake: u64::MAX, - activation_epoch: Epoch::MAX, - deactivation_epoch: Epoch::MAX, - warmup_cooldown_rate: f64::MAX, - }, - credits_observed: 1, - }, - flag, - ); - - let bincode_serialized = serialize(&stake).unwrap(); - let borsh_serialized = borsh::to_vec(&stake).unwrap(); - - assert_eq!(bincode_serialized[FLAG_OFFSET], expected); - assert_eq!(borsh_serialized[FLAG_OFFSET], expected); - }; - #[allow(deprecated)] - check_flag( - StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, - 1, - ); - check_flag(StakeFlags::empty(), 0); - } - - mod deprecated { - use super::*; - #[cfg(feature = "borsh")] - fn check_borsh_deserialization(stake: StakeState) { - let serialized = serialize(&stake).unwrap(); - let deserialized = StakeState::try_from_slice(&serialized).unwrap(); - assert_eq!(stake, deserialized); - } - - #[cfg(feature = "borsh")] - fn check_borsh_serialization(stake: StakeState) { - let bincode_serialized = serialize(&stake).unwrap(); - let borsh_serialized = borsh::to_vec(&stake).unwrap(); - assert_eq!(bincode_serialized, borsh_serialized); - } - - #[test] - fn test_size_of() { - assert_eq!(StakeState::size_of(), std::mem::size_of::()); - } - - #[cfg(feature = "borsh")] - #[test] - fn bincode_vs_borsh_deserialization() { - check_borsh_deserialization(StakeState::Uninitialized); - check_borsh_deserialization(StakeState::RewardsPool); - check_borsh_deserialization(StakeState::Initialized(Meta { - rent_exempt_reserve: u64::MAX, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - })); - check_borsh_deserialization(StakeState::Stake( - Meta { - rent_exempt_reserve: 1, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - }, - Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_unique(), - stake: u64::MAX, - activation_epoch: Epoch::MAX, - deactivation_epoch: Epoch::MAX, - warmup_cooldown_rate: f64::MAX, - }, - credits_observed: 1, - }, - )); - } - - #[cfg(feature = "borsh")] - #[test] - fn bincode_vs_borsh_serialization() { - check_borsh_serialization(StakeState::Uninitialized); - check_borsh_serialization(StakeState::RewardsPool); - check_borsh_serialization(StakeState::Initialized(Meta { - rent_exempt_reserve: u64::MAX, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - })); - check_borsh_serialization(StakeState::Stake( - Meta { - rent_exempt_reserve: 1, - authorized: Authorized { - staker: Pubkey::new_unique(), - withdrawer: Pubkey::new_unique(), - }, - lockup: Lockup::default(), - }, - Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_unique(), - stake: u64::MAX, - activation_epoch: Epoch::MAX, - deactivation_epoch: Epoch::MAX, - warmup_cooldown_rate: f64::MAX, - }, - credits_observed: 1, - }, - )); - } - - #[cfg(feature = "borsh")] - #[test] - fn borsh_deserialization_live_data() { - let data = [ - 1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, - 119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, - 149, 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, - 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, - 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - // As long as we get the 4-byte enum and the first field right, then - // we're sure the rest works out - let deserialized = try_from_slice_unchecked::(&data).unwrap(); - assert_matches!( - deserialized, - StakeState::Initialized(Meta { - rent_exempt_reserve: 2282880, - .. - }) - ); - } - } -} diff --git a/interface/src/tools.rs b/interface/src/tools.rs deleted file mode 100644 index 73f92017..00000000 --- a/interface/src/tools.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Utility functions -use { - crate::{program_error::ProgramError, stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION}, - solana_clock::Epoch, -}; - -/// Helper function for programs to call [`GetMinimumDelegation`] and then fetch the return data -/// -/// This fn handles performing the CPI to call the [`GetMinimumDelegation`] function, and then -/// calls [`get_return_data()`] to fetch the return data. -/// -/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation -/// [`get_return_data()`]: crate::program::get_return_data -pub fn get_minimum_delegation() -> Result { - let instruction = super::instruction::get_minimum_delegation(); - crate::program::invoke_unchecked(&instruction, &[])?; - get_minimum_delegation_return_data() -} - -/// Helper function for programs to get the return data after calling [`GetMinimumDelegation`] -/// -/// This fn handles calling [`get_return_data()`], ensures the result is from the correct -/// program, and returns the correct type. -/// -/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation -/// [`get_return_data()`]: crate::program::get_return_data -fn get_minimum_delegation_return_data() -> Result { - crate::program::get_return_data() - .ok_or(ProgramError::InvalidInstructionData) - .and_then(|(program_id, return_data)| { - (program_id == super::program::id()) - .then_some(return_data) - .ok_or(ProgramError::IncorrectProgramId) - }) - .and_then(|return_data| { - return_data - .try_into() - .or(Err(ProgramError::InvalidInstructionData)) - }) - .map(u64::from_le_bytes) -} - -// Check if the provided `epoch_credits` demonstrate active voting over the previous -// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` -pub fn acceptable_reference_epoch_credits( - epoch_credits: &[(Epoch, u64, u64)], - current_epoch: Epoch, -) -> bool { - if let Some(epoch_index) = epoch_credits - .len() - .checked_sub(MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION) - { - let mut epoch = current_epoch; - for (vote_epoch, ..) in epoch_credits[epoch_index..].iter().rev() { - if *vote_epoch != epoch { - return false; - } - epoch = epoch.saturating_sub(1); - } - true - } else { - false - } -} - -// Check if the provided `epoch_credits` demonstrate delinquency over the previous -// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` -pub fn eligible_for_deactivate_delinquent( - epoch_credits: &[(Epoch, u64, u64)], - current_epoch: Epoch, -) -> bool { - match epoch_credits.last() { - None => true, - Some((epoch, ..)) => { - if let Some(minimum_epoch) = - current_epoch.checked_sub(MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch) - { - *epoch <= minimum_epoch - } else { - false - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_acceptable_reference_epoch_credits() { - let epoch_credits = []; - assert!(!acceptable_reference_epoch_credits(&epoch_credits, 0)); - - let epoch_credits = [(0, 42, 42), (1, 42, 42), (2, 42, 42), (3, 42, 42)]; - assert!(!acceptable_reference_epoch_credits(&epoch_credits, 3)); - - let epoch_credits = [ - (0, 42, 42), - (1, 42, 42), - (2, 42, 42), - (3, 42, 42), - (4, 42, 42), - ]; - assert!(!acceptable_reference_epoch_credits(&epoch_credits, 3)); - assert!(acceptable_reference_epoch_credits(&epoch_credits, 4)); - - let epoch_credits = [ - (1, 42, 42), - (2, 42, 42), - (3, 42, 42), - (4, 42, 42), - (5, 42, 42), - ]; - assert!(acceptable_reference_epoch_credits(&epoch_credits, 5)); - - let epoch_credits = [ - (0, 42, 42), - (2, 42, 42), - (3, 42, 42), - (4, 42, 42), - (5, 42, 42), - ]; - assert!(!acceptable_reference_epoch_credits(&epoch_credits, 5)); - } - - #[test] - fn test_eligible_for_deactivate_delinquent() { - let epoch_credits = []; - assert!(eligible_for_deactivate_delinquent(&epoch_credits, 42)); - - let epoch_credits = [(0, 42, 42)]; - assert!(!eligible_for_deactivate_delinquent(&epoch_credits, 0)); - - let epoch_credits = [(0, 42, 42)]; - assert!(!eligible_for_deactivate_delinquent( - &epoch_credits, - MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - 1 - )); - assert!(eligible_for_deactivate_delinquent( - &epoch_credits, - MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - )); - - let epoch_credits = [(100, 42, 42)]; - assert!(!eligible_for_deactivate_delinquent( - &epoch_credits, - 100 + MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - 1 - )); - assert!(eligible_for_deactivate_delinquent( - &epoch_credits, - 100 + MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - )); - } -}