Skip to main content

solana_runtime/
stake_account.rs

1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4    solana_account::{AccountSharedData, ReadableAccount, state_traits::StateMut},
5    solana_instruction::error::InstructionError,
6    solana_pubkey::Pubkey,
7    solana_stake_interface::{
8        program as stake_program,
9        state::{Delegation, Stake, StakeStateV2},
10    },
11    std::marker::PhantomData,
12    thiserror::Error,
13};
14#[cfg(feature = "frozen-abi")]
15use {
16    solana_frozen_abi::{abi_example::AbiExample, stable_abi::StableAbi},
17    solana_stake_interface::{stake_flags::StakeFlags, state::Meta},
18};
19
20/// An account and a stake state deserialized from the account.
21/// Generic type T enforces type-safety so that StakeAccount<Delegation> can
22/// only wrap a stake-state which is a Delegation; whereas StakeAccount<()>
23/// wraps any account with stake state.
24#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
25#[derive(Clone, Debug, Default)]
26pub struct StakeAccount<T> {
27    // Skipped by the custom (delegation/stake-format) serializer; sample the default.
28    #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Default::default()"))]
29    account: AccountSharedData,
30    #[cfg_attr(
31        feature = "frozen-abi",
32        stable_abi_sample(with = "sample_delegated_stake_state(rng)")
33    )]
34    stake_state: StakeStateV2,
35    _phantom: PhantomData<T>,
36}
37
38/// Samples a random `StakeStateV2::Stake`; the delegation-format serializer unwraps
39/// `delegation_ref()`, which would panic on any other variant.
40#[cfg(feature = "frozen-abi")]
41fn sample_delegated_stake_state(
42    rng: &mut (impl solana_frozen_abi::rand::RngCore + ?Sized),
43) -> StakeStateV2 {
44    StakeStateV2::Stake(
45        Meta::random(rng),
46        Stake::random(rng),
47        StakeFlags::random(rng),
48    )
49}
50
51#[derive(Debug, Error)]
52pub enum Error {
53    #[error(transparent)]
54    InstructionError(#[from] InstructionError),
55    #[error("Invalid delegation: {0:?}")]
56    InvalidDelegation(Box<StakeStateV2>),
57    #[error("Invalid stake account owner: {0}")]
58    InvalidOwner(/*owner:*/ Pubkey),
59}
60
61impl<T> StakeAccount<T> {
62    #[inline]
63    pub(crate) fn lamports(&self) -> u64 {
64        self.account.lamports()
65    }
66
67    #[inline]
68    pub(crate) fn stake_state(&self) -> &StakeStateV2 {
69        &self.stake_state
70    }
71
72    #[inline]
73    pub(crate) fn data_len(&self) -> usize {
74        self.account.data().len()
75    }
76}
77
78impl StakeAccount<Delegation> {
79    #[inline]
80    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
81    pub(crate) fn delegation(&self) -> &Delegation {
82        // Safe to unwrap here because StakeAccount<Delegation> will always
83        // only wrap a stake-state which is a delegation.
84        self.stake_state.delegation_ref().unwrap()
85    }
86
87    #[inline]
88    pub(crate) fn stake(&self) -> &Stake {
89        // Safe to unwrap here because StakeAccount<Delegation> will always
90        // only wrap a stake-state.
91        self.stake_state.stake_ref().unwrap()
92    }
93}
94
95impl TryFrom<AccountSharedData> for StakeAccount<Delegation> {
96    type Error = Error;
97    fn try_from(account: AccountSharedData) -> Result<Self, Self::Error> {
98        if account.owner() != &stake_program::id() {
99            return Err(Error::InvalidOwner(*account.owner()));
100        }
101        let stake_state: StakeStateV2 = account.state()?;
102        if stake_state.delegation().is_none() {
103            return Err(Error::InvalidDelegation(Box::new(stake_state)));
104        }
105        Ok(Self {
106            account,
107            stake_state,
108            _phantom: PhantomData,
109        })
110    }
111}
112
113impl<T> From<StakeAccount<T>> for (AccountSharedData, StakeStateV2) {
114    #[inline]
115    fn from(stake_account: StakeAccount<T>) -> Self {
116        (stake_account.account, stake_account.stake_state)
117    }
118}
119
120impl<S, T> PartialEq<StakeAccount<S>> for StakeAccount<T> {
121    fn eq(&self, other: &StakeAccount<S>) -> bool {
122        let StakeAccount {
123            account,
124            stake_state,
125            _phantom,
126        } = other;
127        account == &self.account && stake_state == &self.stake_state
128    }
129}
130
131#[cfg(feature = "frozen-abi")]
132impl AbiExample for StakeAccount<Delegation> {
133    fn example() -> Self {
134        use solana_account::Account;
135        let stake_state =
136            StakeStateV2::Stake(Meta::example(), Stake::example(), StakeFlags::example());
137        let mut account = Account::example();
138        account.data.resize(200, 0u8);
139        account.owner = stake_program::id();
140        account.set_state(&stake_state).unwrap();
141        Self::try_from(AccountSharedData::from(account)).unwrap()
142    }
143}