|
1 | 1 | //! A type to hold data for the [`StakeHistory` sysvar][sv]. |
2 | 2 | //! |
3 | | -//! [sv]: https://docs.solanalabs.com/runtime/sysvars#stakehistory |
| 3 | +//! [sv]: https://docs.anza.xyz/runtime/sysvars#stakehistory |
4 | 4 |
|
5 | | -pub use solana_clock::Epoch; |
6 | | -use std::ops::Deref; |
7 | | - |
8 | | -pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down |
9 | | - |
10 | | -/// Serialized size of a single `(Epoch, StakeHistoryEntry)` tuple |
11 | | -pub(crate) const EPOCH_AND_ENTRY_SERIALIZED_SIZE: usize = 32; |
12 | | -const _: () = |
13 | | - assert!(EPOCH_AND_ENTRY_SERIALIZED_SIZE == size_of::<u64>() + size_of::<StakeHistoryEntry>()); |
14 | | - |
15 | | -const LEN_PREFIX: usize = size_of::<u64>(); |
16 | | - |
17 | | -/// Serialized size of `StakeHistory` sysvar account |
18 | | -pub const SIZE: usize = LEN_PREFIX + MAX_ENTRIES * EPOCH_AND_ENTRY_SERIALIZED_SIZE; |
19 | | -const _: () = assert!(SIZE == 16_392); |
20 | | - |
21 | | -#[repr(C)] |
22 | | -#[cfg_attr( |
23 | | - feature = "frozen-abi", |
24 | | - derive( |
25 | | - solana_frozen_abi_macro::AbiExample, |
26 | | - solana_frozen_abi_macro::StableAbi, |
27 | | - solana_frozen_abi_macro::StableAbiSample |
28 | | - ) |
29 | | -)] |
30 | | -#[cfg_attr( |
31 | | - feature = "serde", |
32 | | - derive(serde_derive::Deserialize, serde_derive::Serialize) |
33 | | -)] |
34 | | -#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))] |
35 | | -#[derive(Debug, PartialEq, Eq, Default, Clone)] |
36 | | -pub struct StakeHistoryEntry { |
37 | | - pub effective: u64, // effective stake at this epoch |
38 | | - pub activating: u64, // sum of portion of stakes not fully warmed up |
39 | | - pub deactivating: u64, // requested to be cooled down, not fully deactivated yet |
40 | | -} |
41 | | - |
42 | | -impl StakeHistoryEntry { |
43 | | - pub fn with_effective(effective: u64) -> Self { |
44 | | - Self { |
45 | | - effective, |
46 | | - ..Self::default() |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - pub fn with_effective_and_activating(effective: u64, activating: u64) -> Self { |
51 | | - Self { |
52 | | - effective, |
53 | | - activating, |
54 | | - ..Self::default() |
55 | | - } |
56 | | - } |
57 | | - |
58 | | - pub fn with_deactivating(deactivating: u64) -> Self { |
59 | | - Self { |
60 | | - effective: deactivating, |
61 | | - deactivating, |
62 | | - ..Self::default() |
63 | | - } |
64 | | - } |
65 | | - |
66 | | - pub fn checked_add(self, rhs: StakeHistoryEntry) -> Option<Self> { |
67 | | - Some(Self { |
68 | | - effective: self.effective.checked_add(rhs.effective)?, |
69 | | - activating: self.activating.checked_add(rhs.activating)?, |
70 | | - deactivating: self.deactivating.checked_add(rhs.deactivating)?, |
71 | | - }) |
72 | | - } |
73 | | - |
74 | | - pub fn wrapping_add(self, rhs: StakeHistoryEntry) -> Self { |
75 | | - Self { |
76 | | - effective: self.effective.wrapping_add(rhs.effective), |
77 | | - activating: self.activating.wrapping_add(rhs.activating), |
78 | | - deactivating: self.deactivating.wrapping_add(rhs.deactivating), |
79 | | - } |
80 | | - } |
81 | | - |
82 | | - pub fn saturating_add(self, rhs: StakeHistoryEntry) -> Self { |
83 | | - Self { |
84 | | - effective: self.effective.saturating_add(rhs.effective), |
85 | | - activating: self.activating.saturating_add(rhs.activating), |
86 | | - deactivating: self.deactivating.saturating_add(rhs.deactivating), |
87 | | - } |
88 | | - } |
89 | | -} |
90 | | - |
91 | | -// TODO: Remove once we are comfortable with adding breaking changes. |
92 | | -impl std::ops::Add for StakeHistoryEntry { |
93 | | - type Output = StakeHistoryEntry; |
94 | | - fn add(self, rhs: StakeHistoryEntry) -> Self::Output { |
95 | | - Self { |
96 | | - effective: self.effective.saturating_add(rhs.effective), |
97 | | - activating: self.activating.saturating_add(rhs.activating), |
98 | | - deactivating: self.deactivating.saturating_add(rhs.deactivating), |
99 | | - } |
100 | | - } |
101 | | -} |
102 | | - |
103 | | -#[repr(C)] |
104 | | -#[cfg_attr( |
105 | | - feature = "frozen-abi", |
106 | | - derive( |
107 | | - solana_frozen_abi_macro::AbiExample, |
108 | | - solana_frozen_abi_macro::StableAbi, |
109 | | - solana_frozen_abi_macro::StableAbiSample |
110 | | - ) |
111 | | -)] |
112 | | -#[cfg_attr( |
113 | | - feature = "serde", |
114 | | - derive(serde_derive::Deserialize, serde_derive::Serialize) |
115 | | -)] |
116 | | -#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))] |
117 | | -#[derive(Debug, PartialEq, Eq, Default, Clone)] |
118 | | -pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>); |
119 | | - |
120 | | -impl StakeHistory { |
121 | | - #[inline] |
122 | | - fn latest_epoch(&self) -> Option<&Epoch> { |
123 | | - self.first().map(|(epoch, _)| epoch) |
124 | | - } |
125 | | - |
126 | | - pub fn get(&self, epoch: Epoch) -> Option<&StakeHistoryEntry> { |
127 | | - self.latest_epoch() |
128 | | - .and_then(|latest| latest.checked_sub(epoch)) |
129 | | - .and_then(|index| self.0.get(index as usize).map(|(_, entry)| entry)) |
130 | | - } |
131 | | - |
132 | | - pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) { |
133 | | - match self.binary_search_by(|probe| epoch.cmp(&probe.0)) { |
134 | | - Ok(index) => (self.0)[index] = (epoch, entry), |
135 | | - Err(index) => (self.0).insert(index, (epoch, entry)), |
136 | | - } |
137 | | - (self.0).truncate(MAX_ENTRIES); |
138 | | - } |
139 | | -} |
140 | | - |
141 | | -impl Deref for StakeHistory { |
142 | | - type Target = Vec<(Epoch, StakeHistoryEntry)>; |
143 | | - fn deref(&self) -> &Self::Target { |
144 | | - &self.0 |
145 | | - } |
146 | | -} |
147 | | - |
148 | | -pub trait StakeHistoryGetEntry { |
149 | | - fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry>; |
150 | | -} |
151 | | - |
152 | | -impl StakeHistoryGetEntry for StakeHistory { |
153 | | - fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry> { |
154 | | - self.get(epoch).map(|entry| entry.to_owned()) |
155 | | - } |
156 | | -} |
157 | | - |
158 | | -#[cfg(test)] |
159 | | -mod tests { |
160 | | - use {super::*, solana_sysvar_id::SysvarId}; |
161 | | - |
162 | | - #[test] |
163 | | - fn test_stake_history() { |
164 | | - let mut stake_history = StakeHistory::default(); |
165 | | - |
166 | | - let current_epoch = MAX_ENTRIES as u64 + 1; |
167 | | - for i in 0..current_epoch { |
168 | | - stake_history.add( |
169 | | - i, |
170 | | - StakeHistoryEntry { |
171 | | - activating: i, |
172 | | - ..StakeHistoryEntry::default() |
173 | | - }, |
174 | | - ); |
175 | | - } |
176 | | - assert_eq!(stake_history.len(), MAX_ENTRIES); |
177 | | - assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1); |
178 | | - assert_eq!(stake_history.get(0), None); |
179 | | - for epoch in 1..current_epoch { |
180 | | - assert_eq!( |
181 | | - stake_history.get(epoch), |
182 | | - Some(&StakeHistoryEntry { |
183 | | - activating: epoch, |
184 | | - ..StakeHistoryEntry::default() |
185 | | - }) |
186 | | - ); |
187 | | - } |
188 | | - assert_eq!(stake_history.get(current_epoch), None); |
189 | | - } |
190 | | - |
191 | | - #[test] |
192 | | - fn test_id() { |
193 | | - assert_eq!( |
194 | | - StakeHistory::id(), |
195 | | - solana_sdk_ids::sysvar::stake_history::id() |
196 | | - ); |
197 | | - } |
198 | | -} |
| 5 | +pub use solana_stake_history::{ |
| 6 | + Epoch, StakeHistory, StakeHistoryEntry, StakeHistoryGetEntry, MAX_ENTRIES, SIZE, |
| 7 | +}; |
0 commit comments