Skip to main content

solana_vote_program/vote_state/
handler.rs

1//! Vote state handler API.
2//!
3//! Wraps the vote state behind a "handler" API to support converting from an
4//! existing vote state version to whichever version is the target (or
5//! "current") vote state version.
6//!
7//! The program must be generic over whichever vote state version is the
8//! target, since at compile time the target version is not known (can be
9//! changed with a feature gate). For this reason, the handler offers a
10//! getter and setter API around vote state, for all operations required by the
11//! vote program.
12
13#[cfg(feature = "dev-context-only-utils")]
14use qualifier_attr::qualifiers;
15#[cfg(test)]
16use solana_vote_interface::authorized_voters::AuthorizedVoters;
17use {
18    solana_clock::{Clock, Epoch, Slot, UnixTimestamp},
19    solana_instruction_error::InstructionError,
20    solana_pubkey::Pubkey,
21    solana_transaction_context::instruction_accounts::BorrowedInstructionAccount,
22    solana_vote_interface::{
23        error::VoteError,
24        state::{
25            BLS_PUBLIC_KEY_COMPRESSED_SIZE, BlockTimestamp, LandedVote, Lockout,
26            MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, VOTE_CREDITS_GRACE_SLOTS,
27            VOTE_CREDITS_MAXIMUM_PER_SLOT, VoteInit, VoteInitV2, VoteState1_14_11, VoteStateV3,
28            VoteStateV4,
29        },
30    },
31    std::collections::VecDeque,
32    wincode::{SchemaRead, SchemaWrite},
33};
34
35/// Copy of `VoteStateVersions` except that state is without a `Box`.
36/// Eliminates the cost of the `Box` when deserializing a `VoteAccount`.
37#[derive(Debug, PartialEq, Eq, SchemaRead)]
38pub enum VoteStateVersionsInline {
39    Uninitialized,
40    V1_14_11(VoteState1_14_11),
41    V3(VoteStateV3),
42    V4(VoteStateV4),
43}
44
45impl VoteStateVersionsInline {
46    pub(crate) fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
47        let versioned =
48            wincode::deserialize(input).map_err(|_| InstructionError::InvalidAccountData)?;
49        match versioned {
50            // Preserve VoteStateVersions::deserialize's rejection of discriminant 0.
51            Self::Uninitialized => Err(InstructionError::InvalidAccountData),
52            _ => Ok(versioned),
53        }
54    }
55
56    pub fn is_uninitialized(&self) -> bool {
57        match self {
58            Self::Uninitialized => true,
59            Self::V1_14_11(vote_state) => vote_state.is_uninitialized(),
60            Self::V3(vote_state) => vote_state.is_uninitialized(),
61            // As per SIMD-0185, v4 is always initialized.
62            Self::V4(_) => false,
63        }
64    }
65}
66
67/// Copy of `VoteStateVersions` except that references to state are stored.
68/// Allows for faster serialization as data doesn't have to be moved around.
69#[derive(SchemaWrite)]
70enum VoteStateVersionsRef<'a> {
71    #[wincode(tag = 3)]
72    V4(&'a VoteStateV4),
73}
74
75/// The target version to convert all deserialized vote state into.
76#[derive(Clone, Copy, Debug, PartialEq)]
77pub enum VoteStateTargetVersion {
78    V4,
79    // New vote state versions will be added here...
80}
81
82#[allow(clippy::large_enum_variant)]
83#[derive(Clone, Debug, PartialEq)]
84enum TargetVoteState {
85    V4(VoteStateV4),
86    // New vote state versions will be added here...
87}
88
89/// Vote state handler for
90/// * Deserializing vote state
91/// * Converting vote state in-memory to target version
92/// * Operating on the vote state data agnostically
93/// * Serializing the resulting state to the vote account
94#[derive(Clone, Debug, PartialEq)]
95pub struct VoteStateHandler {
96    target_state: TargetVoteState,
97}
98
99impl VoteStateHandler {
100    pub(crate) fn authorized_withdrawer(&self) -> &Pubkey {
101        match &self.target_state {
102            TargetVoteState::V4(v4) => &v4.authorized_withdrawer,
103        }
104    }
105
106    pub(crate) fn set_authorized_withdrawer(&mut self, authorized_withdrawer: Pubkey) {
107        match &mut self.target_state {
108            TargetVoteState::V4(v4) => v4.authorized_withdrawer = authorized_withdrawer,
109        }
110    }
111
112    #[cfg(test)]
113    pub(crate) fn authorized_voters(&self) -> &AuthorizedVoters {
114        match &self.target_state {
115            TargetVoteState::V4(v4) => &v4.authorized_voters,
116        }
117    }
118
119    pub(crate) fn set_new_authorized_voter<F>(
120        &mut self,
121        authorized_pubkey: &Pubkey,
122        current_epoch: Epoch,
123        target_epoch: Epoch,
124        bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
125        verify: F,
126    ) -> Result<(), InstructionError>
127    where
128        F: Fn(Pubkey) -> Result<(), InstructionError>,
129    {
130        let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
131        verify(epoch_authorized_voter)?;
132
133        match &mut self.target_state {
134            TargetVoteState::V4(v4) => {
135                // The offset in slots `n` on which the target_epoch
136                // (default value `DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET`) is
137                // calculated is the number of slots available from the
138                // first slot `S` of an epoch in which to set a new voter for
139                // the epoch at `S` + `n`
140                if v4.authorized_voters.contains(target_epoch) {
141                    return Err(VoteError::TooSoonToReauthorize.into());
142                }
143
144                v4.authorized_voters
145                    .insert(target_epoch, *authorized_pubkey);
146
147                if bls_pubkey.is_some() {
148                    v4.bls_pubkey_compressed = bls_pubkey.copied();
149                }
150
151                Ok(())
152            }
153        }
154    }
155
156    pub(crate) fn get_and_update_authorized_voter(
157        &mut self,
158        current_epoch: Epoch,
159    ) -> Result<Pubkey, InstructionError> {
160        match &mut self.target_state {
161            TargetVoteState::V4(v4) => {
162                let pubkey = v4
163                    .authorized_voters
164                    .get_and_cache_authorized_voter_for_epoch(current_epoch)
165                    .ok_or(InstructionError::InvalidAccountData)?;
166                // Per SIMD-0185, v4 retains voters for `current_epoch - 1` through
167                // `current_epoch + 2`. Only purge entries for epochs less than
168                // `current_epoch - 1`.
169                v4.authorized_voters
170                    .purge_authorized_voters(current_epoch.saturating_sub(1));
171                Ok(pubkey)
172            }
173        }
174    }
175
176    pub(crate) fn commission(&self) -> u8 {
177        match &self.target_state {
178            TargetVoteState::V4(v4) => {
179                (v4.inflation_rewards_commission_bps / 100).min(u8::MAX as u16) as u8
180            }
181        }
182    }
183
184    #[allow(clippy::arithmetic_side_effects)]
185    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
186    pub(crate) fn set_commission(&mut self, commission: u8) {
187        match &mut self.target_state {
188            TargetVoteState::V4(v4) => {
189                // Safety: u16::MAX > u8::MAX * 100
190                v4.inflation_rewards_commission_bps = (commission as u16) * 100;
191            }
192        }
193    }
194
195    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
196    pub(crate) fn set_inflation_rewards_commission_bps(&mut self, commission_bps: u16) {
197        match &mut self.target_state {
198            TargetVoteState::V4(v4) => v4.inflation_rewards_commission_bps = commission_bps,
199        }
200    }
201
202    pub(crate) fn set_block_revenue_commission_bps(&mut self, commission_bps: u16) {
203        match &mut self.target_state {
204            TargetVoteState::V4(v4) => v4.block_revenue_commission_bps = commission_bps,
205        }
206    }
207
208    pub fn node_pubkey(&self) -> &Pubkey {
209        match &self.target_state {
210            TargetVoteState::V4(v4) => &v4.node_pubkey,
211        }
212    }
213
214    pub(crate) fn set_node_pubkey(&mut self, node_pubkey: Pubkey) {
215        match &mut self.target_state {
216            TargetVoteState::V4(v4) => v4.node_pubkey = node_pubkey,
217        }
218    }
219
220    pub(crate) fn set_inflation_rewards_collector(&mut self, collector: Pubkey) {
221        match &mut self.target_state {
222            TargetVoteState::V4(v4) => v4.inflation_rewards_collector = collector,
223        }
224    }
225
226    pub(crate) fn set_block_revenue_collector(&mut self, collector: Pubkey) {
227        match &mut self.target_state {
228            TargetVoteState::V4(v4) => v4.block_revenue_collector = collector,
229        }
230    }
231
232    pub(crate) fn pending_delegator_rewards(&self) -> u64 {
233        match &self.target_state {
234            TargetVoteState::V4(v4) => v4.pending_delegator_rewards,
235        }
236    }
237
238    pub(crate) fn add_pending_delegator_rewards(
239        &mut self,
240        amount: u64,
241    ) -> Result<(), InstructionError> {
242        match &mut self.target_state {
243            TargetVoteState::V4(v4) => {
244                v4.pending_delegator_rewards = v4
245                    .pending_delegator_rewards
246                    .checked_add(amount)
247                    .ok_or(InstructionError::ArithmeticOverflow)?;
248                Ok(())
249            }
250        }
251    }
252
253    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
254    pub(crate) fn votes(&self) -> &VecDeque<LandedVote> {
255        match &self.target_state {
256            TargetVoteState::V4(v4) => &v4.votes,
257        }
258    }
259
260    pub(crate) fn votes_mut(&mut self) -> &mut VecDeque<LandedVote> {
261        match &mut self.target_state {
262            TargetVoteState::V4(v4) => &mut v4.votes,
263        }
264    }
265
266    pub fn set_votes(&mut self, votes: VecDeque<LandedVote>) {
267        match &mut self.target_state {
268            TargetVoteState::V4(v4) => v4.votes = votes,
269        }
270    }
271
272    pub(crate) fn contains_slot(&self, candidate_slot: Slot) -> bool {
273        match &self.target_state {
274            TargetVoteState::V4(v4) => v4
275                .votes
276                .binary_search_by(|vote| vote.slot().cmp(&candidate_slot))
277                .is_ok(),
278        }
279    }
280
281    pub(crate) fn last_lockout(&self) -> Option<&Lockout> {
282        match &self.target_state {
283            TargetVoteState::V4(v4) => v4.votes.back().map(|vote| &vote.lockout),
284        }
285    }
286
287    pub fn last_voted_slot(&self) -> Option<Slot> {
288        self.last_lockout().map(|v| v.slot())
289    }
290
291    pub fn root_slot(&self) -> Option<Slot> {
292        match &self.target_state {
293            TargetVoteState::V4(v4) => v4.root_slot,
294        }
295    }
296
297    pub fn set_root_slot(&mut self, root_slot: Option<Slot>) {
298        match &mut self.target_state {
299            TargetVoteState::V4(v4) => v4.root_slot = root_slot,
300        }
301    }
302
303    pub(crate) fn current_epoch(&self) -> Epoch {
304        match &self.target_state {
305            TargetVoteState::V4(v4) => {
306                if v4.epoch_credits.is_empty() {
307                    0
308                } else {
309                    v4.epoch_credits.last().unwrap().0
310                }
311            }
312        }
313    }
314
315    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
316    pub(crate) fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
317        match &self.target_state {
318            TargetVoteState::V4(v4) => &v4.epoch_credits,
319        }
320    }
321
322    pub fn epoch_credits_mut(&mut self) -> &mut Vec<(Epoch, u64, u64)> {
323        match &mut self.target_state {
324            TargetVoteState::V4(v4) => &mut v4.epoch_credits,
325        }
326    }
327
328    pub fn last_timestamp(&self) -> &BlockTimestamp {
329        match &self.target_state {
330            TargetVoteState::V4(v4) => &v4.last_timestamp,
331        }
332    }
333
334    pub fn set_last_timestamp(&mut self, timestamp: BlockTimestamp) {
335        match &mut self.target_state {
336            TargetVoteState::V4(v4) => v4.last_timestamp = timestamp,
337        }
338    }
339
340    pub(crate) fn set_vote_account_state(
341        &self,
342        vote_account: &mut BorrowedInstructionAccount,
343    ) -> Result<(), InstructionError> {
344        match &self.target_state {
345            TargetVoteState::V4(v4) => {
346                // If the account is not large enough to store the vote state, then attempt a realloc to make it large enough.
347                // The realloc can only proceed if the vote account has balance sufficient for rent exemption at the new size.
348                if (vote_account.get_data().len() < VoteStateV4::size_of())
349                    && (!vote_account.is_rent_exempt_at_data_length(VoteStateV4::size_of())
350                        || vote_account
351                            .set_data_length(VoteStateV4::size_of())
352                            .is_err())
353                {
354                    // Unlike with conversions to v3, we will not gracefully default to
355                    // storing a v1_14_11. Instead, throw an error, as per SIMD-0185.
356                    return Err(InstructionError::AccountNotRentExempt);
357                }
358                // Vote account is large enough to store the newest version of vote state
359                let state = VoteStateVersionsRef::V4(v4);
360                let data = vote_account.get_data_mut()?;
361                let serialized_size =
362                    wincode::serialized_size(&state).map_err(|_| InstructionError::GenericError)?;
363                if serialized_size > data.len() as u64 {
364                    return Err(InstructionError::AccountDataTooSmall);
365                }
366                wincode::serialize_into(data, &state).map_err(|_| InstructionError::GenericError)
367            }
368        }
369    }
370
371    pub(crate) fn has_bls_pubkey(&self) -> bool {
372        match &self.target_state {
373            TargetVoteState::V4(v4) => v4.bls_pubkey_compressed.is_some(),
374        }
375    }
376
377    pub fn init_vote_account_state(
378        vote_account: &mut BorrowedInstructionAccount,
379        vote_init: &VoteInit,
380        clock: &Clock,
381        target_version: VoteStateTargetVersion,
382    ) -> Result<(), InstructionError> {
383        let handler = match target_version {
384            VoteStateTargetVersion::V4 => {
385                let vote_state =
386                    VoteStateV4::new_with_defaults(vote_account.get_key(), vote_init, clock);
387                Self::new_v4(vote_state)
388            }
389        };
390        handler.set_vote_account_state(vote_account)
391    }
392
393    pub fn init_vote_account_state_v2(
394        vote_account: &mut BorrowedInstructionAccount,
395        vote_init: &VoteInitV2,
396        inflation_rewards_collector: &Pubkey,
397        block_revenue_collector: &Pubkey,
398        clock: &Clock,
399        target_version: VoteStateTargetVersion,
400    ) -> Result<(), InstructionError> {
401        let handler = match target_version {
402            VoteStateTargetVersion::V4 => {
403                let vote_state = VoteStateV4::new(
404                    vote_init,
405                    inflation_rewards_collector,
406                    block_revenue_collector,
407                    clock,
408                );
409                Self::new_v4(vote_state)
410            }
411        };
412        handler.set_vote_account_state(vote_account)
413    }
414
415    pub fn deinitialize_vote_account_state(
416        vote_account: &mut BorrowedInstructionAccount,
417        target_version: VoteStateTargetVersion,
418    ) -> Result<(), InstructionError> {
419        match target_version {
420            VoteStateTargetVersion::V4 => {
421                // As per SIMD-0185, clear the entire account.
422                vote_account.get_data_mut()?.fill(0);
423                Ok(())
424            }
425        }
426    }
427
428    pub fn check_vote_account_length(
429        vote_account: &mut BorrowedInstructionAccount,
430        target_version: VoteStateTargetVersion,
431    ) -> Result<(), InstructionError> {
432        let length = vote_account.get_data().len();
433        let expected = match target_version {
434            VoteStateTargetVersion::V4 => VoteStateV4::size_of(),
435        };
436        if length != expected {
437            Err(InstructionError::InvalidAccountData)
438        } else {
439            Ok(())
440        }
441    }
442
443    pub(crate) fn credits_for_vote_at_index(&self, index: usize) -> u64 {
444        let latency = self
445            .votes()
446            .get(index)
447            .map_or(0, |landed_vote| landed_vote.latency);
448
449        // If latency is 0, this means that the Lockout was created and stored from a software version that did not
450        // store vote latencies; in this case, 1 credit is awarded
451        if latency == 0 {
452            1
453        } else {
454            match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) {
455                None | Some(0) => {
456                    // latency was <= VOTE_CREDITS_GRACE_SLOTS, so maximum credits are awarded
457                    VOTE_CREDITS_MAXIMUM_PER_SLOT as u64
458                }
459
460                Some(diff) => {
461                    // diff = latency - VOTE_CREDITS_GRACE_SLOTS, and diff > 0
462                    // Subtract diff from VOTE_CREDITS_MAXIMUM_PER_SLOT which is the number of credits to award
463                    match VOTE_CREDITS_MAXIMUM_PER_SLOT.checked_sub(diff) {
464                        // If diff >= VOTE_CREDITS_MAXIMUM_PER_SLOT, 1 credit is awarded
465                        None | Some(0) => 1,
466
467                        Some(credits) => credits as u64,
468                    }
469                }
470            }
471        }
472    }
473
474    pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
475        // increment credits, record by epoch
476
477        // never seen a credit
478        if self.epoch_credits().is_empty() {
479            self.epoch_credits_mut().push((epoch, 0, 0));
480        } else if epoch != self.epoch_credits().last().unwrap().0 {
481            let (_, credits, prev_credits) = *self.epoch_credits().last().unwrap();
482
483            if credits != prev_credits {
484                // if credits were earned previous epoch
485                // append entry at end of list for the new epoch
486                self.epoch_credits_mut().push((epoch, credits, credits));
487            } else {
488                // else just move the current epoch
489                self.epoch_credits_mut().last_mut().unwrap().0 = epoch;
490            }
491
492            // Remove too old epoch_credits
493            if self.epoch_credits().len() > MAX_EPOCH_CREDITS_HISTORY {
494                self.epoch_credits_mut().remove(0);
495            }
496        }
497
498        self.epoch_credits_mut().last_mut().unwrap().1 = self
499            .epoch_credits()
500            .last()
501            .unwrap()
502            .1
503            .saturating_add(credits);
504    }
505
506    pub(crate) fn process_timestamp(
507        &mut self,
508        slot: Slot,
509        timestamp: UnixTimestamp,
510    ) -> Result<(), VoteError> {
511        let last_timestamp = self.last_timestamp();
512        if (slot < last_timestamp.slot || timestamp < last_timestamp.timestamp)
513            || (slot == last_timestamp.slot
514                && &BlockTimestamp { slot, timestamp } != last_timestamp
515                && last_timestamp.slot != 0)
516        {
517            return Err(VoteError::TimestampTooOld);
518        }
519        self.set_last_timestamp(BlockTimestamp { slot, timestamp });
520        Ok(())
521    }
522
523    fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
524        while let Some(vote) = self.last_lockout() {
525            if !vote.is_locked_out_at_slot(next_vote_slot) {
526                self.votes_mut().pop_back();
527            } else {
528                break;
529            }
530        }
531    }
532
533    fn double_lockouts(&mut self) {
534        let stack_depth = self.votes().len();
535        for (i, v) in self.votes_mut().iter_mut().enumerate() {
536            // Don't increase the lockout for this vote until we get more confirmations
537            // than the max number of confirmations this vote has seen
538            if stack_depth
539                > i.checked_add(v.confirmation_count() as usize).expect(
540                    "`confirmation_count` and tower_size should be bounded by \
541                     `MAX_LOCKOUT_HISTORY`",
542                )
543            {
544                v.lockout.increase_confirmation_count(1);
545            }
546        }
547    }
548
549    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
550    pub(crate) fn process_next_vote_slot(
551        &mut self,
552        next_vote_slot: Slot,
553        epoch: Epoch,
554        current_slot: Slot,
555    ) {
556        // Ignore votes for slots earlier than we already have votes for
557        if self
558            .last_voted_slot()
559            .is_some_and(|last_voted_slot| next_vote_slot <= last_voted_slot)
560        {
561            return;
562        }
563
564        self.pop_expired_votes(next_vote_slot);
565
566        let landed_vote = LandedVote {
567            latency: compute_vote_latency(next_vote_slot, current_slot),
568            lockout: Lockout::new(next_vote_slot),
569        };
570
571        // Once the stack is full, pop the oldest lockout and distribute rewards
572        if self.votes().len() == MAX_LOCKOUT_HISTORY {
573            let credits = self.credits_for_vote_at_index(0);
574            let landed_vote = self.votes_mut().pop_front().unwrap();
575            self.set_root_slot(Some(landed_vote.slot()));
576
577            self.increment_credits(epoch, credits);
578        }
579        self.votes_mut().push_back(landed_vote);
580        self.double_lockouts();
581    }
582
583    #[cfg(test)]
584    pub(crate) fn credits(&self) -> u64 {
585        if self.epoch_credits().is_empty() {
586            0
587        } else {
588            self.epoch_credits().last().unwrap().1
589        }
590    }
591
592    #[cfg(test)]
593    pub(crate) fn nth_recent_lockout(&self, position: usize) -> Option<&Lockout> {
594        if position < self.votes().len() {
595            let pos = self
596                .votes()
597                .len()
598                .checked_sub(position)
599                .and_then(|pos| pos.checked_sub(1))?;
600            self.votes().get(pos).map(|vote| &vote.lockout)
601        } else {
602            None
603        }
604    }
605
606    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
607    pub(crate) fn new_v4(vote_state: VoteStateV4) -> Self {
608        Self {
609            target_state: TargetVoteState::V4(vote_state),
610        }
611    }
612
613    /// Constructs a `VoteStateHandler` of the same version as the input `VoteStateVersions`.
614    ///
615    /// Returns `Err(InstructionError::InvalidAccountData)` if the version of the input
616    /// `VoteStateVersions` is not supported by `VoteStateHandler`.
617    pub fn try_new_from_vote_state_versions(
618        versions: VoteStateVersionsInline,
619    ) -> Result<Self, InstructionError> {
620        match versions {
621            VoteStateVersionsInline::V4(state) => Ok(Self::new_v4(state)),
622            VoteStateVersionsInline::V3(_)
623            | VoteStateVersionsInline::V1_14_11(_)
624            | VoteStateVersionsInline::Uninitialized => Err(InstructionError::InvalidAccountData),
625        }
626    }
627
628    #[cfg(any(test, feature = "dev-context-only-utils"))]
629    pub fn default_v4() -> Self {
630        Self::new_v4(VoteStateV4::default())
631    }
632
633    #[cfg(any(test, feature = "dev-context-only-utils"))]
634    pub fn as_ref_v4(&self) -> &VoteStateV4 {
635        match &self.target_state {
636            TargetVoteState::V4(v4) => v4,
637        }
638    }
639
640    #[cfg(any(test, feature = "dev-context-only-utils"))]
641    pub fn unwrap_v4(self) -> VoteStateV4 {
642        match self.target_state {
643            TargetVoteState::V4(v4) => v4,
644        }
645    }
646
647    /// Serializes `self` into the provided `data` buffer.
648    ///
649    /// Returns `Err(InstructionError::InvalidAccountData)` if serialization fails.
650    pub fn serialize_into(&self, data: &mut [u8]) -> Result<(), InstructionError> {
651        match &self.target_state {
652            TargetVoteState::V4(v4) => {
653                let versioned = VoteStateVersionsRef::V4(v4);
654                wincode::serialize_into(data, &versioned)
655                    .map_err(|_e| InstructionError::InvalidAccountData)
656            }
657        }
658    }
659
660    /// Serializes `self` into a new buffer of `len` bytes, zero-filling the bytes past the
661    /// encoding.
662    ///
663    /// Returns `Err(InstructionError::InvalidAccountData)` if the encoding exceeds `len` bytes.
664    pub fn serialize_padded(&self, len: usize) -> Result<Vec<u8>, InstructionError> {
665        let mut data = Vec::with_capacity(len);
666        match &self.target_state {
667            TargetVoteState::V4(v4) => {
668                let versioned = VoteStateVersionsRef::V4(v4);
669                wincode::serialize_into(&mut data, &versioned)
670                    .map_err(|_e| InstructionError::InvalidAccountData)?;
671            }
672        }
673        if data.len() > len {
674            return Err(InstructionError::InvalidAccountData);
675        }
676        data.resize(len, 0);
677        Ok(data)
678    }
679
680    #[cfg(test)]
681    pub fn serialize(self) -> Vec<u8> {
682        self.serialize_padded(VoteStateV4::size_of()).unwrap()
683    }
684}
685
686// Computes the vote latency for vote on voted_for_slot where the vote itself landed in current_slot
687pub(crate) fn compute_vote_latency(voted_for_slot: Slot, current_slot: Slot) -> u8 {
688    std::cmp::min(current_slot.saturating_sub(voted_for_slot), u8::MAX as u64) as u8
689}
690
691pub(crate) fn try_convert_to_vote_state_v4(
692    versioned: VoteStateVersionsInline,
693    vote_pubkey: &Pubkey,
694) -> Result<VoteStateV4, InstructionError> {
695    match versioned {
696        VoteStateVersionsInline::Uninitialized => Err(InstructionError::UninitializedAccount),
697        VoteStateVersionsInline::V1_14_11(state) => Ok(VoteStateV4 {
698            node_pubkey: state.node_pubkey,
699            authorized_withdrawer: state.authorized_withdrawer,
700            inflation_rewards_collector: *vote_pubkey,
701            block_revenue_collector: state.node_pubkey,
702            inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
703            block_revenue_commission_bps: 10_000u16,
704            pending_delegator_rewards: 0,
705            bls_pubkey_compressed: None,
706            votes: landed_votes_from_lockouts(state.votes),
707            root_slot: state.root_slot,
708            authorized_voters: state.authorized_voters.clone(),
709            epoch_credits: state.epoch_credits,
710            last_timestamp: state.last_timestamp,
711        }),
712        VoteStateVersionsInline::V3(state) => Ok(VoteStateV4 {
713            node_pubkey: state.node_pubkey,
714            authorized_withdrawer: state.authorized_withdrawer,
715            inflation_rewards_collector: *vote_pubkey,
716            block_revenue_collector: state.node_pubkey,
717            inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
718            block_revenue_commission_bps: 10_000u16,
719            pending_delegator_rewards: 0,
720            bls_pubkey_compressed: None,
721            votes: state.votes,
722            root_slot: state.root_slot,
723            authorized_voters: state.authorized_voters,
724            epoch_credits: state.epoch_credits,
725            last_timestamp: state.last_timestamp,
726        }),
727        VoteStateVersionsInline::V4(state) => Ok(state),
728    }
729}
730
731fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
732    lockouts.into_iter().map(|lockout| lockout.into()).collect()
733}
734
735#[allow(clippy::arithmetic_side_effects)]
736#[allow(clippy::type_complexity)]
737#[cfg(test)]
738mod tests {
739    use {
740        super::*,
741        crate::id,
742        solana_account::AccountSharedData,
743        solana_clock::Clock,
744        solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
745        solana_pubkey::Pubkey,
746        solana_rent::Rent,
747        solana_sdk_ids::native_loader,
748        solana_transaction_context::{
749            instruction_accounts::InstructionAccount, transaction::TransactionContext,
750        },
751        solana_vote_interface::{
752            authorized_voters::AuthorizedVoters,
753            state::{
754                BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BlockTimestamp, MAX_EPOCH_CREDITS_HISTORY,
755                MAX_LOCKOUT_HISTORY, VoteInit, VoteState1_14_11, VoteStateV3, VoteStateVersions,
756            },
757        },
758        std::collections::VecDeque,
759        test_case::test_case,
760    };
761
762    /// Default block revenue commission rate in basis points (100%) per SIMD-0185.
763    const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000;
764
765    #[test]
766    fn test_borrowed_vote_state_versions_compatibility() {
767        let v1 = VoteState1_14_11::default();
768        let v3 = VoteStateV3::default();
769        let v4 = get_max_sized_vote_state_v4();
770        let versions = [
771            (
772                None,
773                VoteStateVersions::V1_14_11(Box::new(v1.clone())),
774                VoteStateVersionsInline::V1_14_11(v1.clone()),
775            ),
776            (
777                None,
778                VoteStateVersions::V3(Box::new(v3.clone())),
779                VoteStateVersionsInline::V3(v3.clone()),
780            ),
781            (
782                Some(VoteStateVersionsRef::V4(&v4)),
783                VoteStateVersions::V4(Box::new(v4.clone())),
784                VoteStateVersionsInline::V4(v4.clone()),
785            ),
786        ];
787        for (borrowed, orig, inline) in versions {
788            let bytes = wincode::serialize(&orig).unwrap();
789            assert_eq!(
790                VoteStateVersionsInline::deserialize(&bytes).unwrap(),
791                inline
792            );
793            if let Some(borrowed) = borrowed {
794                let borrowed_bytes = wincode::serialize(&borrowed).unwrap();
795                assert_eq!(borrowed_bytes, bytes);
796                assert_eq!(
797                    VoteStateVersionsInline::deserialize(&borrowed_bytes).unwrap(),
798                    inline
799                );
800            }
801        }
802    }
803
804    #[test]
805    fn test_inline_vote_state_rejects_uninitialized() {
806        let bytes = wincode::serialize(&VoteStateVersions::Uninitialized).unwrap();
807        assert_eq!(
808            VoteStateVersionsInline::deserialize(&bytes),
809            Err(InstructionError::InvalidAccountData),
810        );
811    }
812
813    #[test]
814    fn test_vote_state_handler_compatibility() {
815        let v4 = get_max_sized_vote_state_v4();
816        let expected = wincode::serialize(&VoteStateVersions::V4(Box::new(v4.clone()))).unwrap();
817        let handler = VoteStateHandler::new_v4(v4);
818        let mut data = vec![0; VoteStateV4::size_of()];
819        handler.serialize_into(&mut data).unwrap();
820        assert_eq!(&data[..expected.len()], expected);
821        let decoded = VoteStateVersionsInline::deserialize(&data).unwrap();
822        assert_eq!(
823            VoteStateHandler::try_new_from_vote_state_versions(decoded).unwrap(),
824            handler,
825        );
826
827        let mut undersized_data = vec![0; expected.len() - 1];
828        assert_eq!(
829            handler.serialize_into(&mut undersized_data),
830            Err(InstructionError::InvalidAccountData),
831        );
832
833        assert_eq!(handler.serialize_padded(data.len()), Ok(data));
834        assert_eq!(
835            handler.serialize_padded(expected.len()).as_ref(),
836            Ok(&expected)
837        );
838        assert_eq!(
839            handler.serialize_padded(expected.len() - 1),
840            Err(InstructionError::InvalidAccountData),
841        );
842    }
843
844    fn mock_transaction_context(
845        vote_pubkey: Pubkey,
846        vote_account: AccountSharedData,
847        rent: Rent,
848    ) -> TransactionContext<'static> {
849        let program_account = AccountSharedData::new(0, 0, &native_loader::id());
850        let mut transaction_context = TransactionContext::new(
851            vec![(id(), program_account), (vote_pubkey, vote_account)],
852            rent,
853            0,
854            0,
855            1,
856        );
857        transaction_context
858            .configure_top_level_instruction_for_tests(
859                0,
860                vec![InstructionAccount::new(1, false, true)],
861                vec![],
862            )
863            .unwrap();
864        transaction_context
865    }
866
867    fn get_max_sized_vote_state_v4() -> VoteStateV4 {
868        let mut authorized_voters = AuthorizedVoters::default();
869        for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
870            authorized_voters.insert(i, Pubkey::new_unique());
871        }
872
873        VoteStateV4 {
874            votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
875            root_slot: Some(u64::MAX),
876            epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
877            authorized_voters,
878            bls_pubkey_compressed: Some([255; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
879            ..Default::default()
880        }
881    }
882
883    fn set_new_authorized_voter_and_assert(
884        vote_state: &mut VoteStateHandler,
885        original_voter: Pubkey,
886        epoch_offset: Epoch,
887    ) {
888        let new_voter = Pubkey::new_unique();
889        // Set a new authorized voter
890        vote_state
891            .set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(()))
892            .unwrap();
893
894        // Trying to set authorized voter for same epoch again should fail
895        assert_eq!(
896            vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())),
897            Err(VoteError::TooSoonToReauthorize.into())
898        );
899
900        // Setting the same authorized voter again should succeed
901        vote_state
902            .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, None, |_| Ok(()))
903            .unwrap();
904
905        // Set a third and fourth authorized voter
906        let new_voter2 = Pubkey::new_unique();
907        vote_state
908            .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, None, |_| Ok(()))
909            .unwrap();
910
911        let new_voter3 = Pubkey::new_unique();
912        vote_state
913            .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, None, |_| Ok(()))
914            .unwrap();
915
916        // Check can set back to original voter
917        vote_state
918            .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, None, |_| Ok(()))
919            .unwrap();
920
921        // Run with these voters for a while, check the ranges of authorized
922        // voters is correct
923        for i in 9..epoch_offset {
924            assert_eq!(
925                vote_state.get_and_update_authorized_voter(i).unwrap(),
926                original_voter
927            );
928        }
929        for i in epoch_offset..3 + epoch_offset {
930            assert_eq!(
931                vote_state.get_and_update_authorized_voter(i).unwrap(),
932                new_voter
933            );
934        }
935        for i in 3 + epoch_offset..6 + epoch_offset {
936            assert_eq!(
937                vote_state.get_and_update_authorized_voter(i).unwrap(),
938                new_voter2
939            );
940        }
941        for i in 6 + epoch_offset..9 + epoch_offset {
942            assert_eq!(
943                vote_state.get_and_update_authorized_voter(i).unwrap(),
944                new_voter3
945            );
946        }
947        for i in 9 + epoch_offset..=10 + epoch_offset {
948            assert_eq!(
949                vote_state.get_and_update_authorized_voter(i).unwrap(),
950                original_voter
951            );
952        }
953    }
954
955    #[test]
956    fn test_set_new_authorized_voter() {
957        let vote_pubkey = Pubkey::new_unique();
958        let original_voter = Pubkey::new_unique();
959        let epoch_offset = 15;
960
961        let vote_init = VoteInit {
962            node_pubkey: original_voter,
963            authorized_voter: original_voter,
964            authorized_withdrawer: original_voter,
965            commission: 0,
966        };
967        let clock = Clock::default();
968
969        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
970            &vote_pubkey,
971            &vote_init,
972            &clock,
973        ));
974
975        set_new_authorized_voter_and_assert(&mut vote_state, original_voter, epoch_offset);
976
977        {
978            let voter_a = Pubkey::new_unique();
979            let voter_b = Pubkey::new_unique();
980
981            let v4 = VoteStateV4 {
982                authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
983                ..Default::default()
984            };
985            let mut handler = VoteStateHandler::new_v4(v4);
986            handler
987                .set_new_authorized_voter(&voter_a, 0, 10, None, |_| Ok(()))
988                .unwrap();
989            handler
990                .set_new_authorized_voter(&voter_b, 0, 5, None, |_| Ok(()))
991                .unwrap();
992        }
993    }
994
995    fn assert_authorized_voter_is_locked_within_epoch(
996        vote_state: &mut VoteStateHandler,
997        original_voter: &Pubkey,
998    ) {
999        // Test that it's not possible to set a new authorized
1000        // voter within the same epoch, even if none has been
1001        // explicitly set before
1002        let new_voter = Pubkey::new_unique();
1003        assert_eq!(
1004            vote_state.set_new_authorized_voter(&new_voter, 1, 1, None, |_| Ok(())),
1005            Err(VoteError::TooSoonToReauthorize.into())
1006        );
1007        assert_eq!(
1008            vote_state.authorized_voters().get_authorized_voter(1),
1009            Some(*original_voter)
1010        );
1011        // Set a new authorized voter for a future epoch
1012        assert_eq!(
1013            vote_state.set_new_authorized_voter(&new_voter, 1, 2, None, |_| Ok(())),
1014            Ok(())
1015        );
1016        // Test that it's not possible to set a new authorized
1017        // voter within the same epoch, even if none has been
1018        // explicitly set before
1019        assert_eq!(
1020            vote_state.set_new_authorized_voter(original_voter, 3, 3, None, |_| Ok(())),
1021            Err(VoteError::TooSoonToReauthorize.into())
1022        );
1023        assert_eq!(
1024            vote_state.authorized_voters().get_authorized_voter(3),
1025            Some(new_voter)
1026        );
1027    }
1028
1029    #[test]
1030    fn test_authorized_voter_is_locked_within_epoch() {
1031        let vote_pubkey = Pubkey::new_unique();
1032        let original_voter = Pubkey::new_unique();
1033
1034        let vote_init = VoteInit {
1035            node_pubkey: original_voter,
1036            authorized_voter: original_voter,
1037            authorized_withdrawer: original_voter,
1038            commission: 0,
1039        };
1040        let clock = Clock::default();
1041
1042        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1043            &vote_pubkey,
1044            &vote_init,
1045            &clock,
1046        ));
1047        assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter);
1048    }
1049
1050    #[test]
1051    fn test_get_and_update_authorized_voter() {
1052        let vote_pubkey = Pubkey::new_unique();
1053        let original_voter = Pubkey::new_unique();
1054        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1055            &vote_pubkey,
1056            &VoteInit {
1057                node_pubkey: original_voter,
1058                authorized_voter: original_voter,
1059                authorized_withdrawer: original_voter,
1060                commission: 0,
1061            },
1062            &Clock::default(),
1063        ));
1064
1065        // Run the same exercise as the v3 test to start.
1066
1067        assert_eq!(vote_state.authorized_voters().len(), 1);
1068        assert_eq!(
1069            *vote_state.authorized_voters().first().unwrap().1,
1070            original_voter
1071        );
1072
1073        // If no new authorized voter was set, the same authorized voter
1074        // is locked into the next epoch
1075        assert_eq!(
1076            vote_state.get_and_update_authorized_voter(1).unwrap(),
1077            original_voter
1078        );
1079
1080        // Try to get the authorized voter for epoch 5, implies
1081        // the authorized voter for epochs 1-4 were unchanged
1082        assert_eq!(
1083            vote_state.get_and_update_authorized_voter(5).unwrap(),
1084            original_voter
1085        );
1086
1087        // Just like with the v3 tests, authorized voters for epochs 0..5 should
1088        // be purged, but only because we didn't cache an entry for current - 1.
1089        assert_eq!(vote_state.authorized_voters().len(), 1);
1090        for i in 0..5 {
1091            assert!(
1092                vote_state
1093                    .authorized_voters()
1094                    .get_authorized_voter(i)
1095                    .is_none()
1096            );
1097        }
1098
1099        // Say we're in epoch 7. Cache entries for both epochs 6 and 7.
1100        assert_eq!(
1101            vote_state.get_and_update_authorized_voter(6).unwrap(),
1102            original_voter
1103        );
1104        assert_eq!(
1105            vote_state.get_and_update_authorized_voter(7).unwrap(),
1106            original_voter
1107        );
1108
1109        // Now we should have length 2.
1110        assert_eq!(vote_state.authorized_voters().len(), 2);
1111
1112        // 0..=5 should still be purged.
1113        for i in 0..=5 {
1114            assert!(
1115                vote_state
1116                    .authorized_voters()
1117                    .get_authorized_voter(i)
1118                    .is_none()
1119            );
1120        }
1121
1122        // Set an authorized voter change at epoch 9.
1123        let new_authorized_voter = Pubkey::new_unique();
1124        vote_state
1125            .set_new_authorized_voter(&new_authorized_voter, 7, 9, None, |_| Ok(()))
1126            .unwrap();
1127
1128        // Try to get the authorized voter for epoch 8, unchanged
1129        assert_eq!(
1130            vote_state.get_and_update_authorized_voter(8).unwrap(),
1131            original_voter
1132        );
1133
1134        // Try to get the authorized voter for epoch 9 and onwards, should
1135        // be the new authorized voter
1136        for i in 9..12 {
1137            assert_eq!(
1138                vote_state.get_and_update_authorized_voter(i).unwrap(),
1139                new_authorized_voter
1140            );
1141        }
1142        assert_eq!(vote_state.authorized_voters().len(), 2);
1143
1144        // If we skip a few epochs ahead, only the current epoch is retained.
1145        assert_eq!(
1146            vote_state.get_and_update_authorized_voter(15).unwrap(),
1147            new_authorized_voter
1148        );
1149        assert_eq!(vote_state.authorized_voters().len(), 1);
1150
1151        // V4 purge boundary: at epoch 11 with voter set at epoch 10,
1152        // V4 retains epoch 10 (purge range 0..10) while V3 would not.
1153        {
1154            let voter_10 = Pubkey::new_unique();
1155            let voter_15 = Pubkey::new_unique();
1156            let mut v4 = VoteStateV4 {
1157                authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
1158                ..Default::default()
1159            };
1160            v4.authorized_voters.insert(10, voter_10);
1161            v4.authorized_voters.insert(15, voter_15);
1162            let mut handler = VoteStateHandler::new_v4(v4);
1163            let v4_voter = handler.get_and_update_authorized_voter(11).unwrap();
1164            assert_eq!(v4_voter, voter_10);
1165            // V4 purge range 0..10: epoch 10 retained.
1166            assert!(
1167                handler
1168                    .authorized_voters()
1169                    .get_authorized_voter(10)
1170                    .is_some()
1171            );
1172        }
1173
1174        // Epoch 0: saturating_sub(1) = 0, purge range 0..0 is empty.
1175        {
1176            let voter = Pubkey::new_unique();
1177            let v4 = VoteStateV4 {
1178                authorized_voters: AuthorizedVoters::new(0, voter),
1179                ..Default::default()
1180            };
1181            let mut handler = VoteStateHandler::new_v4(v4);
1182            let result = handler.get_and_update_authorized_voter(0).unwrap();
1183            assert_eq!(result, voter);
1184            assert!(!handler.authorized_voters().is_empty());
1185        }
1186    }
1187
1188    #[test_case(
1189        VoteStateV4::size_of(),
1190        VoteStateHandler::new_v4(get_max_sized_vote_state_v4()),
1191        |vote_state, data| {
1192            let versioned = VoteStateVersions::new_v4(vote_state.unwrap_v4());
1193            VoteStateV4::serialize(&versioned, data).unwrap();
1194        };
1195        "VoteStateV4"
1196    )]
1197    fn test_vote_state_max_size(
1198        max_size: usize,
1199        mut vote_state: VoteStateHandler,
1200        verify_serialize: fn(VoteStateHandler, &mut [u8]),
1201    ) {
1202        let mut max_sized_data = vec![0; max_size];
1203        let (start_leader_schedule_epoch, _) = vote_state.authorized_voters().last().unwrap();
1204        let start_current_epoch =
1205            start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1;
1206
1207        for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
1208            vote_state
1209                .set_new_authorized_voter(
1210                    &Pubkey::new_unique(),
1211                    i,
1212                    i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
1213                    None,
1214                    |_| Ok(()),
1215                )
1216                .unwrap();
1217
1218            verify_serialize(vote_state.clone(), &mut max_sized_data);
1219        }
1220    }
1221
1222    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1223    fn test_vote_state_epoch_credits(mut vote_state: VoteStateHandler) {
1224        assert_eq!(vote_state.credits(), 0);
1225        assert_eq!(vote_state.epoch_credits().clone(), vec![]);
1226
1227        let mut expected = vec![];
1228        let mut credits = 0;
1229        let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1230        for epoch in 0..epochs {
1231            for _j in 0..epoch {
1232                vote_state.increment_credits(epoch, 1);
1233                credits += 1;
1234            }
1235            expected.push((epoch, credits, credits - epoch));
1236        }
1237
1238        while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
1239            expected.remove(0);
1240        }
1241
1242        assert_eq!(vote_state.credits(), credits);
1243        assert_eq!(vote_state.epoch_credits().clone(), expected);
1244    }
1245
1246    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1247    fn test_vote_state_epoch0_no_credits(mut vote_state: VoteStateHandler) {
1248        assert_eq!(vote_state.epoch_credits().len(), 0);
1249        vote_state.increment_credits(1, 1);
1250        assert_eq!(vote_state.epoch_credits().len(), 1);
1251
1252        vote_state.increment_credits(2, 1);
1253        assert_eq!(vote_state.epoch_credits().len(), 2);
1254    }
1255
1256    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1257    fn test_vote_state_increment_credits(mut vote_state: VoteStateHandler) {
1258        let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1259        for i in 0..credits {
1260            vote_state.increment_credits(i, 1);
1261        }
1262        assert_eq!(vote_state.credits(), credits);
1263        assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
1264    }
1265
1266    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1267    fn test_vote_process_timestamp(mut vote_state: VoteStateHandler) {
1268        let (slot, timestamp) = (15, 1_575_412_285);
1269        vote_state.set_last_timestamp(BlockTimestamp { slot, timestamp });
1270
1271        assert_eq!(
1272            vote_state.process_timestamp(slot - 1, timestamp + 1),
1273            Err(VoteError::TimestampTooOld)
1274        );
1275        assert_eq!(
1276            vote_state.last_timestamp(),
1277            &BlockTimestamp { slot, timestamp }
1278        );
1279        assert_eq!(
1280            vote_state.process_timestamp(slot + 1, timestamp - 1),
1281            Err(VoteError::TimestampTooOld)
1282        );
1283        assert_eq!(
1284            vote_state.process_timestamp(slot, timestamp + 1),
1285            Err(VoteError::TimestampTooOld)
1286        );
1287        assert_eq!(vote_state.process_timestamp(slot, timestamp), Ok(()));
1288        assert_eq!(
1289            vote_state.last_timestamp(),
1290            &BlockTimestamp { slot, timestamp }
1291        );
1292        assert_eq!(vote_state.process_timestamp(slot + 1, timestamp), Ok(()));
1293        assert_eq!(
1294            vote_state.last_timestamp(),
1295            &BlockTimestamp {
1296                slot: slot + 1,
1297                timestamp
1298            }
1299        );
1300        assert_eq!(
1301            vote_state.process_timestamp(slot + 2, timestamp + 1),
1302            Ok(())
1303        );
1304        assert_eq!(
1305            vote_state.last_timestamp(),
1306            &BlockTimestamp {
1307                slot: slot + 2,
1308                timestamp: timestamp + 1
1309            }
1310        );
1311
1312        // Test initial vote
1313        vote_state.set_last_timestamp(BlockTimestamp::default());
1314        assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
1315    }
1316
1317    fn init_vote_account_state_v4_and_assert(
1318        vote_pubkey: Pubkey,
1319        vote_account: AccountSharedData,
1320        vote_init: &VoteInit,
1321        clock: &Clock,
1322        rent: Rent,
1323        expected_result: Result<(), InstructionError>,
1324    ) {
1325        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1326        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1327        let mut vote_account = instruction_context
1328            .try_borrow_instruction_account(0)
1329            .unwrap();
1330
1331        // Initialize.
1332        let result = VoteStateHandler::init_vote_account_state(
1333            &mut vote_account,
1334            vote_init,
1335            clock,
1336            VoteStateTargetVersion::V4,
1337        );
1338        assert_eq!(result, expected_result);
1339
1340        if result.is_ok() {
1341            let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1342            assert!(matches!(vote_state_versions, VoteStateVersions::V4(_)));
1343            assert!(!vote_state_versions.is_uninitialized());
1344
1345            // Verify fields.
1346            if let VoteStateVersions::V4(v4) = vote_state_versions {
1347                assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1348                assert_eq!(
1349                    v4.authorized_voters.get_authorized_voter(clock.epoch),
1350                    Some(vote_init.authorized_voter)
1351                );
1352                assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1353                assert_eq!(
1354                    v4.inflation_rewards_commission_bps,
1355                    (vote_init.commission as u16) * 100
1356                );
1357
1358                // SIMD-0185 fields
1359                assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
1360                assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
1361                assert_eq!(
1362                    v4.block_revenue_commission_bps,
1363                    DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1364                );
1365
1366                // Fields that should be default
1367                assert_eq!(v4.pending_delegator_rewards, 0);
1368                assert_eq!(v4.bls_pubkey_compressed, None);
1369                assert!(v4.votes.is_empty());
1370                assert_eq!(v4.root_slot, None);
1371                assert!(v4.epoch_credits.is_empty());
1372                assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1373            } else {
1374                panic!("should be v4");
1375            }
1376        }
1377    }
1378
1379    fn deinit_vote_account_state_v4_and_assert(
1380        vote_pubkey: Pubkey,
1381        vote_account: AccountSharedData,
1382        rent: Rent,
1383    ) {
1384        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1385        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1386        let mut vote_account = instruction_context
1387            .try_borrow_instruction_account(0)
1388            .unwrap();
1389
1390        // Deinitialize.
1391        VoteStateHandler::deinitialize_vote_account_state(
1392            &mut vote_account,
1393            VoteStateTargetVersion::V4,
1394        )
1395        .unwrap();
1396
1397        // Per SIMD-0185, V4 should completely zero out the account data.
1398        let account_data = vote_account.get_data();
1399        assert!(account_data.iter().all(|&b| b == 0),);
1400
1401        // Vote account was completely zeroed, so this should deserialize as
1402        // uninitialized.
1403        let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1404        assert!(matches!(
1405            vote_state_versions,
1406            VoteStateVersions::Uninitialized
1407        ));
1408        assert!(vote_state_versions.is_uninitialized());
1409    }
1410
1411    #[test]
1412    fn test_init_vote_account_state_v4() {
1413        let vote_pubkey = Pubkey::new_unique();
1414        let vote_init = VoteInit {
1415            node_pubkey: Pubkey::new_unique(),
1416            authorized_voter: Pubkey::new_unique(),
1417            authorized_withdrawer: Pubkey::new_unique(),
1418            commission: 5,
1419        };
1420        let clock = Clock::default();
1421        let rent = Rent::default();
1422
1423        // First create a vote account that's too small for v4.
1424        let v1_14_11_size = VoteState1_14_11::size_of();
1425        let lamports = rent.minimum_balance(v1_14_11_size);
1426        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1427
1428        // Initialize - should fail.
1429        init_vote_account_state_v4_and_assert(
1430            vote_pubkey,
1431            vote_account,
1432            &vote_init,
1433            &clock,
1434            rent.clone(),
1435            Err(InstructionError::AccountNotRentExempt),
1436        );
1437
1438        // Create a vote account that's too small for v4, but has enough
1439        // lamports for resize.
1440        let v4_size = VoteStateV4::size_of();
1441        let lamports = rent.minimum_balance(v4_size);
1442        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1443
1444        // Initialize - should resize and create v4.
1445        init_vote_account_state_v4_and_assert(
1446            vote_pubkey,
1447            vote_account,
1448            &vote_init,
1449            &clock,
1450            rent.clone(),
1451            Ok(()),
1452        );
1453
1454        // Now create a vote account that's large enough for v4.
1455        let lamports = rent.minimum_balance(v4_size);
1456        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1457
1458        // Initialize - should create v4.
1459        init_vote_account_state_v4_and_assert(
1460            vote_pubkey,
1461            vote_account,
1462            &vote_init,
1463            &clock,
1464            rent,
1465            Ok(()),
1466        );
1467    }
1468
1469    #[test]
1470    fn test_deinitialize_vote_account_state_v4() {
1471        let vote_pubkey = Pubkey::new_unique();
1472        let rent = Rent::default();
1473
1474        // First create a vote account that's too small for v4.
1475        let v1_14_11_size = VoteState1_14_11::size_of();
1476        let lamports = rent.minimum_balance(v1_14_11_size);
1477        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1478
1479        // Deinitialize - should fail.
1480        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1481
1482        // Create a vote account that's too small for v4, but has enough
1483        // lamports for resize.
1484        let v4_size = VoteStateV4::size_of();
1485        let lamports = rent.minimum_balance(v4_size);
1486        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1487
1488        // Deinitialize - should resize and create v4.
1489        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1490
1491        // Now create a vote account that's large enough for v4.
1492        let lamports = rent.minimum_balance(v4_size);
1493        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1494
1495        // Deinitialize - should create v4.
1496        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent);
1497    }
1498
1499    #[test]
1500    fn test_v4_commission_basis_points() {
1501        // Test that commission is properly converted to basis points
1502        // (multiplied by 100), as per SIMD-0185.
1503        let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1504
1505        for (input, expected) in [
1506            (0, 0),
1507            (1, 100),
1508            (5, 500),
1509            (10, 1_000),
1510            (25, 2_500),
1511            (50, 5_000),
1512            (75, 7_500),
1513            (100, 10_000),
1514            (255, 25_500),
1515        ] {
1516            handler.set_commission(input);
1517            assert_eq!(handler.commission(), input);
1518
1519            // Verify the internal V4 state has the correct basis points.
1520            let vote_state_v4 = handler.as_ref_v4();
1521            assert_eq!(vote_state_v4.inflation_rewards_commission_bps, expected);
1522        }
1523    }
1524
1525    #[test]
1526    fn test_v4_commission_getter_clamps_extreme_bps() {
1527        // Verify commission() clamps to u8::MAX for extreme bps values
1528        // instead of wrapping around. SIMD-0291 allows storing any u16 as
1529        // inflation_rewards_commission_bps — the legacy getter must not
1530        // produce misleading values.
1531        for (bps, expected) in [
1532            (10_000, 100),   // normal
1533            (25_500, 255),   // exact boundary, no clamping needed
1534            (25_501, 255),   // just past boundary, clamped
1535            (25_600, 255),   // would wrap to 0 without clamping
1536            (u16::MAX, 255), // would wrap to 143 without clamping
1537        ] {
1538            let vote_state = VoteStateV4 {
1539                inflation_rewards_commission_bps: bps,
1540                ..Default::default()
1541            };
1542            let handler = VoteStateHandler::new_v4(vote_state);
1543            assert_eq!(handler.commission(), expected);
1544        }
1545    }
1546
1547    #[test]
1548    fn test_commission_v3_v4_preserved() {
1549        // Verify commission() returns the same value through V3→V4 migration.
1550        let vote_pubkey = Pubkey::new_unique();
1551        for n in [0u8, 1, 50, 100, 101, 255] {
1552            let v3 = VoteStateV3 {
1553                commission: n,
1554                ..Default::default()
1555            };
1556
1557            let versioned = VoteStateVersionsInline::V3(v3);
1558            let migrated = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1559            let migrated_handler = VoteStateHandler::new_v4(migrated);
1560            assert_eq!(migrated_handler.commission(), n);
1561            assert_eq!(
1562                migrated_handler
1563                    .as_ref_v4()
1564                    .inflation_rewards_commission_bps,
1565                n as u16 * 100
1566            );
1567        }
1568    }
1569
1570    #[test]
1571    fn test_v4_conversion_from_all_versions() {
1572        // Test conversion from all vote state versions to v4 per SIMD-0185.
1573        let vote_pubkey = Pubkey::new_unique();
1574        let node_pubkey = Pubkey::new_unique();
1575        let authorized_voter = Pubkey::new_unique();
1576        let authorized_withdrawer = Pubkey::new_unique();
1577        let commission = 42u8;
1578        let votes = vec![Lockout::new(100)];
1579        let votes_deque: VecDeque<Lockout> = votes.iter().cloned().collect();
1580        let epoch_credits = vec![(5, 200, 100)];
1581        let root_slot = Some(50);
1582
1583        // Helper to verify v4 defaults per SIMD-0185.
1584        let verify_v4_defaults = |v4_state: &VoteStateV4, expected_commission_bps: u16| {
1585            assert_eq!(
1586                v4_state.inflation_rewards_commission_bps,
1587                expected_commission_bps
1588            );
1589            assert_eq!(v4_state.inflation_rewards_collector, vote_pubkey);
1590            assert_eq!(v4_state.block_revenue_collector, node_pubkey);
1591            assert_eq!(
1592                v4_state.block_revenue_commission_bps,
1593                DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1594            );
1595            assert_eq!(v4_state.pending_delegator_rewards, 0);
1596            assert_eq!(v4_state.bls_pubkey_compressed, None);
1597        };
1598
1599        // V0_23_5
1600        // NOTE: On target_os = "solana", VoteState0_23_5::deserialize should
1601        // fail, but we can't replicate that in these unit tests. This should
1602        // be tested in the program's processor tests.
1603
1604        // V1_14_11
1605        {
1606            let vote_state_v2 = VoteState1_14_11 {
1607                node_pubkey,
1608                authorized_withdrawer,
1609                commission,
1610                authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1611                votes: votes_deque,
1612                epoch_credits: epoch_credits.clone(),
1613                root_slot,
1614                ..VoteState1_14_11::default()
1615            };
1616
1617            let versioned = VoteStateVersionsInline::V1_14_11(vote_state_v2.clone());
1618            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1619
1620            // Compare fields that were already present in V1_14_11.
1621            assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1622            assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1623            assert_eq!(
1624                vote_state_v4.authorized_voters,
1625                vote_state_v2.authorized_voters
1626            );
1627            assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1628            assert_eq!(vote_state_v4.root_slot, root_slot);
1629            assert_eq!(vote_state_v4.votes.len(), vote_state_v2.votes.len());
1630            for (v4_vote, v2_vote) in vote_state_v4.votes.iter().zip(vote_state_v2.votes.iter()) {
1631                assert_eq!(v4_vote.lockout, *v2_vote);
1632            }
1633
1634            // Verify SIMD-0185 defaults.
1635            verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1636        }
1637
1638        // V3
1639        {
1640            let vote_init = VoteInit {
1641                node_pubkey,
1642                authorized_voter,
1643                authorized_withdrawer,
1644                commission,
1645            };
1646            let mut vote_state_v3 = VoteStateV3::new(&vote_init, &Clock::default());
1647            for lockout in &votes {
1648                vote_state_v3.votes.push_back(LandedVote {
1649                    latency: 1,
1650                    lockout: *lockout,
1651                });
1652            }
1653            vote_state_v3.epoch_credits = epoch_credits.clone();
1654            vote_state_v3.root_slot = root_slot;
1655
1656            let versioned = VoteStateVersionsInline::V3(vote_state_v3.clone());
1657            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1658
1659            // Compare fields that were already present in V3.
1660            assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1661            assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1662            assert_eq!(
1663                vote_state_v4.authorized_voters,
1664                vote_state_v3.authorized_voters
1665            );
1666            assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1667            assert_eq!(vote_state_v4.root_slot, root_slot);
1668            assert_eq!(vote_state_v4.last_timestamp, vote_state_v3.last_timestamp);
1669            assert_eq!(vote_state_v4.votes, vote_state_v3.votes);
1670
1671            // Verify SIMD-0185 defaults.
1672            verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1673        }
1674
1675        // V4
1676        {
1677            let mut initial_vote_state_v4 = VoteStateV4 {
1678                node_pubkey,
1679                authorized_withdrawer,
1680                inflation_rewards_commission_bps: 1234,
1681                block_revenue_commission_bps: 5678,
1682                inflation_rewards_collector: Pubkey::new_unique(),
1683                block_revenue_collector: Pubkey::new_unique(),
1684                pending_delegator_rewards: 999,
1685                bls_pubkey_compressed: Some([42; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
1686                authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1687                epoch_credits,
1688                root_slot,
1689                ..VoteStateV4::default()
1690            };
1691            for lockout in &votes {
1692                initial_vote_state_v4.votes.push_back(LandedVote {
1693                    latency: 1,
1694                    lockout: *lockout,
1695                });
1696            }
1697
1698            let versioned = VoteStateVersionsInline::V4(initial_vote_state_v4.clone());
1699            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1700
1701            // Should be an exact copy.
1702            assert_eq!(vote_state_v4, initial_vote_state_v4);
1703        }
1704    }
1705
1706    #[test]
1707    fn test_v4_migration_with_pre_existing_voters() {
1708        // Verify V4 purge rules apply to the migrated voter map from
1709        // both V1_14_11 and V3.
1710        let vote_pubkey = Pubkey::new_unique();
1711        let voter_5 = Pubkey::new_unique();
1712        let voter_7 = Pubkey::new_unique();
1713        let voter_9 = Pubkey::new_unique();
1714
1715        let build_voters = || {
1716            let mut voters = AuthorizedVoters::new(0, Pubkey::new_unique());
1717            voters.insert(5, voter_5);
1718            voters.insert(7, voter_7);
1719            voters.insert(9, voter_9);
1720            voters
1721        };
1722
1723        let assert_purge = |handler: &mut VoteStateHandler| {
1724            // Advance to epoch 10. V4 purge range 0..9.
1725            let voter = handler.get_and_update_authorized_voter(10).unwrap();
1726            assert_eq!(voter, voter_9);
1727            assert!(
1728                handler
1729                    .authorized_voters()
1730                    .get_authorized_voter(9)
1731                    .is_some()
1732            );
1733            assert!(
1734                handler
1735                    .authorized_voters()
1736                    .get_authorized_voter(7)
1737                    .is_none()
1738            );
1739            assert!(
1740                handler
1741                    .authorized_voters()
1742                    .get_authorized_voter(5)
1743                    .is_none()
1744            );
1745        };
1746
1747        // V1_14_11 → V4
1748        {
1749            let v1 = VoteState1_14_11 {
1750                authorized_voters: build_voters(),
1751                ..Default::default()
1752            };
1753            let versioned = VoteStateVersionsInline::V1_14_11(v1);
1754            let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1755            let mut handler = VoteStateHandler::new_v4(v4);
1756            assert_purge(&mut handler);
1757        }
1758
1759        // V3 → V4
1760        {
1761            let v3 = VoteStateV3 {
1762                authorized_voters: build_voters(),
1763                ..Default::default()
1764            };
1765            let versioned = VoteStateVersionsInline::V3(v3);
1766            let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1767            let mut handler = VoteStateHandler::new_v4(v4);
1768            assert_purge(&mut handler);
1769        }
1770    }
1771
1772    #[test]
1773    fn test_v3_v4_size_equality() {
1774        let v3_size = VoteStateV3::size_of();
1775        let v4_size = VoteStateV4::size_of();
1776        assert_eq!(v3_size, v4_size, "V3 and V4 should have the same size");
1777    }
1778
1779    #[test_case(
1780        VoteState1_14_11::size_of(),
1781        false,
1782        Err(InstructionError::AccountNotRentExempt);
1783        "v1_14_11 size insufficient lamports - no fallback"
1784    )]
1785    #[test_case(
1786        VoteState1_14_11::size_of(),
1787        true,
1788        Ok(());
1789        "v1_14_11 size sufficient lamports - resize and succeed"
1790    )]
1791    #[test_case(
1792        VoteStateV4::size_of(),
1793        true,
1794        Ok(());
1795        "v4 properly sized with rent-exempt lamports - succeed"
1796    )]
1797    fn test_v4_resize_behavior(
1798        account_size: usize,
1799        sufficient_lamports: bool,
1800        expected_result: Result<(), InstructionError>,
1801    ) {
1802        let vote_pubkey = Pubkey::new_unique();
1803        let node_pubkey = Pubkey::new_unique();
1804        let authorized_voter = Pubkey::new_unique();
1805        let authorized_withdrawer = Pubkey::new_unique();
1806        let rent = Rent::default();
1807
1808        let vote_init = VoteInit {
1809            node_pubkey,
1810            authorized_voter,
1811            authorized_withdrawer,
1812            commission: 42,
1813        };
1814
1815        let lamports = if sufficient_lamports {
1816            rent.minimum_balance(VoteStateV4::size_of())
1817        } else {
1818            rent.minimum_balance(account_size)
1819        };
1820
1821        let mut vote_account = AccountSharedData::new(lamports, account_size, &id());
1822        vote_account.set_data_from_slice(&vec![0; account_size]);
1823
1824        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1825        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1826        let mut vote_account_borrowed = instruction_context
1827            .try_borrow_instruction_account(0)
1828            .unwrap();
1829
1830        let result = VoteStateHandler::init_vote_account_state(
1831            &mut vote_account_borrowed,
1832            &vote_init,
1833            &Clock::default(),
1834            VoteStateTargetVersion::V4,
1835        );
1836
1837        assert_eq!(result, expected_result);
1838    }
1839
1840    #[test]
1841    fn test_get_and_update_authorized_voter_v4_with_bls() {
1842        let vote_pubkey = Pubkey::new_unique();
1843        let original_voter = Pubkey::new_unique();
1844        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1845            &vote_pubkey,
1846            &VoteInit {
1847                node_pubkey: original_voter,
1848                authorized_voter: original_voter,
1849                authorized_withdrawer: original_voter,
1850                commission: 0,
1851            },
1852            &Clock::default(),
1853        ));
1854
1855        // It has some initial authorized voter but no BLS pubkey.
1856        assert_eq!(vote_state.authorized_voters().len(), 1);
1857        assert_eq!(
1858            *vote_state.authorized_voters().first().unwrap().1,
1859            original_voter
1860        );
1861        assert!(!vote_state.has_bls_pubkey());
1862
1863        // Update authorized voter with BLS pubkey.
1864        let new_voter = Pubkey::new_unique();
1865        let bls_pubkey_compressed = [3u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1866        vote_state
1867            .set_new_authorized_voter(&new_voter, 0, 1, Some(&bls_pubkey_compressed), |_| Ok(()))
1868            .unwrap();
1869        assert_eq!(vote_state.authorized_voters().len(), 2);
1870        assert_eq!(*vote_state.authorized_voters().last().unwrap().1, new_voter);
1871        assert_eq!(
1872            vote_state.as_ref_v4().bls_pubkey_compressed,
1873            Some(bls_pubkey_compressed)
1874        );
1875        assert!(vote_state.has_bls_pubkey());
1876
1877        // Now update authorized voter again with another BLS pubkey.
1878        let newer_voter = Pubkey::new_unique();
1879        let newer_bls_pubkey_compressed = [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1880        vote_state
1881            .set_new_authorized_voter(
1882                &newer_voter,
1883                1,
1884                2,
1885                Some(&newer_bls_pubkey_compressed),
1886                |_| Ok(()),
1887            )
1888            .unwrap();
1889        assert_eq!(vote_state.authorized_voters().len(), 3);
1890        assert_eq!(
1891            *vote_state.authorized_voters().last().unwrap().1,
1892            newer_voter
1893        );
1894        assert_eq!(
1895            vote_state.as_ref_v4().bls_pubkey_compressed,
1896            Some(newer_bls_pubkey_compressed)
1897        );
1898        assert!(vote_state.has_bls_pubkey());
1899    }
1900
1901    #[test]
1902    fn test_set_inflation_rewards_commission_bps() {
1903        let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1904
1905        // First test some "normal" values.
1906        for bps in [0, 100, 500, 1_000, 5_000, 10_000] {
1907            handler.set_inflation_rewards_commission_bps(bps);
1908            let v4 = handler.as_ref_v4();
1909            assert_eq!(v4.inflation_rewards_commission_bps, bps);
1910            // commission() should return bps / 100
1911            assert_eq!(handler.commission(), (bps / 100) as u8);
1912        }
1913
1914        // Now test values > 10,000 are allowed at program level.
1915        // Capping happens during reward calculation, not storage.
1916        for bps in [10_001, 15_000, u16::MAX] {
1917            handler.set_inflation_rewards_commission_bps(bps);
1918            let v4 = handler.as_ref_v4();
1919            assert_eq!(v4.inflation_rewards_commission_bps, bps);
1920        }
1921    }
1922
1923    #[test]
1924    fn test_compute_vote_latency() {
1925        for (voted_for_slot, current_slot, expected) in [
1926            (0, 0, 0),                   // zero latency
1927            (0, 1, 1),                   // normal
1928            (0, 255, 255),               // max u8
1929            (0, 256, 255),               // clamped to u8::MAX
1930            (0, 1_000, 255),             // large gap, clamped
1931            (u64::MAX - 1, u64::MAX, 1), // near-max slots
1932            (5, 5, 0),                   // same slot
1933            (10, 5, 0),                  // current < voted, saturating_sub → 0
1934        ] {
1935            assert_eq!(compute_vote_latency(voted_for_slot, current_slot), expected);
1936        }
1937    }
1938
1939    #[test]
1940    fn test_init_vote_account_state_v2() {
1941        let vote_pubkey = Pubkey::new_unique();
1942        let inflation_rewards_collector = Pubkey::new_unique();
1943        let block_revenue_collector = Pubkey::new_unique();
1944        let vote_init = VoteInitV2 {
1945            node_pubkey: Pubkey::new_unique(),
1946            authorized_voter: Pubkey::new_unique(),
1947            authorized_voter_bls_pubkey: [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1948            authorized_voter_bls_proof_of_possession: [8u8;
1949                BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1950            authorized_withdrawer: Pubkey::new_unique(),
1951            inflation_rewards_commission_bps: 1_234,
1952            block_revenue_commission_bps: 5_678,
1953        };
1954        let clock = Clock::default();
1955        let rent = Rent::default();
1956
1957        let v4_size = VoteStateV4::size_of();
1958        let lamports = rent.minimum_balance(v4_size);
1959        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1960
1961        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1962        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1963        let mut vote_account = instruction_context
1964            .try_borrow_instruction_account(0)
1965            .unwrap();
1966
1967        VoteStateHandler::init_vote_account_state_v2(
1968            &mut vote_account,
1969            &vote_init,
1970            &inflation_rewards_collector,
1971            &block_revenue_collector,
1972            &clock,
1973            VoteStateTargetVersion::V4,
1974        )
1975        .unwrap();
1976
1977        let VoteStateVersions::V4(v4) = vote_account.get_state::<VoteStateVersions>().unwrap()
1978        else {
1979            panic!("should be v4");
1980        };
1981
1982        assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1983        assert_eq!(
1984            v4.authorized_voters.get_authorized_voter(clock.epoch),
1985            Some(vote_init.authorized_voter),
1986        );
1987        assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1988        assert_eq!(
1989            v4.bls_pubkey_compressed,
1990            Some(vote_init.authorized_voter_bls_pubkey),
1991        );
1992        assert_eq!(
1993            v4.inflation_rewards_commission_bps,
1994            vote_init.inflation_rewards_commission_bps,
1995        );
1996        assert_eq!(
1997            v4.block_revenue_commission_bps,
1998            vote_init.block_revenue_commission_bps,
1999        );
2000        assert_eq!(v4.inflation_rewards_collector, inflation_rewards_collector);
2001        assert_eq!(v4.block_revenue_collector, block_revenue_collector);
2002        assert_eq!(v4.pending_delegator_rewards, 0);
2003        assert!(v4.votes.is_empty());
2004        assert_eq!(v4.root_slot, None);
2005        assert!(v4.epoch_credits.is_empty());
2006        assert_eq!(v4.last_timestamp, BlockTimestamp::default());
2007    }
2008}