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    wincode::SchemaWrite,
14};
15#[cfg(feature = "frozen-abi")]
16use {
17    solana_frozen_abi::{abi_example::AbiExample, stable_abi::StableAbi},
18    solana_stake_interface::{stake_flags::StakeFlags, state::Meta},
19};
20
21/// An account and a stake state deserialized from the account.
22/// Generic type T enforces type-safety so that StakeAccount<Delegation> can
23/// only wrap a stake-state which is a Delegation; whereas StakeAccount<()>
24/// wraps any account with stake state.
25#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
26#[derive(Clone, Debug, Default)]
27pub struct StakeAccount<T> {
28    // Skipped by the custom (delegation/stake-format) serializer; sample the default.
29    #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Default::default()"))]
30    account: AccountSharedData,
31    #[cfg_attr(
32        feature = "frozen-abi",
33        stable_abi_sample(with = "sample_delegated_stake_state(rng)")
34    )]
35    stake_state: StakeStateV2,
36    _phantom: PhantomData<T>,
37}
38
39// `StakeAccount<Delegation>` is only ever wincode-serialized as part of the snapshot's
40// `stake_delegations` map, which uses the delegation format: just the `Delegation` is written (see
41// `serialize_stake_accounts_to_delegation_format`). Mirror that here so the wincode bytes match
42// bincode; `FromIntoIterator` on the map picks up this per-value schema automatically.
43unsafe impl<C: wincode::config::Config> SchemaWrite<C> for StakeAccount<Delegation> {
44    type Src = Self;
45
46    const TYPE_META: wincode::TypeMeta =
47        <Delegation as SchemaWrite<C>>::TYPE_META.keep_zero_copy(false);
48
49    fn size_of(src: &Self::Src) -> wincode::WriteResult<usize> {
50        <Delegation as SchemaWrite<C>>::size_of(src.delegation())
51    }
52
53    fn write(writer: impl wincode::io::Writer, src: &Self::Src) -> wincode::WriteResult<()> {
54        <Delegation as SchemaWrite<C>>::write(writer, src.delegation())
55    }
56}
57
58/// Samples a random `StakeStateV2::Stake`; the delegation-format serializer unwraps
59/// `delegation_ref()`, which would panic on any other variant.
60#[cfg(feature = "frozen-abi")]
61fn sample_delegated_stake_state(
62    rng: &mut (impl solana_frozen_abi::rand::RngCore + ?Sized),
63) -> StakeStateV2 {
64    StakeStateV2::Stake(
65        Meta::random(rng),
66        Stake::random(rng),
67        StakeFlags::random(rng),
68    )
69}
70
71#[derive(Debug, Error)]
72pub enum Error {
73    #[error(transparent)]
74    InstructionError(#[from] InstructionError),
75    #[error("Invalid delegation: {0:?}")]
76    InvalidDelegation(Box<StakeStateV2>),
77    #[error("Invalid stake account owner: {0}")]
78    InvalidOwner(/*owner:*/ Pubkey),
79}
80
81impl<T> StakeAccount<T> {
82    #[inline]
83    pub(crate) fn lamports(&self) -> u64 {
84        self.account.lamports()
85    }
86
87    #[inline]
88    pub(crate) fn stake_state(&self) -> &StakeStateV2 {
89        &self.stake_state
90    }
91
92    #[inline]
93    pub(crate) fn data_len(&self) -> usize {
94        self.account.data().len()
95    }
96}
97
98impl StakeAccount<Delegation> {
99    #[inline]
100    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
101    pub(crate) fn delegation(&self) -> &Delegation {
102        // Safe to unwrap here because StakeAccount<Delegation> will always
103        // only wrap a stake-state which is a delegation.
104        self.stake_state.delegation_ref().unwrap()
105    }
106
107    #[inline]
108    pub(crate) fn stake(&self) -> &Stake {
109        // Safe to unwrap here because StakeAccount<Delegation> will always
110        // only wrap a stake-state.
111        self.stake_state.stake_ref().unwrap()
112    }
113}
114
115impl TryFrom<AccountSharedData> for StakeAccount<Delegation> {
116    type Error = Error;
117    fn try_from(account: AccountSharedData) -> Result<Self, Self::Error> {
118        if account.owner() != &stake_program::id() {
119            return Err(Error::InvalidOwner(*account.owner()));
120        }
121        let stake_state: StakeStateV2 = account.state()?;
122        if stake_state.delegation().is_none() {
123            return Err(Error::InvalidDelegation(Box::new(stake_state)));
124        }
125        Ok(Self {
126            account,
127            stake_state,
128            _phantom: PhantomData,
129        })
130    }
131}
132
133impl<T> From<StakeAccount<T>> for (AccountSharedData, StakeStateV2) {
134    #[inline]
135    fn from(stake_account: StakeAccount<T>) -> Self {
136        (stake_account.account, stake_account.stake_state)
137    }
138}
139
140impl<S, T> PartialEq<StakeAccount<S>> for StakeAccount<T> {
141    fn eq(&self, other: &StakeAccount<S>) -> bool {
142        let StakeAccount {
143            account,
144            stake_state,
145            _phantom,
146        } = other;
147        account == &self.account && stake_state == &self.stake_state
148    }
149}
150
151#[cfg(feature = "frozen-abi")]
152impl AbiExample for StakeAccount<Delegation> {
153    fn example() -> Self {
154        use solana_account::Account;
155        let stake_state =
156            StakeStateV2::Stake(Meta::example(), Stake::example(), StakeFlags::example());
157        let mut account = Account::example();
158        account.data.resize(200, 0u8);
159        account.owner = stake_program::id();
160        account.set_state(&stake_state).unwrap();
161        Self::try_from(AccountSharedData::from(account)).unwrap()
162    }
163}